Android網(wǎng)絡(luò)--我是怎么做的: Volley+OkHttp+Https
使用 OkHttp 作為傳輸層的實(shí)現(xiàn).
Volley 默認(rèn)根據(jù) Android 系統(tǒng)版本使用不同的 Http 傳輸協(xié)議實(shí)現(xiàn).
在 Android 3.0 以上 Volley 使用 ApacheHttpStack 作為傳輸協(xié)議, 在2.3 及以下使用 HttpURLConnection 作為傳輸層協(xié)議
OkHttp 相較于其它的實(shí)現(xiàn)有以下的優(yōu)點(diǎn).
- 支持SPDY,允許連接同一主機(jī)的所有請(qǐng)求分享一個(gè)socket。
- 如果SPDY不可用,會(huì)使用連接池減少請(qǐng)求延遲。
- 使用GZIP壓縮下載內(nèi)容,且壓縮操作對(duì)用戶是透明的。
- 利用響應(yīng)緩存來避免重復(fù)的網(wǎng)絡(luò)請(qǐng)求。
- 當(dāng)網(wǎng)絡(luò)出現(xiàn)問題的時(shí)候,OKHttp會(huì)依然有效,它將從常見的連接問題當(dāng)中恢復(fù)。
- 如果你的服務(wù)端有多個(gè)IP地址,當(dāng)?shù)谝粋€(gè)地址連接失敗時(shí),OKHttp會(huì)嘗試連接其他的地址,這對(duì)IPV4和IPV6以及寄宿在多個(gè)數(shù)據(jù)中心的服務(wù)而言,是非常有必要的。
因此使用 OkHttp 作為替代是好的選擇.
-
首
先用 OkHttp 實(shí)現(xiàn)一個(gè)新的 HurlStack 用于構(gòu)建 Volley 的 requestQueue.- public class OkHttpStack extends HurlStack {
- private OkHttpClient okHttpClient;
- /**
- * Create a OkHttpStack with default OkHttpClient.
- */
- public OkHttpStack() {
- this(new OkHttpClient());
- }
- /**
- * Create a OkHttpStack with a custom OkHttpClient
- * @param okHttpClient Custom OkHttpClient, NonNull
- */
- public OkHttpStack(OkHttpClient okHttpClient) {
- this.okHttpClient = okHttpClient;
- }
- @Override
- protected HttpURLConnection createConnection(URL url) throws IOException {
- OkUrlFactory okUrlFactory = new OkUrlFactory(okHttpClient);
- return okUrlFactory.open(url);
- }
- }
-
然后使用 OkHttpStack 創(chuàng)建新的 Volley requestQueue.
- requestQueue = Volley.newRequestQueue(getContext(), new OkHttpStack());
- requestQueue.start();
這樣就行了.
使用 Https
作為一個(gè)有節(jié)操的開發(fā)者應(yīng)該使用 Https 來保護(hù)用戶的數(shù)據(jù), Android 開發(fā)者網(wǎng)站上文章Security with HTTPS and SSL做了詳盡的闡述.
OkHttp 自身是支持 Https 的. 參考文檔 OkHttp Https, 直接使用上面的 OkHttpStack就可以了, 但是如果遇到服務(wù)器開發(fā)哥哥使用了自簽名的證書(不要問我為什么要用自簽名的), 就無法正常訪問了.
網(wǎng)上有很多文章給出的方案是提供一個(gè)什么事情都不做的TrustManager 跳過 SSL 的驗(yàn)證, 這樣做很容受到攻擊, Https 也就形同虛設(shè)了.
我采用的方案是將自簽名的證書打包入 APK 加入信任.
好處:
- 應(yīng)用難以逆向, 應(yīng)用不再依賴系統(tǒng)的 trust store, 使得 Charles 抓包等工具失效. 要分析應(yīng)用 API 必須反編譯 APK.
- 不用額外購買證書, 省錢....
缺點(diǎn):
- 證書部署靈活性降低, 一旦變更證書必須升級(jí)程序.
實(shí)現(xiàn)步驟
以最著名的自簽名網(wǎng)站12306為例說明
-
導(dǎo)出證書
- echo | openssl s_client -connect kyfw.12306.cn:443 2>&1 | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' > kyfw.12306.cn.pem
-
將證書轉(zhuǎn)為 bks 格式
下載最新的bcprov-jdk, 執(zhí)行下面的命令. storepass 是導(dǎo)出密鑰文件的密碼.- keytool -importcert -v \
- -trustcacerts \
- -alias 0 \
- -file <(openssl x509 -in kyfw.12306.cn.pem) \
- -keystore $CERTSTORE -storetype BKS \
- -providerclass org.bouncycastle.jce.provider.BouncyCastleProvider \
- -providerpath ./bcprov-jdk16-1.46.jar \
- -storepass asdfqaz
-
將導(dǎo)出的 kyfw.bks 文件放入 res/raw 文件夾下.
-
創(chuàng)建 SelfSignSslOkHttpStack
- /**
- * A HttpStack implement witch can verify specified self-signed certification.
- */
- public class SelfSignSslOkHttpStack extends HurlStack {
- private OkHttpClient okHttpClient;
- private Map<String, SSLSocketFactory> socketFactoryMap;
- /**
- * Create a OkHttpStack with default OkHttpClient.
- */
- public SelfSignSslOkHttpStack(Map<String, SSLSocketFactory> factoryMap) {
- this(new OkHttpClient(), factoryMap);
- }
- /**
- * Create a OkHttpStack with a custom OkHttpClient
- * @param okHttpClient Custom OkHttpClient, NonNull
- */
- 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 {
- return new OkUrlFactory(okHttpClient).open(url);
- }
- }
- }
-
然后用 SelfSignSslOkHttpStack 創(chuàng)建 Volley 的 RequestQueue.
- String[] hosts = {"kyfw.12306.cn"};
- int[] certRes = {R.raw.kyfw};
- String[] certPass = {"asdfqaz"};
- 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);
- requestQueue = Volley.newRequestQueue(context, stack);
- requestQueue.start();
-
我們來試一試, 用上一步穿件的 RequestQueue 替換掉原來的, 然后發(fā)請(qǐng)求試試.
- StringRequest request = new StringRequest(
- Request.Method.GET,
- "https://kyfw.12306.cn/otn/",
- new Response.Listener<String>() {
- @Override
- public void onResponse(String response) {
- responseContentTextView.setText(response);
- }
- },
- new Response.ErrorListener() {
- @Override
- public void onErrorResponse(VolleyError error) {
- responseContentTextView.setText(error.toString());
- }
- });
- RequestManager.getInstance(this).addRequest(request, this);
-
done