美文网首页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异常,麻烦楼主抽空看一下。十分感谢
        星期8的深夜:@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 是的
      • 韩倚风:@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