Android 二次封装网络加载框架

写在最前面

开发当中,在请求网络的时候,大家或多或少都会使用一些第三方框架,Android-Async-Http、 Volley、XUtils、Okhttp、Retrofit 等。这些框架减少了我们的很多工作量,同时也对侵入了我们的项目。

大家回顾一下手头上的项目代码,是不是或多或少存在这样那样的历史遗留问题,第三方框架调用混乱,没有封装,或者封装不测底。如果要替换框架,很有可能要对项目大动干戈。


封装的必要性

  • 随着需求的变更或者时间的迁移,某些框架可能已经不能满足我们的需求,我们需要使用新的框架来代替。
  • 对第三方框架进行封装,是为了达到对模块项目的控制,已最小的代价替换框架,达到对项目的控制。

或许你会认为对第三方框架再进行一次封装,没有必要。那是你没有尝试过一行行复制张贴代码,进行替换。

有人可能会想, AS 不是有批量替换的功能吗,干嘛还要封装?

  • 首先,你能保证调用到的地方参数都一模一样吗?
  • 第二,能用更优雅的方式实现,为什么要用最粗暴的方式去替换。

接下来我们先来看一下 okHttp 的使用


Okhttp 的使用

OkHttpClient.Builder mBuilder= new OkHttpClient.Builder().connectTimeout(10, TimeUnit.SECONDS).writeTimeout(10, TimeUnit.SECONDS).readTimeout(30, TimeUnit.SECONDS).addInterceptor(new LoggingInterceptor()).cache(new Cache(mContext.getExternalFilesDir("okhttp"),cacheSize));
client=mBuilder.build();Request.Builder builder = new Request.Builder().url(url).tag(option.mTag);
builder=configHeaders(builder,option);Request build = builder.build();client.newCall(build).enqueue(new Callback() {@Overridepublic void onFailure(Call call, IOException e) {handleError(e, iResponseListener);}@Overridepublic void onResponse(Call call, Response response) throws IOException {handleResult(response, iResponseListener);}
});

如果不进行封装,okHttp 请求网络大概是这样的,想一下,如果我们在项目中都这样使用,要替换框架,那花费的工作量要多大。

不过这种方法,在项目中大多数人不会这样使用,至少都会封装成为一个工具类。封装完成之后如下。

 public static void  doGet(Context context,String url, Map<String, String> paramsMap, NetworkOption networkOption,final IResponseListener iResponseListener){OkHttpClient.Builder mBuilder= new OkHttpClient.Builder().connectTimeout(10, TimeUnit.SECONDS).writeTimeout(10, TimeUnit.SECONDS).readTimeout(30, TimeUnit.SECONDS).addInterceptor(new LoggingInterceptor()).cache(new Cache(context.getExternalFilesDir("okhttp"),cacheSize));OkHttpClient cilent = mBuilder.build();Request.Builder builder = new Request.Builder().url(url);Request build = builder.build();cilent.newCall(build).enqueue(new Callback() {@Overridepublic void onFailure(Call call, IOException e) {handleError(e, iResponseListener);}@Overridepublic void onResponse(Call call, Response response) throws IOException {handleResult(response, iResponseListener);}});}public static void doPost(Context context,String url, Map<String, String> paramsMap, NetworkOption networkOption,final IResponseListener iResponseListener) {OkHttpClient.Builder mBuilder= new OkHttpClient.Builder().connectTimeout(10, TimeUnit.SECONDS).writeTimeout(10, TimeUnit.SECONDS).readTimeout(30, TimeUnit.SECONDS).addInterceptor(new LoggingInterceptor()).cache(new Cache(context.getExternalFilesDir("okhttp"),cacheSize));OkHttpClient cilent = mBuilder.build();url= NetUtils.checkUrl(url);final NetworkOption option=NetUtils.checkNetworkOption(networkOption,url);FormBody.Builder builder = new FormBody.Builder();FormBody formBody = builder.build();Request.Builder requestBuilder = new Request.Builder().url(url).post(formBody).tag(option.mTag);Request request = requestBuilder.build();cilent.newCall(request).enqueue(new Callback() {@Overridepublic void onFailure(Call call, IOException e) {handleError(e,iResponseListener);}@Overridepublic void onResponse(Call call, Response response) throws IOException {handleResult(response,iResponseListener);}});}

这种封装成工具类的比完全没有封装的好了很多,但是还是存在一定的问题的。封装成工具类的话,别人完全有权限访问你这个工具类,他可以随时修改你工具类里面的实现,这给维护带来了一定的成本。那有没有更好的方法呢?

大多数人都会想到的是封装统一网络接口,没错,确实是这样。于是,经过一番思考以后,我们可能写出以下的代码。

   void doGet(Context context,String url, final Map<String, String> paramsMap, final IResponseListener iResponseListener);

如果我们需要动态配置请求头呢,请求 TAG 呢,这时候你会怎么写,继续增加参数吗?

这时候接口可能如下:

public interface NetRequest{void doGet(Context context,String url, final Map<String, String> paramsMap,final Map<String, String> headMap, String tag,final IResponseListener iResponseListener);---}

那以后如果要配置缓存路径呢,配置请求超时时间,读取超时时间呢,直接在方法中增加相应的参数?

这样的做法是不太明智的,会导致接口越来越臃肿。

既然这样,那有没有办法解决呢?

首先,我们先回想一下,网络请求那些参数是必要的,那些是非必要的,即可有可无的。

必要选项

  • url,请求网址
  • paramsMap ,请求参数
  • iResponseListener 请求结果的回调

非必要选项

  • context 通常是用来配置配置一些缓存等一些信息
  • headMap 请求头
  • tag 请求 TAG,用来区分或者取消网络请求
  • connectTimeout 连接超时时间
  • readTimeout 读取超时时间
  • writeTimeout 写入超时时间

了解完必要参数和非必要参数之后,我们的接口要怎样提取呢?

不知道大家有没有注意到 okHttpClient 的构建,他将所有的网络配置都提取封装在 OkHttpClient,Request 中,在请求网络的时候减少了相应的参数,简洁灵活。

       OkHttpClient.Builder mBuilder= new OkHttpClient.Builder().connectTimeout(10, TimeUnit.SECONDS).writeTimeout(10, TimeUnit.SECONDS).readTimeout(30, TimeUnit.SECONDS).addInterceptor(new LoggingInterceptor()).cache(new Cache(context.getExternalFilesDir("okhttp"),cacheSize));OkHttpClient cilent = mBuilder.build();Request.Builder builder = new Request.Builder().url(url);client.newCall(builder.build()).enqueue(new Callback() {@Overridepublic void onFailure(Call call, IOException e) {handleError(e, iResponseListener);}@Overridepublic void onResponse(Call call, Response response) throws IOException {handleResult(response, iResponseListener);}
});

看了 OKhttp 的代码,我们也可以依样画葫芦,我们可以将非必要参数封装在一个实体类 NetworkOption 当中,必要的参数作为方法参数,这样接口变成以下的形式。

void doGet(String url, final Map<String, String> paramsMap, final IResponseListener iResponseListener);void doGet(String url, final Map<String, String> paramsMap, NetworkOption networkOption, final IResponseListener iResponseListener);

对比直接在方法中增加相应的参数,是不是简洁很多。

接着,我们一起来看一下 NetworkOption 的属性。基本上,只要 okhttp 可以配置的,我们都可以往里面配置。这里列举了一些常用的字段 ,baseUrl,请求标志 tag,请求头 mHeaders。-connectTimeout 连接超时时间,readTimeout 读取超时时间,writeTimeout 写入超时时间就不一一列举了。

public class NetworkOption {/*** 网络请求的 TAG*/public String mBaseUrl;public String mTag;public Map<String,String> mHeaders;public NetworkOption(String tag) {this.mTag = tag;}public static final  class Builder{public String tag;public Map<String,String> mHeaders;public String mBaseUrl;public Builder setTag(String tag){this.tag=tag;return this;}public Builder setHeaders(Map<String,String> headers){mHeaders=headers;return this;}public Builder setBaseUrl(String baseUrl) {mBaseUrl = baseUrl;return this;}public NetworkOption build(){NetworkOption networkOption = new NetworkOption(tag);networkOption.mHeaders=mHeaders;networkOption.mBaseUrl=mBaseUrl;return networkOption;}}
}

同时,考虑到 NetworkOption 对象的配置会比较复杂,这里我们采用了建造者模式来构建。有兴趣的话,可以参考我的这一篇博客。建造者模式(Builder)及其应用

建造者模式的优点

  • 封装性很好,将产品本身与产品的创建过程解耦,对外屏蔽了对象的构建过程
  • 扩展性强,如果有新的需求,只需要增加新的具体建造者,无须修改原有类库的代码

最后的封装实现

NetRequest 接口的封装

public interface NetRequest {void init(Context context);void doGet(String url, final Map<String, String> paramsMap, final IResponseListener iResponseListener);void doGet(String url, final Map<String, String> paramsMap, NetworkOption networkOption, final IResponseListener iResponseListener);void doPost(String url, final Map<String, String> paramsMap, final IResponseListener iResponseListener);void doPost(String url, final Map<String, String> paramsMap, NetworkOption networkOption,final IResponseListener iResponseListener);void cancel(Object tag);}

可以看到,我们主要有几个方法

  • init 方法,主要用来配置一些初始化参数
  • doGet 有两个方法,其中一个方法是另外一个方法的重载,这样设计的目的是为了减少调用方法的时候减少方法参数的传递
  • doPost 跟 doGet 方法一样,就不说了
  • cancel 主要是用来取消网络请求的。在项目当中,在 Activity 或者 Fragment 销毁的时候,最好取消网络请求,不然可能导致内存泄露或者异常,如空指针异常等。

OkHttpRequest 的实现

OkHttp 的配置是非常灵活的,这样我们主要看一下怎么配置请求头,请求参数,以及怎样取消网络请求。

public class OKHttpRequest implements NetRequest {//  -----  省略若干方法,有兴趣的话上 github 查阅@Overridepublic void doGet(String url, Map<String, String> paramsMap, final IResponseListener iResponseListener) {doGet(url,paramsMap,null,iResponseListener);}@Overridepublic void doGet(String url, Map<String, String> paramsMap, NetworkOption networkOption,final   IResponseListener iResponseListener) {url= NetUtils.checkUrl(url);url=NetUtils.appendUrl(url,paramsMap);final NetworkOption option=NetUtils.checkNetworkOption(networkOption,url);Request.Builder builder = new Request.Builder().url(url).tag(option.mTag);builder=configHeaders(builder,option);Request build = builder.build();getCilent().newCall(build).enqueue(new Callback() {@Overridepublic void onFailure(Call call, IOException e) {handleError(e, iResponseListener);}@Overridepublic void onResponse(Call call, Response response) throws IOException {handleResult(response, iResponseListener);}});}private Request.Builder configHeaders(Request.Builder builder, NetworkOption option) {Map<String, String> headers = option.mHeaders;if(headers==null || headers.size()==0){return builder;}Set<Map.Entry<String, String>> entries = headers.entrySet();for(Map.Entry<String, String> entry: entries){String key = entry.getKey();String value = entry.getValue();// 添加请求头builder.addHeader(key,value);}return builder;}@Overridepublic void doPost(String url, Map<String, String> paramsMap, final IResponseListener iResponseListener) {doPost(url,paramsMap,null,iResponseListener);}private FormBody.Builder configPushParam(FormBody.Builder builder, Map<String, String> paramsMap) {if(paramsMap!=null){Set<Map.Entry<String, String>> entries = paramsMap.entrySet();for(Map.Entry<String,String> entry:entries ){String key = entry.getKey();String value = entry.getValue();builder.add(key,value);}}return builder;}@Overridepublic void doPost(String url, Map<String, String> paramsMap, NetworkOption networkOption,final IResponseListener iResponseListener) {url= NetUtils.checkUrl(url);final NetworkOption option=NetUtils.checkNetworkOption(networkOption,url);// 以表单的形式提交FormBody.Builder builder = new FormBody.Builder();builder=configPushParam(builder,paramsMap);FormBody formBody = builder.build();Request.Builder requestBuilder = new Request.Builder().url(url).post(formBody).tag(option.mTag);requestBuilder=configHeaders(requestBuilder,option);Request request = requestBuilder.build();getCilent().newCall(request).enqueue(new Callback() {@Overridepublic void onFailure(Call call, IOException e) {handleError(e,iResponseListener);}@Overridepublic void onResponse(Call call, Response response) throws IOException {handleResult(response,iResponseListener);}});}@Overridepublic void cancel(Object tag) {if(client!=null){if(client != null) {// 在等待队列中查找是否有相应的请求for(Call call : client.dispatcher().queuedCalls()) {if(call.request().tag().equals(tag))call.cancel();}// 在正在请求的请求队列中查找是否有相应的请求for(Call call : client.dispatcher().runningCalls()) {if(call.request().tag().equals(tag))call.cancel();}}}}}

OKHttpRequest 的实现其实很就简单,主要是根据 NetworkOption 做相应的配置,不熟悉 okhttpRequest 的用法的可以参考该博客。OkHttp使用完全教程

VolleyRequest

VolleyRequest 的实现也不说了,也是根据 NetworkOption 做相应的配置,有兴趣的话可以点击查看 Networklibrary

NetworkManger

考虑到项目当中有可能要切换框架,这里我们使用简单工厂模式来实现,方便我们框架的随时切换。

UMl 类图如下

  • NetRequest 统一的网络接口
  • VolleyRequest ,Volley 请求网络的具体实现
  • OkhttpRequest,Okhttp 请求网络的实现
  • NetManger ,根据参数的不同返回不同的网络实现

最后考虑到网络加载在项目中是经常用到的,为了节省资源,提高速度,我们结合了单例模式,最终的实现如下:

public class NetManger {private static NetRequest instance;private static Context mContext;public static NetRequest getRequest(){return instance;}static HashMap<String,NetRequest> mMap=new HashMap<>();public static void  init(Context context){instance = OKHttpRequest.getInstance();mContext = context.getApplicationContext();instance.init(NetManger.mContext);}// 采用反射的形式实现,这样有一个好处是,以后增加新的实现类的话,我们只需要传递相应 的 Class,
//而不需要更改 NetManger 的代码public static <T extends NetRequest> NetRequest getRequest(Class<T> clz){String simpleName = clz.getSimpleName();NetRequest request = mMap.get(simpleName);if(request ==null){try {Constructor<T> constructor = clz.getDeclaredConstructor();constructor.setAccessible(true);request = constructor.newInstance();request.init(mContext);mMap.put(simpleName,request);} catch (InstantiationException e) {e.printStackTrace();} catch (IllegalAccessException e) {e.printStackTrace();} catch (NoSuchMethodException e) {e.printStackTrace();} catch (InvocationTargetException e) {e.printStackTrace();}}instance=request;return request;}}

你问我答

1) NetManger 怎么使用

首先你需要在 Application 中调用 NetManger 的 init 方法

NetManger.init(application);

默认的实现是使用 okhttp 实现,采用单例模式

NetManger.getRequest().doGet(url, mMap, new IResponseListener() {@Overridepublic void onResponse(String response) {LogUtil.i(TAG,"onResponse:  response ="+response);}@Overridepublic void onFail(HttpException httpException) {Log.i(TAG, "onFail: httpException=" +httpException.toString());}
});

2) NetManger 怎么切换具体的实现

加入我们想切换成 Volley,那么我们在传递参数的时候只需要传递VolleyRequest.class 即可

NetManger.getRequest(VolleyRequest.class).doPost(url, mMap, new IResponseListener() {@Overridepublic void onResponse(String response) {mTv.setText("post 请求\n"+response);LogUtil.i(TAG,"onResponse:  response ="+response);}@Overridepublic void onFail(HttpException httpException) {Log.i(TAG, "onFail: httpException=" +httpException.toString());}
});
  1. 如果我们不想使用 okhttp,Volley,而是想使用 XUtils或者 retrofit,有没有办法实现呢?

答案是坑定的,我们只需要自己增加一个实现类 implement NetRequest 接口即可。然后在使用传递参数的时候传递相应的 Class 即可。

NetManger.getRequest(XUtilsRequest.class).doPost(url, mMap, new IResponseListener() {@Overridepublic void onResponse(String response) {mTv.setText("post 请求\n"+response);LogUtil.i(TAG,"onResponse:  response ="+response);}@Overridepublic void onFail(HttpException httpException) {Log.i(TAG, "onFail: httpException=" +httpException.toString());}
})

题外话

看了上面网络框架的二次封装,对图片框架,json 解析框架的封装,你是不是也想到了什么,懂得怎样封装了吧。


Networklibrary:https://github.com/gdutxiaoxu/Networklibrary


相关推荐

观察者设计模式 Vs 事件委托(java)

装饰者模式及其应用

建造者模式(Builder)及其应用

二次封装图片第三方框架——简单工厂模式的运用

Android 二次封装网络加载框架

java 代理模式详解


最后的最后,卖一下广告,欢迎大家关注我的微信公众号,扫一扫下方二维码或搜索微信号 stormjun,即可关注。 目前专注于 Android 开发,主要分享 Android开发相关知识和一些相关的优秀文章,包括个人总结,职场经验等。

Android 二次封装网络加载框架相关推荐

  1. android 自定义域名,Android基于Retrofit2改造的可设置多域名的网络加载框架

    Android基于Retrofit2改造的可设置多域名的网络加载框架 1.使用说明 添加仓库 ``` allprojects { repositories { google() jcenter() m ...

  2. 网络加载框架 - Retrofit

    Retrofit是什么? Retrofit其实我们可以理解为OkHttp的加强版,它也是一个网络加载框架.底层是使用OKHttp封装的.准确来说,网络请求的工作本质上是OkHttp完成,而 Retro ...

  3. android快捷开发之Retrofit网络加载框架的简单使用

    大家都知道,安卓最大的特点就是开源化,这自然会产生很多十分好用的第三方API,而基本每一个APP都会与网络操作和缓存处理机制打交道,当然,你可以自己通过HttpUrlConnection再通过返回数据 ...

  4. Android网络加载框架Glide的使用

    前言 Glide是Google推荐的用于Android平台的图片加载框架,Glide和Picasso有90%的相似度,可以说Glide是Picasso的克隆版本,只是在细节上存在不少区别.还不是很了解 ...

  5. (三) 技术选型 1.项目框架模式:MVP(得分点);注意:分包分层,避免内存泄漏; 2.图片加载:Fresco图片加载框架; 3.网络加载框架:retrofit;使用Retrofit+RxJ

    首先呢    先把业务逻辑需求讲一下  大致思路都是根据自己所想的去做,下面是我个人做的一个小model,希望能帮到有需要的人 业务逻辑需求 1.MVP分包分层:Model.View.Presente ...

  6. Android中常见的图片加载框架

    图片加载涉及到图片的缓存.图片的处理.图片的显示等.而随着市面上手机设备的硬件水平飞速发展,对图片的显示要求越来越高,稍微处理不好就会造成内存溢出等问题.很多软件厂家的通用做法就是借用第三方的框架进行 ...

  7. Android一行代码实现网络加载GIF闪图(附源码)

    最近项目有个需求是要从网络加载GIF闪图, 但是Android原生的ImageView并不支持Gif... 于是从网上看了些Dome, 发现总是有些这样那样的问题, 譬如: ☹ 没有缓存,还要自己写一 ...

  8. Android实现异步从网络加载图片列表

     博文出处:http://blog.csdn.net/carterjin/article/details/7995935 有时会有在加载ListView的时候,包含用户头像或其他需要到网络获取的图 ...

  9. Android Glide图片加载框架(二)源码解析之into()

    文章目录 一.前言 二.源码解析 1.into(ImageView) 2.GlideContext.buildImageViewTarget() 3.RequestBuilder.into(Targe ...

最新文章

  1. 中国算力发展指数白皮书(2021)
  2. django入门与实践 3-1 环境搭建
  3. 2018-05-31 第二十五天
  4. Java 9:欢迎来到Module World
  5. java发送c语言结构体_C语言中结构体直接赋值?
  6. Git 原理详解及实用指南
  7. c++排序算法之 快速排序
  8. 操作系统编写之代码解释
  9. Mysql 计划任务
  10. ssh 免密配置、修改hadoop配置文件
  11. IP报文格式及各字段的意义
  12. 关于AutoCAD软件的PDF打印机的问题
  13. win10安装完ubuntu后win10时间改变了 最简单调整方式
  14. 台式计算机怎么安装无线网卡,台式机用无线网卡,小编教你台式机怎么用无线网卡...
  15. 如何一次将蓝牙耳机连接到多个设备
  16. bing壁纸获取(Java)
  17. 语料标注平台BRAT安装指导
  18. 【图像去噪】基于matlab小波滤波(硬阙值+软阙值)+中值滤波图像去噪【含Matlab源码 462期】
  19. IDEA代理配置(插件无法下载)
  20. java识别图片中文字技术

热门文章

  1. HanLP-停用词表的使用示例
  2. 从零开始vue(下)
  3. Linux的libc库
  4. java实习生/应届毕业生面试大纲
  5. PE文件-手工修改重定位表-WinHex-CFF Explorer
  6. oracle字段加约束,添加/删除约束(Oracle)
  7. #2019江苏领航杯部分wp
  8. 安装破解Navicat
  9. 04742通信概论考点汇总
  10. Docker环境部署PHP后端项目集群