Retrofit2的再封装实战—同步与异步请求

前言

首先这篇文章是面向对Retrofit有了解的朋友,如果您对Retrofit并不了解,请自行查阅其用法,本文不会讲解Retrofit的基础用法。
写这篇文章的目的很简单:
1.为了让自己回忆一下(代码半年前就完成了),看是否有改进的地方。
2.如果能帮到有同样需求的朋友,那是再好不过的。
3.如果大家对文章有不同意见之处,本人表示200%的欢迎提议。

这次封装实现的功能:
1.抽离网络层为module,实现即插即用。
2.统一网络层入口,统一实现方法。
3.支持网络请求缓存,自动添加删除缓存,也可以手动cancel请求。

本文是基于Retrofit2.1版本,高于此版本在使用思想方法上应该不会有任何问题。

正题

最终的版本应该是这样的,请忽略download包,这是实现了断点续传和多线程下载的功能

一、初始化

网络层的总入口,在这里进行Retrofit的初始化,配置。包括网络请求的调用方法,当然,他一定是单列的。
很简单,根据你app的需求,自行配置相关方法,相信用过Retrofit的朋友这部分不会陌生,所以这里不做解释。

public NetWorkRequest init(Context context, String baseURL) {    
this.mContext = context;    
synchronized (NetWorkRequest.this) {        
mOkHttpClient = new OkHttpClient.Builder()
.cache(new Cache(new File(context.getExternalCacheDir(), "http_cache"), 1024 * 1024 * 100))                
.readTimeout(15, TimeUnit.SECONDS) 
.writeTimeout(10, TimeUnit.SECONDS) 
.connectTimeout(10, TimeUnit.SECONDS)
.addInterceptor(new CommonInterceptor())
.addInterceptor(new LoggingInterceptor())
.cookieJar(new CookieManager())
.authenticator(new AuthenticatorManager())
.build();       
 mRetrofit = new Retrofit.Builder()
.addConverterFactory(FastJsonConverterFactory.create())
.baseUrl(baseURL)//主机地址                
.client(mOkHttpClient)                
.build();   
 }   
 return this;
}

第三行的synchronized关键字是为了保证初始化线程安全的。Retrofit是利用接口和注释生成网络请求的,这句话你一定不陌生:
ApiService mApiService = mRetrofit.create(ApiService.class);
因为我们现在将网络层抽离为module,是由我们的app项目引用的。所以我们需要在代码中提供create方法:
public <T> T create(Class<T> tClass) { return mRetrofit.create(tClass); }
这样我们的主项目就可以拿到我们的APIService来创建我们的网络请求Call<T>了。思路很清晰,在主项目中执行网络层的初始化方法,并利用create方法获取Api实例,创建接口对应的Call对象。我们在主项目中,创建一个单例类,ApiManager,部分代码如下:

public void init(Context context) {  
  NetWorkRequest.getInstance().init(context, AppConfig.APP_ROOT_URL);    
  mApiService = NetWorkRequest.getInstance().create(ApiService.class);
}
public ApiService getApiService() {   
  return mApiService;
}

在主项目的Application的onCreate方法中初始化ApiManager:
ApiManager.getInstance().init(this);
初始化部分我们就搞定了,是不是很简单。顺便说一下:因为我们的NetWorkRequest是单列的,所以这里的Context一定要是Application的Context,以防内存回收造成的泄漏问题。

二、调用方法封装

先上代码:

public <T extends BaseResponseEntity> void asyncNetWork(final String TAG, final int requestCode, final Call<T> requestCall, final KKNetworkResponse<T> responseListener) { 
1.   Call<T> call;  
      if (requestCall.isExecuted()) {  
       call = requestCall.clone();  
      } else {     
       call = requestCall;  
     }    
2.  addCall(TAG, requestCode, call);   
    call.enqueue(new Callback<T>() {   
     @Override       
    public void onResponse(Call<T> call, Response<T> response) {            
3.    cancelCall(TAG, requestCode);           
      if (response.isSuccessful()) {    
         T result = response.body();  
4.         if (result == null) {
             responseListener.onDataError(requestCode, ""); 
             return;                
         }        
5.       result.requestCode = requestCode;     
         result.serverTip = response.message(); 
         result.responseCode = response.code();
         responseListener.onDataReady(result);  
       } else {  
       responseListener.onDataError(requestCode, NetErrCodeConfig.getErrString(mContext, response.code())); 
       }       
  }     
     @Override     
     public void onFailure(Call<T> call, Throwable t) { 
        cancelCall(TAG, requestCode);
        responseListener.onDataError(requestCode, NetErrCodeConfig.getErrString(mContext, t));
        }  
     });
}

async顾名思义是异步的意思,方法中四个参数逐一解释一下:
TAG:开始说到设计思路要缓存网络请求,你可以把TAG当做不同Activity的网络请求区分字段。

requestCode:上面把TAG比作一个Activity,那requestCode就是同一个页面的不同请求,为了区分并发回调判断是哪个接口。那怎么做缓存呢,思路很简单了,用Map!
private Map<String, Map<Integer, Call>> mRequestMap = new ConcurrentHashMap<>();
我们使用ConcurrentHashMap实现,为了保证数据的安全性。key即是TAG,value是另一个map集合,requestCode即是key,相信看到这里已经很清楚了。结合代码看第2、3标注部分看分别是加入、清除缓存,至于怎么实现,就不贴代码了,都是很基础的东西,对map进行增和删。

requestCall:即是retrofit为我们创建的call对象,对应ApiManager.getInstance().getApiService().xxxx();

KKNetworkResponse<T>:

KKNetworkResponse
可以看到这里使用了泛型,范围是继承BaseResponseEntity的类,解释一下为什么这么做:
我们可能会遇到这样的情况,一个页面同时会并发多个网络请求,每个接口我们都调用asyncNetWork()方法,如果不使用内部类传递KKNetworkResponse对象,我们可能会这么做:
public class WaitWeightIPresenter implements WaitWeightContract.IPresenter, NetworkResponse<PadUserWeighingGetUserDataResponseArgs> {
使用类实现KKNetworkResponse接口,然后重写KKNetworkResponse中的方法,在asyncNetWork()中传入this对象像这样:

public void onDataReady(BaseResponseEntity response) {
    ViewUtils.closeLoadingDialog();
    switch (response.requestCode) {
        case HttpConstants.HTTP_GET_MY_DIET_DETAIL:
            if (mIMyDietView != null){
               mIMyDietView.onGetedMyDietData((ResponseMyDietEntity) response);
            } 
            break;
        case HttpConstants.HTTP_POST_EVERYDAY_DIET_DETAIL:
            if (mIMyDietView != null) {
              mIMyDietView.onSubmitedMealData((ResponseUpLoadDietPlanEntity) response);
            }
            break;
        case HttpConstants.HTTP_GET_EVERYMONTH_DIET_STATUS:  
            if (mIMyDietView != null) {
              mIMyDietView.onGetedDietMonthData((DietCalendarEntity) response);
            }
            break;
    }
}

我们知道Retrofit的response对象是在Api接口中定义好的,接口成功返回后会自动反序列化得到response bean。我们如何在多个response中区别是哪个接口返回的数据呢,你不可能要求后台大哥哥们单独为你在每个返回对象中加个接口参数吧,所以这就需要我们自己去做。代码中的
switch (response.requestCode) {
requestCode是不是很眼熟?这不就是asyncNetWork()中的参数吗?看asyncNetWork中的代码,第5部分,我们自己赋值给response这样回调中就可以通过requestCode来判断是哪个接口了,回调实体BaseResponseEntity直接向下强转T类型,注意这里的向下转型是安全的,response此时已经是子类的引用。
至于你都可以在BaseResponse中存放什么字段?基于你们项目的规范,放一些共有的属性,比如responseCode,serverTip等等。(建议这里跟后台好好商量,合理的公有属性设定可以减少客户端很多的工作)
整体的逻辑大概就是这些了,最后我们来回顾一下到底做了些什么:
1.初始化Retrofit库
2.在主项目中获取retrofit对象,create ApiService
3.调用NetWorkRequest.getInstance().asyncNetWork()方法
4.对回调数据进行处理
网络缓存的缓存和清除都在asyncNetWork()中自动进行处理,当然你也可以在每个Activity的Destory方法中,手动清除当前TAG的所有请求,或者是哪个TAG的哪个request请求,都随你啦(当然这里建议统一在基类里处理)。

最后,如有疑问,请留言,我会认真回复,第一篇文章,多提意见。如果大家感兴趣,我会分享mvp设计模式的个人经验和基于Retrofit的断点续传和多线程下载,稍后会提交代码到git,后会有期。
代码传送门

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

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,398评论 25 707
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,585评论 18 139
  • iOS网络架构讨论梳理整理中。。。 其实如果没有APIManager这一层是没法使用delegate的,毕竟多个单...
    yhtang阅读 5,160评论 1 23
  • 制作软件:PPT\ScreenToGif 2 本文由妈妈桑原创出品,转载请注明 制作分解 第一部分 ★ 圈圈的动效...
    呕心沥血的老妈子阅读 326评论 0 1
  • 上个月末,是大学时参加过的社团「登山远足协会」的十周年,做了一直以来很想做的一件小事情——和社团的老会员们一起筹集...
    Coco吕大方阅读 563评论 0 0