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

回顾

前两章我们用Vollery+FastJson很方便的获得了数据,Java8的Lambda表达式可以将initData方法的代码显示得如下图般精简。

Lambda精简后的代码块

Java8 Lambda表达式教程

Charles抓包数据解析

添加Header

真实的项目中,有时会遇到加上token,统计信息等请求头,那么怎么办呢,看下代码。

重写getHeaders

可以看到我们仅仅是重写了Request的getHeaders()方法。

@Override
public Map<String, String> getHeaders() throws AuthFailureError {
    Map<String, String> map = new HashMap<>();
    map.put("versionName", BuildConfig.VERSION_NAME);
    return map;
}

向请求添加了一个Header参数,参数名称为versionName,参数值是当前应用的版本名称。

header已经成功添加上了

缺点

但是个人认为还不够完美,如果你对Volley比较熟悉的话,就能发现每一次请求都要创建一个Request,然后将其add到RequestQueue,如果项目中请求非常多,那么将会看到遍地都是onResponse,onError。而且如果项目进行到一半,突然说每个请求头都要加上某字段(如果有值)。那将是一件多么痛苦的事情。
接下来分析我是如何将Volley封装为更适合该类项目需求的。

修改分析

接下来要做的,应该是将重复的代码整合到一起,整个网络流程可以归纳为三句话:

  • 我想要做什么(url)
  • 我能提供什么(headers, params)
  • 结果返回后要做什么

这里要说一下为什么我用Volley,为什么是使用StringRequest而不是JsonObjectRequest,最大的原因就是为了接下来的封装。而请求结果返回后,应当将结果返回给请求发起的地方。接下来看代码。
先在com.joyin.volleydemo.utils.network包下新建RequestHandler类,里面创建addRequestByGet方法:

/**
 * @param method  Request.Method.GET 或 Request.Method.POST
 * @param handler 请求结束后将结果作为Message.obj发送到该Handler
 * @param what    请求结束后发送的Message.what
 * @param bundle  不参与网络请求,仅携带参数
 *                (请求结束后,通过Message.setData设置到Message对象,数据原样返回)
 * @param url     请求地址
 * @param params  请求参数
 * @param header  请求头
 */
public static void addRequest(
        final int method, final Handler handler, final int what,
        final Bundle bundle, final String url,
        final Map<String, String> params,
        final Map<String, String> header) {

}

其中bundle对象在请求结束后,可通过Message.getData()可得到,并不参与网络流程。合理利用该参数,可以将多种操作串起来。

接下来首先新建com.joyin.volleydemo.utils.network.NetworkHelper类,并且提供三个方法,完整代码如下:

package com.joyin.volleydemo.utils.network;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Created by joyin on 16-4-2.
 */
public class NetworkHelper {

    /**
     * 将参数转换为字符串
     */
    public static String convertMapToString(Map<String, String> params) {
        StringBuffer content = new StringBuffer();
        List<String> keys = new ArrayList<>(params.keySet());
        for (int i = 0; i < keys.size(); i++) {
            String key = keys.get(i);
            String value = params.get(key);
            if (value != null) {
                content.append((i == 0 ? "" : "&") + key + "=" + value);
            } else {
                content.append((i == 0 ? "" : "&") + key + "=");
            }
        }
        return content.toString();
    }

    /**
     * 講參數进行URLEncode编码转换
     * @param params
     * @return
     */
    public static Map<String, String> getURLEncodeParams(Map<String, String> params) {
        Map<String, String> map = new HashMap<String, String>();
        for (String key : params.keySet()) {
            String value = params.get(key);
            try {
                value = URLEncoder.encode(value, "UTF-8");
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
            map.put(key, value);
        }
        return map;
    }

    /**
     * 拼接url和参数,用于Get请求
     * @param url
     * @param params
     * @return
     */
    public static String getUrlWithParams(String url, Map<String, String> params) {
        if (params == null || params.isEmpty()) {
            return url;
        }
        params = getURLEncodeParams(params);
        String paramsStr = convertMapToString(params);
        if (!url.endsWith("?")) {
            url += "?";
        }
        url += paramsStr;
        return url;
    }
}

代码比较简单,在Get请求的时候,调用getUrlWithParams方法,将url和params一起传入,即可自动生成拼接好参数的url。

新建com.joyin.volleydemo.app.MyApplication类,完整代码:

package com.joyin.volleydemo.app;

import android.app.Application;

import com.android.volley.RequestQueue;
import com.android.volley.toolbox.Volley;

/**
 * Created by joyin on 16-4-3.
 */
public class MyApplication extends Application {

    private RequestQueue mRequestQueue;

    private static MyApplication mInstance;

    @Override
    public void onCreate() {
        super.onCreate();
        mInstance = this;
        mRequestQueue = Volley.newRequestQueue(this);
    }

    public static MyApplication getInstance() {
        return mInstance;
    }

    public static RequestQueue getRequestQueue() {
        return mInstance.mRequestQueue;
    }
}

修改AndroidManifest.xml

修改为自定义Application

某些条件下,网络请求是需要显示loading动画的,在这里也考虑到了,现在用默认的ProgressDialog,先把逻辑实现了,最后再实现自定义的loading框。

再来看整理好功能的RequestHandler.java

package com.joyin.volleydemo.utils.network;

import android.app.ProgressDialog;
import android.content.Context;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;


import com.android.volley.AuthFailureError;
import com.android.volley.Request;
import com.android.volley.Response;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.StringRequest;
import com.joyin.volleydemo.BuildConfig;
import com.joyin.volleydemo.app.MyApplication;

import java.util.HashMap;
import java.util.Map;

/**
 * Created by joyin on 16-4-2.
 */
public class RequestHandler {

    public static final int NET_ERROR_VOLLEY = -2;

    private static void addRequest(
            int method,
            final Handler handler, final int what,
            final Bundle bundle, String url, final Map<String, String> params, final Map<String, String> header,
            final NetWorkRequestListener listener) {
        if (method == Request.Method.GET) {
            url = NetworkHelper.getUrlWithParams(url, params);
        }
        listener.onPreRequest();
        StringRequest request = new StringRequest(method, url, new Response.Listener<String>() {

            @Override
            public void onResponse(String response) {
                onVolleyResponse(response, handler, what, bundle);
                listener.onResponse();
            }
        }, new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError volleyError) {
                onVolleyErrorResponse(volleyError, listener, handler, bundle);
            }
        }) {
            @Override
            public Map<String, String> getHeaders() throws AuthFailureError {
                Map<String, String> map = header;
                if (map == null) {
                    map = new HashMap<>();
                }
                // 在此统一添加header
                map.put("versionName", BuildConfig.VERSION_NAME);
                return map;
            }

            /**
             * Volley仅在post的情况下会回调该方法,获取form表单参数
             * @return
             * @throws AuthFailureError
             */
            @Override
            protected Map<String, String> getParams() throws AuthFailureError {
                return params;
            }
        };

        MyApplication.getRequestQueue().add(request);
    }

    private static void onVolleyErrorResponse(VolleyError volleyError, NetWorkRequestListener listener, Handler handler, Bundle bundle) {
        if (listener.retry()) {
            listener.onFailed();
            return;
        }
        Message msg = handler.obtainMessage(NET_ERROR_VOLLEY);
        msg.setData(bundle);
        handler.sendMessage(msg);
        listener.onFailed();
    }

    private static void onVolleyResponse(String response, Handler handler, int what, Bundle bundle) {
        Message msg = handler.obtainMessage(what, response);
        msg.setData(bundle);
        handler.sendMessage(msg);
    }

    /**
     * @param method  Request.Method.GET 或 Request.Method.POST
     * @param handler 请求结束后将结果作为Message.obj发送到该Handler
     * @param what    请求结束后发送的Message.what
     * @param bundle  不参与网络请求,仅携带参数
     *                (请求结束后,通过Message.setData设置到Message对象,数据原样返回)
     * @param url     请求地址
     * @param params  请求参数
     * @param header  请求头
     */
    public static void addRequest(
            final int method, final Handler handler, final int what, final Bundle bundle,
            final String url, final Map<String, String> params, final Map<String, String> header) {
        addRequest(method, handler, what, bundle, url, params, header, new DefaultRequestListener() {
            @Override
            public boolean retry() {
                addRequest(method, handler, what, bundle, url, params, header,
                        retryTimer++ >= MAX_RETRY_TIME ? new DefaultRequestListener() : this);
                return true;
            }
        });
    }

    public static void addRequestWithDialog(
            final int method, Context context, final Handler handler, final int what, final Bundle bundle,
            final String url, final Map<String, String> params, final Map<String, String> header) {
        addRequest(method, handler, what, bundle, url, params, header, new DefaultDialogRequestListener(context) {
            @Override
            public boolean retry() {
                addRequest(method, handler, what, bundle, url, params, header,
                        retryTimer++ >= MAX_RETRY_TIME ? new DefaultDialogRequestListener(context) : this);
                return true;
            }
        });
    }


    /**
     * 请求过程中显示加载对话框,且自动处理其生命周期
     */
    private static class DefaultDialogRequestListener extends DefaultRequestListener {

        Context context;
        ProgressDialog dialog;

        public DefaultDialogRequestListener(Context context) {
            this.context = context;
            dialog = new ProgressDialog(context);
        }

        @Override
        public void onPreRequest() {
            dialog.show();
        }

        @Override
        public void onResponse() {
            dialog.dismiss();
        }

        @Override
        public void onFailed() {
            dialog.dismiss();
        }
    }

    private static class DefaultRequestListener implements NetWorkRequestListener {

        int retryTimer;

        static final int MAX_RETRY_TIME = 3;

        @Override
        public void onPreRequest() {

        }

        @Override
        public void onResponse() {

        }

        @Override
        public void onFailed() {

        }

        @Override
        public boolean retry() {
            return false;
        }
    }

    /**
     * 用于所有网络请求,在不同时机回调的接口
     */
    private static interface NetWorkRequestListener {
        void onPreRequest();

        void onResponse();

        void onFailed();

        boolean retry();
    }
}

这个类里面的代码有点多,但是都不难,想必熟悉Android的人都可以很好的理解。

onVolleyResponse里可以对数据做处理,然后再分发下去,比如服务端api所有返回结果都是以{"code":1,"message":{}}格式,根据code判断各项认证是否成功等,都可以在这拦截处理。这点下一章会讲到。

现在看修改代码后的MainActivity

package com.joyin.volleydemo.activity;

import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.widget.TextView;

import com.alibaba.fastjson.JSON;
import com.android.volley.Request;
import com.joyin.volleydemo.R;
import com.joyin.volleydemo.data.api.IpInfo;
import com.joyin.volleydemo.utils.network.RequestHandler;

import java.util.HashMap;
import java.util.Map;

public class MainActivity extends AppCompatActivity {

    TextView mTvCountry, mTvCountryId, mTvIP;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initViews();
        initData();
    }

    private void initViews() {
        mTvCountry = (TextView) findViewById(R.id.tv_country);
        mTvCountryId = (TextView) findViewById(R.id.tv_country_id);
        mTvIP = (TextView) findViewById(R.id.tv_ip);
    }

    private void initData() {
        String url = "http://ip.taobao.com/service/getIpInfo.php";
        Map<String, String> params = new HashMap<>();
        params.put("ip", "21.22.11.33");
        RequestHandler.addRequest(Request.Method.GET, mHandler, RESULT_GET_IP_INFO, null, url, params, null);
    }

    private void setIpInfoToView(IpInfo ipInfo) {
        mTvCountry.setText(ipInfo.getData().getCountry());
        mTvCountryId.setText(ipInfo.getData().getCountry_id());
        mTvIP.setText(ipInfo.getData().getIp());
    }

    private static final int RESULT_GET_IP_INFO = 101;
    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case RESULT_GET_IP_INFO:
                    String result = (String) msg.obj;
                    Log.d("demo", result);
                    IpInfo ipInfo = JSON.parseObject(result, IpInfo.class);
                    setIpInfoToView(ipInfo);
                    break;
            }
        }
    };
}

看initData方法里,网络请求是不是很容易。
将initData里最后一行

RequestHandler.addRequest(Request.Method.GET, mHandler, RESULT_GET_IP_INFO, null, url, params, null);

修改为:

RequestHandler.addRequestWithDialog(Request.Method.GET, MainActivity.this, mHandler, RESULT_GET_IP_INFO, null, url, params, null);

就会在请求过程中显示loading框了。

目前我们的网络请求分工已经很明确,后续章节将会在现在的基础上进行优化,包括Handler的优化,网络请求错误码统一处理,loading对话框的定制等。

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

推荐阅读更多精彩内容