美文网首页NetRx系列Android开发
Android基于Retrofit2.0+RxJava 封装的超

Android基于Retrofit2.0+RxJava 封装的超

作者: Tamic | 来源:发表于2016-07-08 16:33 被阅读54381次

简书 :Tamic
原文地址:
http://www.jianshu.com/p/29c2a9ac5abf


基于Retrofit2.0 封装的超好用的RetrofitClient

结尾的github的2.x分支项目已经做对Rxjava2做了支持,可自行下载。


RetrofitClient

基于Retrofit2.0封装的RetrofitClient.

  • 避免重复创建Retrofit实列.
  • 调用方便简洁.
  • 无需重复设置属性的步骤.
  • 可固定配置 Host 也可动态配置Url、请求头、参数等.
  • 支持文件下载和上传.
  • 支持json形式提交.
  • 支持扩展APIService
  • 统一处理无网络情况,和支持加载进度
  • 结合RxJava
  • 支持缓存机制
  • 优化取消

使用原生的Retrofit请求网络,熟悉的朋友必定了解,在某个ApiServie方法多时 Retrofit设置就显得有点累赘,今天给大家带来对Retrofit的基本封装。这次对Retrofit进阶篇,本次封装已加入RxJava,请在阅读下文前请先了解RXJAVA和本人写的Retrofit系列文章,

基本步骤:

构建Retrofit的接口service.

构建基础拦截器 Interceptor.

构建Cookie管理工具CookieManger.

构建 单列RetrofitClient客户端.

RetrofitClient的使用.

ApiService

请求网络的API接口类,这里你可以增加你需要的请求接口,也可复用已经实现的几个方法。

   /**
* Created by Tamic on 2016-07-08.
*/
 public interface ApiService {

public static final String Base_URL = "http://ip.taobao.com/";
/**
 *普通写法
 */
@GET("service/getIpInfo.php/")
Observable<ResponseBody> getData(@Query("ip") String ip);


@GET("{url}")
Observable<ResponseBody> executeGet(
        @Path("url") String url, 
        @QueryMap Map<String, String> maps);


@POST("{url}")
Observable<ResponseBody> executePost(
        @Path("url") String url,
        @FieldMap Map<String, String> maps);

@Multipart
@POST("{url}")
Observable<ResponseBody> upLoadFile(
        @Path("url") String url,
        @Part("image\\"; filename=\\"image.jpg") RequestBody avatar);

@POST("{url}")
Call<ResponseBody> uploadFiles(
        @Url("url") String url,
        @Part("filename") String description,
        @PartMap()  Map<String, RequestBody> maps);

}

上面新增了几个常用的请求方法

第一个只是普通写法的列子, url ,请求头,参数都是写死的。 不建议这么做

第二,三个分别是Get 和POST请求,method Url, headers, body参数都可以动态外部传入。

四 五是单文件/图片和多文件/图片上传

构建基础拦截器

用来设置基础header,这里是通过MAP键值对来构建,将heder加入到Request中。

  /**
 * BaseInterceptor,use set okhttp call header
 * Created by Tamic on 2016-06-30.
*/
public class BaseInterceptor implements Interceptor{

   private Map<String, String> headers;

   public BaseInterceptor(Map<String, String> headers) {
      this.headers = headers;
    }

   @Override
   public Response intercept(Chain chain) throws    IOException {

    Request.Builder builder = chain.request()
            .newBuilder();
    if (headers != null && headers.size() > 0) {
        Set<String> keys = headers.keySet();
        for (String headerKey : keys) {
            builder.addHeader(headerKey,   headers.get(headerKey)).build();
        }
    }
    return chain.proceed(builder.build());

 }
}

构建Cookie管理者

用来管理cookie, 储存cookie的store这里不再重复说明,具体列子请见:

<Retrofit 2.0 超能实践,完美同步Cookie实现免登录>

public class NovateCookieManger implements CookieJar {

private static final String TAG = "NovateCookieManger";
private static Context mContext;
private static PersistentCookieStore cookieStore;

/**
 * Mandatory constructor for the NovateCookieManger
 */
public NovateCookieManger(Context context) {
    mContext = context;
    if (cookieStore == null) {
        cookieStore = new PersistentCookieStore(mContext);
    }
}

@Override
public void saveFromResponse(HttpUrl url, List<Cookie> cookies) {
    if (cookies != null && cookies.size() > 0) {
        for (Cookie item : cookies) {
            cookieStore.add(url, item);
        }
    }
}

@Override
public List<Cookie> loadForRequest(HttpUrl url) {
    List<Cookie> cookies = cookieStore.get(url);
    return cookies;
}

}

构建RetrofitClient客户端.

今天重要的环节来了,RetrofitClient主要负责创建具体Retrofit,和调度分发请求。设置格式工厂。添加cookie同步,构建OkHttpClient,添加BaseUrl,对加密证书https我没做加入,希望读者参考我的本系列文章自行加入,因为我不喜欢升伸手党。

   /**
 * RetrofitClient
 * Created by Tamic on 2016-06-15.
 */
public class RetrofitClient {

private static final int DEFAULT_TIMEOUT = 5;

private ApiService apiService;

private OkHttpClient okHttpClient;

public static String baseUrl = ApiService.Base_URL;

private static Context mContext;

private static RetrofitClient sNewInstance;
private static class SingletonHolder {
    private static RetrofitClient INSTANCE = new RetrofitClient(
            mContext);
}

public static RetrofitClient getInstance(Context context) {
    if (context != null) {
        Log.v("RetrofitClient", DevUtil.isDebug() + "");
        mContext = context;
    }
    return SingletonHolder.INSTANCE;
}


public static RetrofitClient getInstance(Context context, String url) {
    if (context != null) {
        mContext = context;
    }
    sNewInstance = new RetrofitClient(context, url);
    return sNewInstance;
}

private RetrofitClient(Context context) {

    this(context, null);
}

private RetrofitClient(Context context, String url) {

    if (TextUtils.isEmpty(url)) {
        url = baseUrl;
    }
    okHttpClient = new OkHttpClient.Builder()
            .addNetworkInterceptor(
                    new HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.HEADERS))
            .cookieJar(new NovateCookieManger(context))
            .addInterceptor(new BaseInterceptor(mContext))
            .connectTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS)
            .build();
    Retrofit retrofit = new Retrofit.Builder()
            .client(okHttpClient)
            .addConverterFactory(GsonConverterFactory.create())
            .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
            .baseUrl(url)
            .build();
    apiService = retrofit.create(ApiService.class);
}

public void getData(Subscriber<ResponseBody> subscriber, String ip) {
    apiService.getData(ip)
            .subscribeOn(Schedulers.io())
            .unsubscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(subscriber);
}

public void get(String url, Map headers, Map parameters, Subscriber<ResponseBody> subscriber) {
    apiService.executeGet(url, headers, parameters)
            .subscribeOn(Schedulers.io())
            .unsubscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(subscriber);
}

public void post(String url, Map headers, Map parameters, Subscriber<ResponseBody> subscriber) {
    apiService.executePost(url, headers, parameters)
            .subscribeOn(Schedulers.io())
            .unsubscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(subscriber);
 }

}

细心的朋友已经发现上面代码 在指定生产线程和消费线程的时候,步骤有点麻烦,每个api都得进行指定线程,那么可以利用rxJava的转换器写一个Transformer

Observable.Transformer schedulersTransformer() {
  return new Observable.Transformer() {


  @Override
  public Object call(Object observable) {
   return ((Observable)  observable).subscribeOn(Schedulers.io())
   .unsubscribeOn(Schedulers.io())
   .observeOn(AndroidSchedulers.mainThread());
   }
   };
}

那么api可以这样优化了:

 public Subscription getData(Subscriber<IpResult> subscriber, String ip) {
   return apiService.getData(ip)
   .compose(schedulersTransformer())
   .subscribe(subscriber);
  }

调用 RetrofitClient

   RetrofitClient.getInstance(this).getData(new Subscriber<ResponseBody>() {
        @Override
        public void onCompleted() {
            Toast.makeText(MainActivity.this, "加载完成", Toast.LENGTH_LONG).show();
        }

        @Override
        public void onError(Throwable e) {
            Toast.makeText(MainActivity.this, "失败!: " + e.getMessage(), Toast.LENGTH_LONG).show();
        }

        @Override
        public void onNext(ResponseBody ResponseBody) {
            Toast.makeText(MainActivity.this, ResponseBody.toString(), Toast.LENGTH_LONG).show();
        }
    }, "21.22.11.33");

代码很简洁,在用到的地方获取单列直接调用你需要的方法,在RxSubscriber回调中处理你的业务逻辑即可,无需考虑是否在主线程,其他调用方法同上。

很多时候BaseApiService无法满足需求时,Retrofit增加了扩展接口 create 来创建你的API,接着调用execute就可以和RxJava关联

           //create  you APiService    
          MyApiService service =                 

         RetrofitClient.getInstance(MainActivity.this).create(MyApiService.class);    
        // execute and add observable    
        RetrofitClient.getInstance(MainActivity.this).execute(            
                                   service.getData("21.22.11.33"), new Subscriber<IpResult>() {                                     

                          @Override                
                           public void onCompleted() {               
                           } 
           
                           @Override                
                           public void onError(Throwable e) {                    
                                  Toast.makeText(MainActivity.this, e.getMessage(), Toast.LENGTH_LONG).show();                             
                           }   
         
                          @Override                
                          public void onNext(IpResult responseBody) {    
                                          Toast.makeText(MainActivity.this, responseBody.toString(),  Toast.LENGTH_LONG).show();                

                        }             
                    });}

取消

 Subscription subscription =   RetrofitClient.getInstance(MainActivity.this)
                    .createBaseApi()
                    .getData(new BaseSubscriber<IpResult>(MainActivity.this) {

                @Override
                public void onError(ResponeThrowable e) {
                    Log.e("Lyk", e.code + " " + e.message);
                    Toast.makeText(MainActivity.this, e.message, Toast.LENGTH_LONG).show();

                }

                @Override
                public void onNext(IpResult responseBody) {
                    Toast.makeText(MainActivity.this, responseBody.toString(), Toast.LENGTH_LONG).show();
                }
            }, "21.22.11.33");
            
            subscription.unsubscribe();

Rxjava结合Retrofit,如何优雅的取消请求!
优雅的取消请看:http://www.jianshu.com/p/d62962243c33

总结

本次封装只对retrofit进行了简单封装,很多场景和需求还是存在缺陷,这种单列模式已不符合目前流行的Builder模式,本人已开始进行下一步的封装工作,:
笔者已开发了新的框架开发Novate

https://github.com/NeglectedByBoss/Novate


源码 GitHub :https://github.com/NeglectedByBoss/RetrofitClient
已全部更新完成

系列导读

第一时间获取各位大佬的技术文章和资讯请关注微信公众号!

开发者技术前线

相关文章

网友评论

  • 小茅屋09:笔者,首先辛苦你。辛苦了这么久,总该给个能用的东西吧?gson解析到泛型是有问题的!不知道什么 原因,网络也不通 !
  • 毕竟nice:请问支持post数据缓存吗
  • 小蘑菇的驼羊:楼主你好,Retrofit retrofit2 = new Retrofit.Builder().baseUrl("https://www.baidu.com/";)
    .addConverterFactory(ScalarsConverterFactory.create())
    .build();
    这样也可以获取数据,请问这个和添加证书有什么区别吗?
    小蘑菇的驼羊:@Tamic 嗯,是的。的确是这样的
    Tamic:@小蘑菇的驼羊 说明没做证书限制啊
  • 2d0ac5a90df7:有个问题请教,你的RetrofitClient中用intercepter添加请求头,我试着添加了token,然后发现,没登录状态没有带token(正常,我用的时一个参数的getInstance),登录之后我先调用了一次带两个参数的getInstance来传递token,再去调用一个参数的getInstance去调用其他接口时发现居然带上了token,这是怎么回事
  • fd1e786b155b:楼主有rxjava2的吗,看了下你源码里面用的还是rxjava1吧
    Tamic:@蛧虫 切分支到2.x
  • 繁复至极返璞归简:楼主,Retrofit请求参数如何加密?
  • hom03:楼主威武
    Tamic: @tdtk 6666
  • 白银之火:你好,我在源码中的 RetrofitClient 类看到了 changeApiBaseUrl 的方法,这个方法是没用使用了吗?改成在 getInstance 的时候传入 Base_URL
    Tamic: @白银之火 貌似有用 你试试
  • _那个人:楼主在不
  • _那个人:楼主在RetrofitClient里面的Context定义成静态的 在构造中将传入的Ancivity的事例赋值给这个静态变量,那是不是所有传入 的Activity 的实例都会泄漏啊?
  • 55e101ce7ce4:我也是刚开始学习,但是你这样封装不太好.就像是为了强行使用rxjava.事实上你这个去掉rxjava封装代码会更加简洁,因为Retrofit本身自带线程切换. Retrofit结合到rxjava的优点在于返回了Flowable,进而可以对数据通过各种操作进行处理.如果没有这个优势,那就不需要rajava了呀 这是我的见解,不喜勿喷
    Tamic:@一只瘫猫 http://www.jianshu.com/p/8dede8de0713
  • 82fb67508fa0:大神 你好 我想问个问题 如果 我的ApiService 这样写
    @get
    Observable<BaseBean<T>> getData(@Url String url , @QueryMap Map<String, String> map);

    那么每次Url 不同时 这个bean怎么样解决? 想不出办法一个 接口就全部搞定
    Tamic: @日落陪你看小孟孟 反射可以解决的
    82fb67508fa0:@Tamic 那么我每次请求是不是只能对应一个相应的接口,没办法只写两个get ,pos方法就解决了是吧t
    Tamic:@日落陪你看小孟孟 不可以的 retrofit限制了 这里的T必须是一个具体实列
  • HopCoder:貌似你的这个写法有很大的一个弊端,就是返回结果不支持泛型 , 如public Subscription get(String url, Map parameters, Subscriber<IpResult> subscriber) 这个方法, 如果不能够把IpResult换成T , 那你这个方法 完全无意义, 求解如果更换成T
    HopCoder:@Tamic 目前正在封装项目的框架, 但是不管怎么封装,感觉都没有原生的那样灵活。 但是原生的又有一个问题, 每写一个结构,就要到apiservice接口里面去定义一个方法 和参数,所以我想简化下。
    HopCoder:@Tamic ??? 没懂什么意思?
    Tamic:@Joker先生 可以支持 但是比较麻烦 可以我的这个retrofit的客观评价那篇文章
  • Super_l1:楼主您好。我在github上下载了demo跑了一下。发现get、post、json按钮点击均提示classCastException异常,麻烦楼主抽空看一下。十分感谢
    0da7c37d223c:@Tamic 有这个方法吗
    Tamic:@AvALand 直接用novate
  • 5b0dd1982ef1:挺好的,最近项目需要更新数据层,也准备使用 rxjava+ Retrofit
    如果是Retrofit2+rxjava2就好了
    Tamic:@5b0dd1982ef1 后续会升级的
  • 心需要麻醉:@pART("image\\"; filename=\\"image.jpg") 这样的转义用法,,studio中一直不行;是不是应该是这样的:@pART("\"image\"; filename=\"image.jpg\"")
    Tamic:@心需要麻醉 嗯嗯
  • 93210d5182a6:addInterceptor(new BaseInterceptor(mContext))这个地方需要传入的是heads集合,而文中是mContext;
    apiService.executeGet(url, headers, parameters)在apiService的.executeGet方法只有两个参数

    不知道说的对不对,望大神解答
  • 程序员徐公:楼主,请问一下,在调用BaseApiService里面的executeGet 方法的时候抛出异常,om.google.gson.internal.LinkedTreeMap cannot be cast to com.tamic.retrofitclient.IpResult,y要怎样解决?
    程序员徐公:@Tamic 好的,谢谢
    Tamic:@xujun9411 这个已经不维护 看novate那个框架
  • 文淑: @Override
    public void onProgress(final int progress) {
    super.onProgress(progress);
    Log.e("SocketActivity", "onProgress--------" + progress);
    btnDownload.setText(progress+"");

    大概是这样的,我直接传了进度当参数,打印进度有,但是btn的值没有变化。
    Tamic:@文淑 1057531664
    文淑:@Tamic 我测过了,一样的不行。方便加QQ吗?373872690
    Tamic:@文淑 你用UI线程更新下
  • 文淑:你好,楼主看到请回复。onProgress方法中不能更新UI,我也仔细看了你的流程,你在UI线程调用的,按理说可以更新,我测试了好几种方法都不能更新。请教
    Tamic:@文淑 我也纳闷 最后用post发到Ui线程里了
  • a72e9779db56:okhttp3.ResponseBody$1 cannot be cast to com.tamic.retrofitclient.net.BaseResponse
    这是什么意思
    Tamic:@a72e9779db56 你关闭cookie同步试试
    a72e9779db56:@Tamic 我用的是您写的封装retrofit,返回数据在okhttp可以打印出来,请求完成后就报这个错,是怎么回事??
    Tamic: @a72e9779db56 返回体无法转成你想要的对象
  • terryblad:楼主compile 'com.tamic.novate:novate:1.0.2'一直出现Error:Failed to resolve: com.android.support:design:23.2.1 错误
    terryblad:@Tamic build tool用的23.0.2,我在github下了你的example 工程 发现你的novate里是compile的com.android.support:design:23.2.1,但这个无法下载,我替换成了23.0.1的,所以导入compile 'com.tamic.novate:novate:1.0.2'这个会有错
    Tamic:@terryblad sdk
    Tamic:@terryblad 你的工程版本多少?
  • 大象哥哥爱撸码:难道你们没有遇到,动态传入URL,"/"被转义成"%2F"了吗?
    Tamic:@星辉祝愿 用@Url 不要用@path 就可以了
    星辉祝愿:@_西西 我也遇到了。请问解决了?
  • InnerNight:还有个问题,这种用intercepter添加header的方法,必须是在一开始创建OkHttpClient的时候就传入,这样如果后续需要修改或者不同API有自己的header参数,该怎么弄?
    Tamic:@InnerNight public class BaseInterceptor implements Interceptor{
    private Map<String, String> headers;
    public BaseInterceptor(Map<String, String> headers) {
    this.headers = headers;
    }

    @Override
    public Response intercept(Chain chain) throws IOException {

    Request.Builder builder = chain.request()
    .newBuilder();
    if (headers != null && headers.size() > 0) {
    Set<String> keys = headers.keySet();
    for (String headerKey : keys) {
    builder.addHeader(headerKey, headers.get(headerKey)).build();
    }
    }
    return chain.proceed(builder.build());

    }
    InnerNight:@Tamic_码小白 我check out了你的代码,并没有看见三个参数的get方法。猜想你是说用@Header字段吗?
    那用这个字段添加的header和intercepter中用addHeader的header是可以共存的?
    Tamic:@InnerNight 你调用
    RetrofitClient.getInstance(context)..createBaseApi().get("you path url"
    ,maps, maps, new Subscriber<IpResult>());

    这样每个api的header是单独加入的
  • InnerNight:这样没法用GsonConverterFactory在获取的时候做数据类型转换了,需要交给上层处理。
    Tamic:@InnerNight 改天我再优化下
    InnerNight:@Tamic_码小白 那ResponseBody转化成各个数据类型,不知道题主有相关代码分享么?
    Tamic:@InnerNight 是的
  • c21f1319c8a7:@Field参数如何分装?在url拼接参数有自带的QueryMap,那post请求放在消息主体的数据,不能用url拼接参数了,retrofit里@Field每个参数都要写,没什么头绪,求大神给个思路
    Tamic:@韩倚风 @fieldMap
  • 屠龙:楼主好,RetrofitClient类静态持有了Context对象,这样写不会出现内存泄漏么?这一句private static Context mContext;studio会报警告的吧
    Tamic:@屠龙 是的 用week软引用一下 也是可以的
    屠龙:@Tamic_码小白 哦哦,studio报了这个警告,应该是lint检测结果
    Tamic:@屠龙 因为采用单列 持有的也是Applcation的context 不会出现你担心的内存泄露
  • 24K纯帅豆:请问一下,在请求服务端的时候我在自己定义的拦截器中打了个断点,为什么请求的时候不会断住呢?
    24K纯帅豆:@Tamic_码小白 我没有启用混淆,当我断点断在外面okhttpclient的那里,然后是说我定义的拦截器没有参数这样
    Tamic:@24K纯帅豆 可能你启用了混淆的缘故 请关闭混淆开关在debug
  • Tamic:这里统一回复下:cookie不生效的问题,因为首次是cookie为空的所以无法加入,第二次理应加入cookie,但是由于RetofitClient是单列的原因,无法再次实例化,因此建议把单列去掉,或者单着单列再次addcookie就可以了
  • qiaoStr:builder的模式大概啥时候出?
    Tamic:@nullStr https://github.com/NeglectedByBoss/Novate/ 已出
    qiaoStr:@Tamic_码小白 等着
    Tamic:@nullStr 已经进行中,大约下月初 到时候会兼容这种经典的单列模式的
  • 8314e3a0c30e:好高级的感觉
    Tamic:@赵丰年 用习惯了也一般

本文标题:Android基于Retrofit2.0+RxJava 封装的超

本文链接:https://www.haomeiwen.com/subject/rocrjttx.html