Android笔记 (6): 封装Volley实现自动化网络处理(下)

上一章我们完成了Handler优化,统一错误处理,和加载框的实现思路。
现在我们要进行的是让之前的框架支持HTTPS安全访问,同时利用之前的对话框样式,来创建消息提示框。本章结束后,我会将这次的代码放到github上,下一章开始就用这个应用来研究反编译

消息提示框

新建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>

有没有发现所有textSize单位我用的都是dp?虽然官方推荐文字大小单位用sp,但是由于sp与dp的比值会随系统设置里的字体大小变化而变化,届时可能有布局上影响,因此我还是习惯性用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;
    }
}

显示消息对话框的时候,只需要创建对象,设置内容,然后show()就可以了,同时支持对点击事件自行处理。
现在我们来尝试一下,在MainActivity中网络请求返回结果错误的地方添加三行代码:

MessageDialog dialog = new MessageDialog(MainActivity.this);
dialog.setMessage("获取请求失败了");
dialog.show();

编译运行

效果

同理,要做有标题,有内容,下方有确定和取消的对话框,也可以用一样的方法实现。

在现在的基础上做Https模块,当我们点击“我知道了”,就跳到下一个页面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中设置对话框的点击事件:

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,现在已经能进入下一个页面了,接下来我们来实现Https。

支持HTTPS

本文HTTPS相关技术点来自《Android 网络--我是怎么做的: Volley+OkHttp+Https》一文。
我当初是看了这篇文章后,以同样的方法实现的,非常感谢这篇文章原作者,原文以12306作为案例,为了尊重原作者,同时也因为不便于用商业项目做案例,所以我也同样用12306来演示,大部分代码来自原文。

另,我将证书转换为bks文件,用《Android 网络--我是怎么做的: Volley+OkHttp+Https》中的方法并未成功,而是参照在Android应用中使用自定义证书的HTTPS连接(下)一文,导出cer格式证书,然后转换成功。

我们直接进入《Android 网络--我是怎么做的: Volley+OkHttp+Https》文中,根据该作者的github,找到他已经存在的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,返回的数据格式不是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的,因此该请求应该失败。看下效果:

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,使我们的网络请求支持HTTPS安全访问。只需要将RequestHandler类中的:

MyApplication.getRequestQueue().add(request);

替换为:

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

编译运行,验证一下。

支持HTTPS的访问结果

成功!
OK,Volley系列到此为止,下一章开始,就用我们现在的成果来学习反编译技术。同时,这一段的代码我已经放到github上,地址:

https://github.com/joyin5344/VolleyDemo
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,175评论 5 466
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,674评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,151评论 0 328
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,597评论 1 269
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,505评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 47,969评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,455评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,118评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,227评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,213评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,214评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,928评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,512评论 3 302
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,616评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,848评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,228评论 2 344
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,772评论 2 339

推荐阅读更多精彩内容