网络访问组件的思考一(Volley)

前提##

网络访问是一个App的基础功能,也是非常重要的一块;一般我们会使用一些第三方的网络组件,如:volley、okhttp,xutils,并通过一定的封装来实现网络请求;

我们的项目已经快3年了,从最初,简单的封装了一下 xutils的工具类,直到现在,一直在用,虽没出现过问题,但随着一些其他 网络库的 出现,想替换确因耦合度太高,造成无法替换;

耦合度极高

通过上图,可以看到,只要我们想访问网络,我们直接去调用 封装的网络,而不管是在 界面UI,还是Service,或是MVP中的model,这样就造成了。在代码的任何处,都有可能看到网络请求的代码;
如下:
** 在activity中请求网络**

private void initData() {
        NetWorkManager.request(this, NetworkConstant.API_GET_MSG_TYPE, new SimpleRequestCallback<String>(null, false, true) {
            @Override
            public void onStart() {
                super.onStart();
                // UI 上的控件
                mSwipeRefreshLayout.setRefreshing(true);
            }

            @Override
            public void onSuccess(ResponseInfo<String> info) {
                super.onSuccess(info);
                mSwipeRefreshLayout.setRefreshing(false);
                ResponseParser parser = new ResponseParser(info.result, getActivity(), false);
                parser.parse(new ResponseParser.ParseCallback() {

在MVP模式(对应的M中访问)

class DetailSonRepo implements DetailSonContract.Repo {
    @Override
    public void loadData(String processInstanceId, String subCode, String subColumns, final LoadDataCallback<DetailSonModel> callback) {
        Map<String, String> params = new HashMap<>();
        params.put("processInstanceId", processInstanceId);
        params.put("subCode", subCode);
        params.put("subColumns", subColumns);

        NetWorkManager.request(this, NetworkConstant.API___, new SimpleReqCallbackAdapter<>(new AbsReqCallback<DetailSonModel>(DetailSonModel.class) {
            @Override
            protected void onSuccess(DetailSonModel detailSonModel, List<DetailSonModel> tArray, String rawData) {
                super.onSuccess(detailSonModel, tArray, rawData);
                callback.onDataLoaded(detailSonModel);
            }

            @Override
            public void onFailure(String errorMsg, int code) {
                super.onFailure(errorMsg);
                callback.onDataNotAvailable(errorMsg, code);
            }
        }), params);
    }

通过MVP模式,可将网络的请求,在M层搞定;避免了在界面UI层,去做网络请求的事情;

思考##

上面的层级结构,是有缺陷的:

  1. 网络模块耦合度太高,单独分离出来,困难;
  2. 另开一个App,代码无法直接复用,无法移植代码,造成重复开发;

MVP模式

在MVP模式中,是通过M去操作数据的(网络、数据库、缓存)都是在这块完成,但某个时候,迫于业务上开发压力,经常导致开发人员违反设计规范,直接在页面中操作任何数据;毕竟采用MVP模式开发,是会用大量的子类需要创建;

basenet模块
我们思考,通过一个 basenet 的组件去访问网络,这个 组件的目标就是纯网络访问,不涉及到任何业务(如:解析,加密等);一句话:来了请求,我就请求网络;此模块依赖于第三方的网络组件库,如:volley;
basenet通过接口来解耦,通过统一的抽象类 or 接口,对外实现网络请求,客户端,无须关注具体的实现类;

basenet

实践1(老套路,参数通过方法传)##

总体设计图

basenet整体设计图

具体实现代码片段

// 请求接口
public interface IRequest {

    public static final Short GET = 0;
    public static final Short POST = 1;

    /**
     * @param reqType 请求方式
     * @param url     地址
     * @param headers 请求头
     * @param param   请求参数
     */
    void request(final int reqType, final String url, final List<Pair<String, String>> headers, final List<Pair<String, String>> param, final IRequestCallBack callback);

    void request(final int reqType, final String url, final List<Pair<String, String>> headers, final List<Pair<String, String>> param, final IRequestCallBack callback);

    void request(final int reqType, final String url, final List<Pair<String, String>> headers, final List<Pair<String, String>> param, final IRequestCallBack callback, final long timeout);
}

// 回调接口
public interface IRequestCallBack<T> {
    void onSuccess(T t);

    void onFailure(Throwable e);
}

请求抽象类

/**
 * 抽象类
 * Created by zhaoyu1 on 2017/3/6.
 */
public abstract class AbsRequest implements IRequest {

    public static final String CHAR_SET = "UTF-8";

    /**
     * 生成请求的url地址
     *
     * @param url
     * @param params
     * @return
     */
    protected String generateUrl(String url, Map<String, String> params) {
        StringBuilder sb = new StringBuilder(url);
        if (params != null && params.size() > 0) {      // GET 请求,拼接url
            if (sb.charAt(sb.length() - 1) != '?') {            // get 请求 有 ?
                sb.append("?");
            }
            for (Map.Entry<String, String> entry : params.entrySet()) {
                try {
                    sb.append(URLEncoder.encode(entry.getKey(), CHAR_SET)).append("=").append(URLEncoder.encode(entry.getValue(), CHAR_SET)).append("&");
                } catch (UnsupportedEncodingException e) {
                    // NOT_HAPPEND
                }
            }
            sb = sb.deleteCharAt(sb.length() - 1);
        }
        return sb.toString();
    }

Volley Request 类

public class VolleyRequest extends AbsRequest {

    private RequestQueue requestQueue;
    private static VolleyRequest sRequest;

    public static VolleyRequest getInstance() {
        if (sRequest == null) {
            synchronized (VolleyRequest.class) {
                if (sRequest == null) {
                    sRequest = new VolleyRequest();
                    sRequest.requestQueue = Volley.newRequestQueue();
                }
            }
        }
        return sRequest;
    }

    @Override
    public void request(final int reqType, String url, final Map<String, String> headers, final Map<String, String> params, long timeout, final IRequestCallBack callback) {
        StringRequest stringRequest = null;
        int tReqType = Request.Method.GET;
        String tUrl = url;
        switch (reqType) {
            case RequestType.GET:
                tReqType = Request.Method.GET;
                tUrl = generateUrl(url, params);
                break;
            case RequestType.POST:
                tReqType = Request.Method.POST;
                break;
        }

        // 创建请求
        stringRequest = new StringRequest(tReqType, tUrl, new Response.Listener<String>() {
            @Override
            public void onResponse(String response) {
                callback.onSuccess(response);
            }
        }, new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError error) {
                callback.onFailure(error.getCause());
            }
        }) {

            // 设置Header
            @Override
            public Map<String, String> getHeaders() throws AuthFailureError {
                Map<String, String> superHeader = super.getHeaders();
                if (headers != null && headers.size() > 0) {
                    superHeader = headers;
                }
                return superHeader;
            }

            // 设置Body参数
            @Override
            protected Map<String, String> getParams() throws AuthFailureError {
                Map<String, String> tParams = super.getParams();
                if (params != null && params.size() > 0) {
                    tParams = params;
                }
                return tParams;
            }
        };

        // 设置此次请求超时时间
        if (timeout > 1000) {
            stringRequest.setRetryPolicy(new DefaultRetryPolicy((int) timeout, DefaultRetryPolicy.DEFAULT_MAX_RETRIES, DefaultRetryPolicy.DEFAULT_BACKOFF_MULT));
        }
        stringRequest.setTag(url);
        requestQueue.add(stringRequest);
    }

    @Override
    public void request(final int reqType, String url, Map<String, String> headers, Map<String, String> param, IRequestCallBack callback) {
        this.request(reqType, url, headers, param, 0, callback);
    }

    @Override
    public void request(int reqType, String url, Map<String, String> params, IRequestCallBack callback) {
        this.request(reqType, url, null, params, 0, callback);
    }
}

客户端测试调用

    @Test
    public void testVolley() {
        Context appContext = InstrumentationRegistry.getTargetContext();
        getRequest(appContext).request(IRequest.RequestType.GET, "https://www.jd.com", null, new IRequestCallBack<String>() {
            @Override
            public void onSuccess(String o) {
                Log.e("volley", o.toString());
            }

            @Override
            public void onFailure(Throwable e) {
            }
        });
        SystemClock.sleep(500);
    }

实践2(改由builder模式)##

经过上面的一系列步骤,我们完成了一个简单的基于Volley的请求封装,可以实现访问接口了;和同事讨论过后,觉得可用 Builder 建造者模式来重新构建,于是,经常改造,有了第二版,各种网络请求的参数,不再有 方法去传,而改由builder去构造;

修改后的接口

// IRequest 接口
public interface IRequest {

    interface RequestType {
        int GET = 0;
        int POST = 1;
    }

    /**
     * 执行请求,默认是 get 方式
     */
    void request();

    /**
     * 取消网络请求
     */
    void cancel();
}

// AbsRequest抽象类
public abstract class AbsRequest implements IRequest {

    public static final String CHAR_SET = "UTF-8";

    /**
     * url 地址
     */
    protected String mUrl;

    /**
     * 参数
     */
    protected Map<String, String> mParams;

    /**
     * 请求头信息
     */
    protected Map<String, String> mHeader;

    /**
     * 本地请求超时时间
     */
    protected long mTimeOut;

    /**
     * 请求标记
     */
    protected Object mTag;

    /**
     * 回调
     */
    protected IRequestCallBack mCallBack;

    /**
     * 请求方式
     */
    protected int mReqType;

    // 通过builder来构造
    protected AbsRequest(Builder builder) {
        this.mUrl = builder.mUrl;
        this.mCallBack = builder.mCallBack;
        this.mTag = builder.mTag;
        this.mTimeOut = builder.mTimeOut;
        this.mReqType = builder.mReqType;
        this.mParams = builder.mParams;
        this.mHeader = builder.mHeader;
    }

    @Override
    public final void request() {
        switch (mReqType) {
            case RequestType.GET:
                get();
                break;
            case RequestType.POST:
                post();
                break;
        }
    }

    /**
     * 执行get方式
     */
    protected abstract void get();

    /**
     * 执行post方式
     */
    protected abstract void post();


    /**
     * 生成请求的url地址
     *
     * @param url
     * @param params
     * @return
     */
    protected String generateUrl(String url, Map<String, String> params) {
        StringBuilder sb = new StringBuilder(url);
        if (params != null && params.size() > 0) {      // GET 请求,拼接url
            if (sb.charAt(sb.length() - 1) != '?') {            // get 请求 有 ?
                sb.append("?");
            }
            for (Map.Entry<String, String> entry : params.entrySet()) {
                try {
                    sb.append(URLEncoder.encode(entry.getKey(), CHAR_SET)).append("=").append(URLEncoder.encode(entry.getValue(), CHAR_SET)).append("&");
                } catch (UnsupportedEncodingException e) {
                    // NOT_HAPPEND
                }
            }
            sb = sb.deleteCharAt(sb.length() - 1);
        }
        return sb.toString();
    }

    // 抽象的建造者 Builder
    public static abstract class Builder {

        /**
         * url 地址
         */
        private String mUrl;

        /**
         * 参数
         */
        private Map<String, String> mParams;

        /**
         * 请求头信息
         */
        private Map<String, String> mHeader;

        /**
         * 本地请求超时时间
         */
        private long mTimeOut;

        /**
         * 请求标记
         */
        private Object mTag;

        /**
         * 回调
         */
        private IRequestCallBack mCallBack;

        /**
         * 请求方式
         */
        private int mReqType;

        public Builder() {
        }

        public Builder url(String url) {
            this.mUrl = url;
            return this;
        }

        public Builder body(Map<String, String> params) {
            this.mParams = params;
            return this;
        }

        public Builder headders(Map<String, String> headers) {
            this.mHeader = headers;
            return this;
        }

        public Builder timeout(long time) {
            this.mTimeOut = time;
            return this;
        }

        public Builder tag(Object tag) {
            this.mTag = tag;
            return this;
        }

        public Builder callback(IRequestCallBack callBack) {
            this.mCallBack = callBack;
            return this;
        }

        /**
         * @param reqType {@link IRequest.RequestType}中常量
         * @return
         */
        public Builder type(int reqType) {
            this.mReqType = reqType;
            return this;
        }

        public abstract AbsRequest build();
    }
}

接口我们看一下 修改后的Volley Request

public class VolleyRequest extends AbsRequest {

    private static RequestQueue requestQueue;
    private Request mRequest;

    private VolleyRequest(Builder builder) {
        super(builder);
    }

    @Override
    protected void get() {
        realRequest(Request.Method.GET);
    }

    @Override
    protected void post() {
        realRequest(Request.Method.POST);
    }

    private void realRequest(final int reqType) {
        int tReqType = Request.Method.GET;
        String tUrl = mUrl;
        switch (tReqType) {
            case Request.Method.GET:
                tReqType = Request.Method.GET;
                tUrl = generateUrl(mUrl, mParams);
                break;
            case Request.Method.POST:
                tReqType = Request.Method.POST;
                break;
        }

        mRequest = new StringRequest(tReqType, tUrl, new Response.Listener<String>() {
            @Override
            public void onResponse(String response) {
                mCallBack.onSuccess(response);
            }
        }, new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError error) {
                mCallBack.onFailure(error);
            }
        }) {
            @Override
            public Map<String, String> getHeaders() throws AuthFailureError {
                Map<String, String> superHeader = super.getHeaders();
                if (mHeader != null && mHeader.size() > 0) {
                    superHeader = mHeader;
                }
                return superHeader;
            }

            // 设置Body参数
            @Override
            protected Map<String, String> getParams() throws AuthFailureError {
                Map<String, String> tParams = super.getParams();
                if (mParams != null && mParams.size() > 0 && reqType == Request.Method.POST) {
                    tParams = mParams;
                }
                return tParams;
            }
        };

        // 设置此次请求超时时间
        if (mTimeOut > 1000) {
            mRequest.setRetryPolicy(new DefaultRetryPolicy((int) mTimeOut, 0, DefaultRetryPolicy.DEFAULT_BACKOFF_MULT));
        }
        mRequest.setTag(mTag);
        requestQueue.add(mRequest);
    }

    @Override
    public void cancel() {
        if (mRequest != null) {
            mRequest.cancel();
        } else if (mTag != null) {
            requestQueue.cancelAll(mTag);
        }
    }

        // 实现建造者
    public static class Builder extends AbsRequest.Builder {

        private Context mCtx;
                // volley request 为单例
        private VolleyRequest sRequest;

        public Builder(Context ctx) {
            this.mCtx = ctx;
        }

        @Override
        public AbsRequest build() {
            if (sRequest == null) {
                synchronized (VolleyRequest.class) {
                    if (sRequest == null) {
                        sRequest = new VolleyRequest(this);
                        requestQueue = Volley.newRequestQueue(mCtx);
                    }
                }
            }
            return sRequest;
        }
    }
}

客户端的测试代码(简洁的链式编程):

 Context appContext = InstrumentationRegistry.getTargetContext();
 AbsRequest req = new VolleyRequest.Builder(appContext).url("http://www.jd.com")
                .timeout(2000).tag("hello")
                .callback(new IRequestCallBack() {
                    @Override
                    public void onSuccess(Object o) {
                        Log.e("volley", o.toString());
                    }

                    @Override
                    public void onFailure(Throwable e) {
                        Log.e("volley", e.toString());
                    }
                }).build();
        req.request();

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

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,825评论 25 707
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,633评论 18 139
  • Java中的String类可以被继承么? 答:不能,因为它是一个final类,同样的还有Integer,Float...
    gyymz1993阅读 3,970评论 2 104
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,598评论 18 399
  • 今生缘wuxianping阅读 199评论 0 0