Android筆記 (6): 封裝Volley實現(xiàn)自動化網(wǎng)絡(luò)處理(下)

上一章我們完成了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)該失敗。看下效果:

HTTPS訪問失敗

并且得到如下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中訪問該鏈接,也是提示失敗。

在PostMan中訪問該鏈接效果

接下來,替換RequestQueue,使我們的網(wǎng)絡(luò)請求支持HTTPS安全訪問。只需要將RequestHandler類中的:

MyApplication.getRequestQueue().add(request);

替換為:

RequestManager.getInstance(MyApplication.getInstance()).getRequestQueue().add(request);

編譯運行,驗證一下。

支持HTTPS的訪問結(jié)果

成功!
OK,Volley系列到此為止,下一章開始,就用我們現(xiàn)在的成果來學(xué)習(xí)反編譯技術(shù)。同時,這一段的代碼我已經(jīng)放到github上,地址:

https://github.com/joyin5344/VolleyDemo
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,501評論 6 544
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,673評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,610評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,939評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 72,668評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 56,004評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,001評論 3 449
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 43,173評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,705評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 41,426評論 3 359
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,656評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,139評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,833評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,247評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,580評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,371評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 48,621評論 2 380

推薦閱讀更多精彩內(nèi)容