上一章我們完成了Handler優(yōu)化,統(tǒng)一錯誤處理,和加載框的實現(xiàn)思路。
現(xiàn)在我們要進行的是讓之前的框架支持HTTPS安全訪問,同時利用之前的對話框樣式,來創(chuàng)建消息提示框。本章結(jié)束后,我會將這次的代碼放到github上,下一章開始就用這個應(yīng)用來研究反編譯
。
消息提示框
新建res/drawable/bg_dialog.xml
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<corners android:radius="5dp" />
<solid android:color="#ececed" />
</shape>
新建res/layout/dialog_message.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/bg_dialog"
android:gravity="center"
android:orientation="vertical">
<RelativeLayout
android:layout_width="250dp"
android:layout_height="wrap_content"
android:layout_margin="20dp">
<TextView
android:id="@+id/tv_dialog_message"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:lineSpacingExtra="5dp"
android:textColor="#4f4f4f"
android:textSize="14dp" />
</RelativeLayout>
<View
android:layout_width="match_parent"
android:layout_height="1px"
android:background="#d9d9d9" />
<TextView
android:id="@+id/tv_dialog_ok"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/bg_dialog_message_ok_selector"
android:gravity="center"
android:padding="10dp"
android:text="@string/dialog_info_ok"
android:textColor="#4fa0e6"
android:textSize="16dp" />
</LinearLayout>
有沒有發(fā)現(xiàn)所有textSize單位我用的都是dp?雖然官方推薦文字大小單位用sp,但是由于sp與dp的比值會隨系統(tǒng)設(shè)置里的字體大小變化而變化,屆時可能有布局上影響,因此我還是習(xí)慣性用dp表示字號。
在res/values/strings.xml中加入dialog_info_ok
<string name="dialog_info_ok">我知道了</string>
新建res/drawable/bg_dialog_message_ok_selector.xml
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_focused="true">
<shape>
<corners android:bottomLeftRadius="5dp" android:bottomRightRadius="5dp" />
<solid android:color="@color/bg_dialog_option_pressed" />
</shape>
</item>
<item android:state_selected="true">
<shape>
<corners android:bottomLeftRadius="5dp" android:bottomRightRadius="5dp" />
<solid android:color="@color/bg_dialog_option_pressed" />
</shape>
</item>
<item android:state_pressed="true">
<shape>
<corners android:bottomLeftRadius="5dp" android:bottomRightRadius="5dp" />
<solid android:color="@color/bg_dialog_option_pressed" />
</shape>
</item>
<item android:drawable="@android:color/transparent" />
</selector>
在res/values/colors.xml中加入bg_dialog_option_pressed
<color name="bg_dialog_option_pressed">#d6d6d6</color>
新建com.joyin.volleydemo.view.dialog.MessageDialog.java
package com.joyin.volleydemo.view.dialog;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.TextView;
import com.joyin.volleydemo.R;
/**
* Created by joyin on 16-4-4.
*/
public class MessageDialog extends BaseDialog {
private TextView mTvMessage;
public MessageDialog(Context context) {
super(context);
}
public void setMessage(String message) {
mTvMessage.setText(message);
}
@Override
protected View getDefaultView(Context context) {
View view = LayoutInflater.from(context).inflate(R.layout.dialog_message, null);
mTvMessage = (TextView) view.findViewById(R.id.tv_dialog_message);
TextView tvOk = (TextView) view.findViewById(R.id.tv_dialog_ok);
tvOk.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
dismiss();
if (mOnClickListener != null) {
mOnClickListener.onClick(v);
}
}
});
return view;
}
private View.OnClickListener mOnClickListener;
public void setOnClickListener(View.OnClickListener listener) {
this.mOnClickListener = listener;
}
}
顯示消息對話框的時候,只需要創(chuàng)建對象,設(shè)置內(nèi)容,然后show()就可以了,同時支持對點擊事件自行處理。
現(xiàn)在我們來嘗試一下,在MainActivity中網(wǎng)絡(luò)請求返回結(jié)果錯誤的地方添加三行代碼:
MessageDialog dialog = new MessageDialog(MainActivity.this);
dialog.setMessage("獲取請求失敗了");
dialog.show();
編譯運行
同理,要做有標(biāo)題,有內(nèi)容,下方有確定和取消的對話框,也可以用一樣的方法實現(xiàn)。
在現(xiàn)在的基礎(chǔ)上做Https模塊,當(dāng)我們點擊“我知道了”,就跳到下一個頁面HttpsTestActivity
。
新建布局文件res/layout/activity_test_https.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/tv_https_result"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</RelativeLayout>
新建com.joyin.volleydemo.activity.HttpsTestActivity.java
package com.joyin.volleydemo.activity;
import android.os.Bundle;
import com.joyin.volleydemo.R;
/**
* Created by joyin on 16-4-4.
*/
public class HttpsTestActivity extends BaseActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test_https);
}
}
記得在AndroidManifest.xml中注冊
<activity android:name=".activity.HttpsTestActivity" />
在MainActivity中設(shè)置對話框的點擊事件:
MessageDialog dialog = new MessageDialog(MainActivity.this);
dialog.setMessage("獲取請求失敗了");
dialog.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startActivity(new Intent(MainActivity.this, HttpsTestActivity.class));
}
});
dialog.show();
OK,現(xiàn)在已經(jīng)能進入下一個頁面了,接下來我們來實現(xiàn)Https。
支持HTTPS
本文HTTPS相關(guān)技術(shù)點來自《Android 網(wǎng)絡(luò)--我是怎么做的: Volley+OkHttp+Https》一文。
我當(dāng)初是看了這篇文章后,以同樣的方法實現(xiàn)的,非常感謝這篇文章原作者,原文以12306作為案例,為了尊重原作者,同時也因為不便于用商業(yè)項目做案例,所以我也同樣用12306來演示,大部分代碼來自原文。
另,我將證書轉(zhuǎn)換為bks文件,用《Android 網(wǎng)絡(luò)--我是怎么做的: Volley+OkHttp+Https》中的方法并未成功,而是參照在Android應(yīng)用中使用自定義證書的HTTPS連接(下)一文,導(dǎo)出cer格式證書,然后轉(zhuǎn)換成功。
我們直接進入《Android 網(wǎng)絡(luò)--我是怎么做的: Volley+OkHttp+Https》文中,根據(jù)該作者的github,找到他已經(jīng)存在的kyfw.bks文件。將其放入我們項目的res/raw/目錄下。
在build.gradle中的dependencies塊中加入:
compile 'com.squareup.okhttp:okhttp:2.4.0'
compile 'com.squareup.okhttp:okhttp-urlconnection:2.4.0'
新建com.joyin.volleydemo.utils.network.SelfSignSslOkHttpStack.java
package com.joyin.volleydemo.utils.network;
import com.android.volley.toolbox.HurlStack;
import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.OkUrlFactory;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Map;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLSocketFactory;
/**
* Created by joyin on 16-4-5.
*/
public class SelfSignSslOkHttpStack extends HurlStack {
private OkHttpClient okHttpClient;
private Map<String, SSLSocketFactory> socketFactoryMap;
public SelfSignSslOkHttpStack(Map<String, SSLSocketFactory> factoryMap) {
this(new OkHttpClient(), factoryMap);
}
public SelfSignSslOkHttpStack(OkHttpClient okHttpClient, Map<String, SSLSocketFactory> factoryMap) {
this.okHttpClient = okHttpClient;
this.socketFactoryMap = factoryMap;
}
@Override
protected HttpURLConnection createConnection(URL url) throws IOException {
if ("https".equals(url.getProtocol()) && socketFactoryMap.containsKey(url.getHost())) {
HttpsURLConnection connection = (HttpsURLConnection) new OkUrlFactory(okHttpClient).open(url);
connection.setSSLSocketFactory(socketFactoryMap.get(url.getHost()));
return connection;
} else {
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
return connection;
}
}
}
新建com.joyin.volleydemo.utils.network.RequestManager.java
package com.joyin.volleydemo.utils.network;
import android.content.Context;
import android.text.TextUtils;
import com.android.volley.Request;
import com.android.volley.RequestQueue;
import com.android.volley.toolbox.HurlStack;
import com.android.volley.toolbox.Volley;
import com.joyin.volleydemo.R;
import java.io.IOException;
import java.io.InputStream;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.util.Hashtable;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManagerFactory;
/**
* Created by joyin on 16-4-5.
*/
public class RequestManager {
private RequestManager() {
}
private static RequestManager instance;
public RequestQueue mRequestQueue;
public RequestManager(Context context) {
mRequestQueue = newRequestQueue(context);
}
public static RequestManager getInstance(Context context) {
if (instance == null) {
instance = new RequestManager(context);
}
return instance;
}
public static HurlStack getSelfSignSslOkHttpStack(Context context) {
String[] hosts = {"kyfw.12306.cn"};
int[] certRes = {R.raw.kyfw};
String[] certPass = {"asdfqaz"};
try {
Hashtable<String, SSLSocketFactory> socketFactoryMap = new Hashtable<>(hosts.length);
for (int i = 0; i < certRes.length; i++) {
int res = certRes[i];
String password = certPass[i];
SSLSocketFactory sslSocketFactory = createSSLSocketFactory(context, res, password);
socketFactoryMap.put(hosts[i], sslSocketFactory);
}
HurlStack stack = new SelfSignSslOkHttpStack(socketFactoryMap);
return stack;
} catch (Exception e) {
return null;
}
}
private static SSLSocketFactory createSSLSocketFactory(Context context, int res, String password)
throws CertificateException, NoSuchAlgorithmException, IOException,
KeyStoreException, KeyManagementException {
InputStream inputStream = context.getResources().openRawResource(res);
KeyStore keyStore = KeyStore.getInstance("BKS");
keyStore.load(inputStream, password.toCharArray());
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(keyStore);
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, tmf.getTrustManagers(), new SecureRandom());
return sslContext.getSocketFactory();
}
private RequestQueue newRequestQueue(Context context) {
RequestQueue requestQueue = Volley.newRequestQueue(context, getSelfSignSslOkHttpStack(context));
return requestQueue;
}
public RequestQueue getRequestQueue() {
return mRequestQueue;
}
public void addRequest(Request request, Object tag) {
if (tag != null) {
request.setTag(tag);
} else if (!TextUtils.isEmpty(request.getUrl())) {
request.setTag(request.getUrl());
}
mRequestQueue.add(request);
}
}
因為我們這一次請求12306,返回的數(shù)據(jù)格式不是code-data的JSON字符串,所以我們修改RequestHandler的onVolleyResponse方法,將code校驗加上異常保護(真實項目中理論上不需要這一步,這里是作為測試案例,因為返回格式不一樣,所以臨時修改)。
private static void onVolleyResponse(String response, Handler handler, int what, Bundle bundle) {
LogUtil.d(response);
try {
JSONObject json = JSON.parseObject(response);
if (json != null && json.containsKey("code")) {
int code = json.getIntValue("code");
if (code != 0) {
// 如果code不為0,則走錯誤處理流程
Message msg = handler.obtainMessage(NetworkError.NET_ERROR_CUSTOM);
msg.setData(bundle);
handler.sendMessage(msg);
NetworkError.error("" + code, json, bundle);
return;
}
}
} catch (Exception e) {
}
Message msg = handler.obtainMessage(what, response);
msg.setData(bundle);
handler.sendMessage(msg);
}
同時在onVolleyErrorResponse方法中打印出錯誤信息。
LogUtil.e(volleyError.getMessage());
接下來,我們運行一遍代碼,注意:目前我們還沒有將默認的RequestQueue替換為支持HTTPS的,因此該請求應(yīng)該失敗。看下效果:
并且得到如下log:
D/demo (28987): {"code":1,"data":"invaild ip."}
E/demo (28987): javax.net.ssl.SSLHandshakeException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.
同時,在PostMan中訪問該鏈接,也是提示失敗。
接下來,替換RequestQueue,使我們的網(wǎng)絡(luò)請求支持HTTPS安全訪問。只需要將
RequestHandler
類中的:
MyApplication.getRequestQueue().add(request);
替換為:
RequestManager.getInstance(MyApplication.getInstance()).getRequestQueue().add(request);
編譯運行,驗證一下。
成功!
OK,Volley系列到此為止,下一章開始,就用我們現(xiàn)在的成果來學(xué)習(xí)反編譯技術(shù)。同時,這一段的代碼我已經(jīng)放到github上,地址:
https://github.com/joyin5344/VolleyDemo