美文网首页常用框架第三方框架的学习网络请求
Android网络框架源码分析二---Retrofit

Android网络框架源码分析二---Retrofit

作者: 楚云之南 | 来源:发表于2016-01-03 23:11 被阅读6884次

    前面分析了Volley的代码,读者可能已经发现了基本上就是分析几个任务队列的处理逻辑和工作线程(网络工作线程和缓存工作线程)。Volley中的工作线程是自己使用线程数组来维护的,那么就有可能存在线程由于异常退出之后,没有下一个工作线程补充的风险(线程池可以弥补这个缺陷)。不管怎么样,个人觉得Volley代码是比较简洁、高效的,而且也比较适合阅读,建议大家花个小半天搂一眼源码。

    ok,我们接着说今天的主题。俗话说社会在进步,人类在发展,精彩的故事发生地让人目不暇接,就让我们刚想站起来为Volley鼓掌时,Retrofit又适时的开源了,并且适时地发布了2.0版本。在Retrofit 2.0中,最大的改动莫过于减小库的体积,首先,Retrofit 2.0去掉了对所有的HTTP客户端的兼容,而钟情于OkHttpClient一个,极大地减少了各种适配代码,原因一会儿说;其次,拆库,比如将对RxJava的支持设置为可选(需要额外引入库);再比如将各个序列化反序列化转换器支持设置为可选(需要额外引入库); 这次升级的其它内容,可以戳这里,中文版的哦, Retrofit 2.0升级演讲PPT总结

    注: 本文余下部分中出现的HttpClient均代指apache 的HttpClient实现

    注: 对于2.0抛弃HttpClient和HttpURLConnection,为了减小库体积是一方面,另外一个重要的原因作为一个专门为Android&Java 应用量身打造的Http栈,OkHttpClient越来越受到各个大的开源项目的青睐,毕竟它本身就是开源的。

    此外,OkHttpClientHttpClient相比,它最大的改进是自带工作线程池,所以上层应用无需自己去维护复杂的并发模型,而HttpClient仅仅提供了一个线程安全的类,所以还需要上层应用去处理并发的逻辑(实际上Volley一部分工作就是干这个)。从这个角度来说,Retrofit得益于OkHttpClient的优势,较之于Volley是一种更加先进的网络框架。

    Paste_Image.png

    由于Retrofit不需要去关心并发工作线程的维护,所以它可以全力关注于如何精简发送一个请求的代价,实际上使用Retrofit发送一个请求实在是太easy了!

    注意,本文并不是使用Retrofit的帮助文档,建议先看Retrofit的文档和OkHttp的文档,这些对于理解余下部分很重要。

    使用Retrofit发送一个请求

    假设我们要从这个地址 http://www.exm.com/search.json?key=retrofit中获取如下Json返回:

      {
        "data": [
                   {
                     "title":"Retrofit使用简介",
                     "desc":"Retrofit是一款面向Android和Java的HttpClient",
                     "link":"http://www.exm.com/retrofit"
                   },
                   {
                      "title":"Retrofit使用简介",
                      "desc":"Retrofit是一款面向Android和Java的HttpClient",
                      "link":"http://www.exm.com/retrofit"
                   },
                   {
                     "title":"Retrofit使用简介",
                     "desc":"Retrofit是一款面向Android和Java的HttpClient",
                     "link":"http://www.exm.com/retrofit"
                   } 
              ]
      }
    

    1.引入依赖

    compile 'com.squareup.retrofit:retrofit:2.0.0-beta2'
    //gson解析
    compile 'com.squareup.retrofit:converter-gson:2.0.0-beta2'
    

    2.配置Retrofit

    Retrofit retrofit = new Retrofit.Builder()
                        .baseUrl("http://www.exm.com")
                        .addConverterFactory(GsonConverterFactory.create())
                        .client(new OkHttpClient())
                        .build();
    

    3.新建Model类 SearchResult来解析结果

    4.新建请求接口
    Retrofit使用注解来定义一个请求,在方法上面指定请求的方法等信息,在参数中指定参数等信息。

    public interface RestApi {
            @GET("/search.json")
            Call<List<SearchResult>> search(
                @Query("key") String key
                 );
                
                //可以定义其它请求
               @GET("/something.json")
                Call<SomeThing> dosomething(
                       @Query("params") long params
                        .......
                        .......
                  );
                
    }
    

    5.发送请求,我们可以发送同步请求(阻塞当前线程)和异步请求,并在回调中处理请求结果。

      RestApi restApi = retrofit.create(RestApi.class);
      Call<List<SearchResult>> searchResultsCall = resetApi.search("retrofit");
      //Response<List<SearchResult> searchResults = searchResultsCall.execute();   同步方法
      searchResultsCall.enqueue(new Callback<List<SearchResult>>() {
                    @Override
                    public void onResponse(Response<List<SearchResult>> response, Retrofit retrofit) {
                        content.setText(response.body().toString());
                    }
    
                    @Override
                    public void onFailure(Throwable t) {
                        content.setText("error");
                    }
                });
    

    Retrofit源码分析

    Retrofit整个项目中使用了动态代理和静态代理,如果你不太清楚代理模式,建议先google一下,如果对于Java的动态代理原理不是太熟悉,强烈建议先看:这篇文章-戏说代理和Java动态代理
    ok,下面按照我们使用Retrofit发送请求的步骤来:

    RestApi restApi = retrofit.create(RestApi.class); //产生一个RestApi的实例
    输入一个接口,直接输出一个实例。

    这里岔开说一句,现在随便在百度上搜一下Java动态代理,出来一堆介绍AOP(面向切面编程)和Spring,导致一部分人本末倒置,认为动态代理几乎等于AOP,甚至有些人认为动态代理是专门在一个函数执行前和执行后添加一个操作,比如统计时间(因为现在几乎所有介绍动态代理的地方都有这个例子),害人不浅。实际上动态代理是JDK提供的API,并不是由这些上层建筑决定的,它还可以做很多别的事情,Retrofit中对动态代理的使用就是佐证。

    搂一眼这里的源码,再次建议,如果这里代码看不明白,先看看上面提到的那篇文章:

    public <T> T create(final Class<T> service) {
      //返回一个动态代理类的实例
      return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
        //这个InvocationHandler是关键所在,以后调用restapi接口中的方法都会被发送到这里
        new InvocationHandler() {
          private final Platform platform = Platform.get();
    
          @Override 
          public Object invoke(Object proxy, Method method, Object... args)
              throws Throwable {
            /* 如果是Object的方法,如toString()等,直接调用InvocationHandler的方法,
             *注意,这里其实是没有任何意义的,因为InvocationHandler其实是一个命令传送者
             *在动态代理中,这些方法是没有任何语义的,所以不需要在意
             */
            if (method.getDeclaringClass() == Object.class) {
              return method.invoke(this, args);
            }
           //对于Java8的兼容,在Android中不需要考虑
            if (platform.isDefaultMethod(method)) {
              return platform.invokeDefaultMethod(method, service, proxy, args);
            }
            //返回一个Call对象
            return loadMethodHandler(method).invoke(args);
          } 
        });
    }
    

    我们可以看到Retrofit.create()之后,返回了一个接口的动态代理类的实例,那么我们调用这个代理类的方法时,调用自然就被发送到我们定义的InvocationHandler中,所以调用
    Call<List<SearchResult>> searchResultsCall = resetApi.search("retrofit"); 时,直接调用到InvocationHandlerinvoke方法,下面是invoke此时的上下文:

       @Override 
          public Object invoke(Object proxy, Method method, Object... args)
              throws Throwable {
              //proxy对象就是你在外面调用方法的resetApi对象
              //method是RestApi中的函数定义,
              //据此,我们可以获取定义在函数和参数上的注解,比如@GET和注解中的参数
              //args,实际参数,这里传送的就是字符串"retrofit"
    
            //这里必然是return 一个Call对象
            return loadMethodHandler(method).invoke(args);
          } 
    

    此时,invoke的返回必然是一个CallCallRetrofit中对一个Request的抽象,由此,大家应该不难想象到loadMethodHandler(method).invoke(args); 这句代码应该就是去解析接口中传进来的注解,并生成一个OkHttpClient中对应的请求,这样我们调用searchResultsCall时,调用OkHttpClient走网络即可。确实,Retrofit的主旋律的确就是这样滴。

    注意,实际上Call,CallBack这种描述方式是在OkHttp中引入的,Retrofit底层使用OkHttp所以也是使用这两个类名来抽象一个网络请求和一个请求回来之后的回调。总体来看,Retrofit中的Call Callback持有一个OkHttpCall Callback,将对Retrofit中的各种调用转发到OkHttp的类库中,实际上这里就是静态代理啦,因为我们会定义各种代理类,比如OkHttpCall

    注 下文中如不无明确指出,则所有的Call,CallBack都是Retrofit中的类

    MethodHandler类

    MethodHandler类,它是Retrofit中最重要的抽象了,每一个MethodHandler对应于本例的RestApi中的一个每个方法代表的请求以及和这个请求相关其它配置,我们来看看吧。

    //这个OkHttp的工厂,用于产生一个OkHttp类库中的Call,实际上就是传入配置的Builder的OkHttpClient
    private final okhttp3.Call.Factory callFactory;
    //通过Method中的注解和传入的参数,组建一个OkHttp的Request
    private final RequestFactory requestFactory;
    //用于对Retrofit中的Call进行代理
    private final CallAdapter<?> callAdapter;
    //用于反序列化返回结果
    private final Converter<ResponseBody, ?> responseConverter;
    
    // 返回一个Call对象
    Object invoke(Object... args) {  
        return callAdapter.adapt(new OkHttpCall<>(callFactory, requestFactory, args, responseConverter));
    }
    

    Retrofit中通过添加Converter.Factory来为Retrofit添加请求和响应的数据编码和解析。所以我们可以添加多个Converter.FactoryRetrofit提供处理不同数据的功能。

    CallAdapter 可以对执行的Call进行代理,这里是静态代理。我们也可以通过添加自己的CallAdapter来作一些操作,比如为请求加上缓存:

      new CallAdapter.Factory {
          @Override 
           public <R> Call<R> adapt(Call<R> call) {  return call;}
      }
    
    class CacheCall implements Call  {
        Call delegate;
         CacheCall(Call call) {
             this.delegate = call;
        }
    
        @Override
        public void enqueue(Callback<T> callback) {
            //查看是否有缓存,否则直接走网络
            if(cached) {
                return cache;
            }
            this.delegate.enqueue(callback);
        }
    }
    

    至此,我们在调用resetApi.search("retrofit");时,实际上调用的层层代理之后的OkHttpCall,它是MethodHandler中invoke的时候塞入的。看看OkHttpCall中的代码吧:

    @Override 
    public void enqueue(final Callback<T> callback) {
      okhttp3.Call rawCall;
      try {
        //创建一个okhttp的Call
        rawCall = createRawCall();
      } catch (Throwable t) {
        callback.onFailure(t);
        return;
      }
     //直接调用okhttp的入队操作
    rawCall.enqueue(new okhttp3.Callback() {
      private void callFailure(Throwable e) {
        try {
          callback.onFailure(e);
        } catch (Throwable t) {
          t.printStackTrace();
        }
      }
    
      private void callSuccess(Response<T> response) {
        try {
          callback.onResponse(response);
        } catch (Throwable t) {
          t.printStackTrace();
        }
      }
    
      @Override 
      public void onFailure(Request request, IOException e) {
        callFailure(e);
      }
    
      @Override 
       public void onResponse(okhttp3.Response rawResponse) {
        Response<T> response;
        try {
          //解析结果
          response = parseResponse(rawResponse);
        } catch (Throwable e) {
          callFailure(e);
          return;
        }
        callSuccess(response);
      }
    });
    

    }

    如此一来,OkHttpClient的回调也被引导到我们的Callback上来,整个流程就已经走通了。

    总结

    终于到了总结的时候了,一般来说,这都是干货的时候,哈哈~

    Volley对比优势

    1. 缓存处理;Volley自己就提供了一套完整的缓存处理方案,默认使用文件存储到磁盘中,并且提供了TTL SOFTTTL这么体贴入微的机制;一个Request可能存在两个Response,对于需要显示缓存,再显示网络数据的场景真是爽的不要不要的。而Retrofit中并没有提供任何和缓存相关的方案。
    2. 代码简单,可读性高。Volley的代码是写的如此的直接了当,让你看起代码来都不需要喝口茶,这样的好处是我们我们需要修改代码时不太容易引入bug....囧
    3. 同一个请求如果同时都在发送,那么实际上只会有一个请求真正发出去, 其它的请求会等待这个结果回来,算小小优化吧。实际上这种场景不是很多哈,如果有,可能是你代码有问题...
    4. 请求发送的时候提供了优先级的概念,但是是只保证顺序出去,不保证顺序回来,然并卵。
    5. 支持不同的Http客户端实现,默认提供了HttpClientHttpUrlConnection的实现,而Retrofit在2.0版本之后,锁死在OkHttp上了。
      6.支持请求取消

    Retrofit

    1.发送请求真简单,定义一个方法就可以了,这么简单的请求框架还有谁?Volley?
    2.较好的可扩展性,Volley中每一个新建一个Request时都需要指定一个父类,告知序列化数据的方式,而Retrofit中只需要在配置时,指定各种转换器即可。CallAdapter的存在,可以使你随意代理调用的Call,不错不错。。。
    3.OkHttpClient自带并发光环,而Volley中的工作线程是自己维护的,那么就有可能存在线程由于异常退出之后,没有下一个工作线程补充的风险(线程池可以弥补这个缺陷),这在Retrofit中不存在,因为即使有问题,这个锅也会甩给OkHttp,嘿嘿
    4.支持请求取消

    再次说明的是,Retrofit没有对缓存提供任何额外支持,也就是说它只能通过HTTPCache control做文件存储,这样就会有一些问题:
    1.需要Server通过Cache control头部来控制缓存,需要修改后台代码
    2.有些地方比较适合使用数据库来存储,比如关系存储,此时,Retrofit就无能为力了
    3.缓存不在我们的控制范围之内,而是完全通过OkHttp来管理,多少有些不便,比如我们要删除某一个指定的缓存,或者更新某一个指定缓存,代码写起来很别扭(自己hack请求头里面的cache contrl)

    而在我们项目的实际使用过程中,缓存是一个比较重要的角色,Retrofit对缓存的支持度不是很好,真是让人伤心。。。
    但是,我们还是觉得在使用中Retrofit真心比较方便,容易上手,通过注解代码可读性和可维护性提升了N个档次,几乎没有样板代码(好吧,如果你觉得每个请求都需要定义一个方法,那这也算。。),所以最后的决定是选择Retrofit。

    有人说了,Volley中的两次响应和缓存用起来很happy怎么办?嗯,我们会修改Retrofit,使它支持文件存储和ORM存储,并将Volley的缓存 网络两次响应回调移接过来,这个项目正在测试阶段,待我们项目做完小白鼠,上线稳定之后,我会把代码开源,大家敬请关注。

    更新 2016.061.06
    Volley官方文档上明确说鸟,只适用于轻量级请求,而不适用数据量较大的请求,比如下载一个50M的文件,原因是Volley默认在请求回来之后,就把数据从IO流里面搞到内存里面了,50M。。。
    Retrofit是直接把流甩出来了,你上面怎么处理是你的事情,所以这里Retrofit还是更加灵活

    相关文章

      网友评论

      • 1s的消失:大神请教一个问题:有这样一个情况,ViewPager+Fragment由于UI相同复用同一个Fragment,每个Fragment请求数据的参数不同。由于ViewPager会预加载1页,所以首次启动页面时第一页的数据有几率被第二页的覆盖掉,最后锁定问题应该是出在网络框架上,我用的retrofit,
        请问,retrofit有类似Volley同一请求同时发送会只发送一个的设计吗?(retrofit源码我看了看,不太好读,找不到在哪。)
      • 黄光华:大哥的文章总是把知识梳理的很好:clap:
      • 1d0e5516138e:开源带缓存的Retrofit
      • 牵着驼羊看简书:干货,好文章
      • 王玮123:学到了,谢谢
      • 洪小倲:等文章末尾说的开源带缓存的Retrofit :smile:
      • gatsby_dhn:为什么没有找到MethodHandler类的定义? :flushed:
        hjhjw1991:最新版本的`create`已经改变, 完成相同功能的是`ServiceMethod`和`CallAdapter`
      • fendo:👍
      • 捡淑:已关注文集
      • 带心情去旅行:注 下文中如不无明确支出,则所有的Call,CallBack都是Retrofit中的类
        支出?-->指出
        楚云之南:@带心情去旅行 多谢,已经改正
      • 748eba423c35:嗯,学习了
      • 裸奔的凯子哥:Retrofit最主要的就是对OkHttp的封装,缓存什么的都扔给OkHttp了,但是包装之后,Retrofit、OkHttp、RxJava可以被紧密的结合起来了,而且功能异常强大,我觉得这是Retrofit最重要的贡献
      • 曾樑::smile::smile:
        曾樑:@楚云之南 :pray::pray:
        楚云之南:@曾樑 辛苦~

      本文标题:Android网络框架源码分析二---Retrofit

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