Retrofit 的神秘面纱

yiweishi 8年前
   <p style="text-align:center"><img src="https://simg.open-open.com/show/1a63f42356bf31624a7915f435b72c09.png"></p>    <p>初识 <a href="/misc/goto?guid=4958837204152834453" rel="nofollow,noindex">Retrofit</a> ,觉得很神奇,体现在以下两点:</p>    <p><strong>神奇之一:仅凭注解和接口便可执行网络请求。</strong></p>    <pre>  <code class="language-java">public interface GithubService {        @GET("users/{username}")      Call<User> getUser(@Path("username") String username);    }</code></pre>    <p><strong>神奇之二:接口方法的返回值变幻无穷。</strong></p>    <p>无论你是用默认的方式,或者是RxJava,无论是在Java或是Android中调用,Retrofit均可满足,体现在接口的返回值,如下代码:</p>    <pre>  <code class="language-java">public interface GithubService {        //默认方式      @GET("users/{username}")      Call<User> getUserByDefault(@Path("username") String username);        //RxJava方式      @GET("users/{username}")      Observable<User> getUserByRxJava(@Path("username") String username);    }</code></pre>    <p>这两个神奇之处,犹如两层神秘面纱,将Retrofit原本俊俏娇羞的脸庞,遮掩得若隐若现。若不揭开面纱,一睹芳容,便无处释放程序员们最原始的冲动,于是一起来阅读源码吧。</p>    <h2><strong>寻找接口的实现类</strong></h2>    <p>对于第一个神奇之处,首先我们来看看GithubService接口是如何调用的:</p>    <pre>  <code class="language-java">Retrofit retrofit = new Retrofit.Builder()      .baseUrl("https://api.github.com/")      .build();    GitHubService service = retrofit.create(GitHubService.class);  Call<User> call = githubService.getUser("geniusmart");</code></pre>    <p>仔细斟酌这段代码, GitHubService 本身是个接口,并调用了 getUser() 方法获得返回值,那么问题来了:</p>    <ol>     <li>GitHubService 是由我们定义的接口,但是我们并没有定义实现类,实现类到底在哪里?</li>     <li>既然我们没有定义实现类,那么大胆假设该实现类是由Retrofit框架提供的。对于框架而言,该接口是由开发者自定义的,框架无法做到未卜先知,那么他是如何做到的?</li>    </ol>    <p><strong>以上两个问题可以总结为:框架如何动态生成 GitHubService 的实现类?</strong> 答案也不难猜测——动态代理模式。(对动态代理有疑问的同学,可以先看下 <a href="/misc/goto?guid=4959714139066546638" rel="nofollow,noindex">《公共技术点之 Java 动态代理》</a> 。</p>    <p>接下来验证我们的猜测, GitHubService 的来源在此行代码中:</p>    <pre>  <code class="language-java">GitHubService service = retrofit.create(GitHubService.class);</code></pre>    <p>查看 create() 的源码,所有谜底都将揭开:</p>    <p><img src="https://simg.open-open.com/show/99ac45f7997dd2325bc2385bf5a4bec7.png"></p>    <p style="text-align:center">通过JDK实现动态代理</p>    <p>这个方法生成并返回了 GitHubService 的动态代理对象,在执行接口的每个方法时,实际执行的是 invoke() ,而该方法里的逻辑便是此框架的主体流程,如下代码:</p>    <pre>  <code class="language-java">public Object invoke(Object proxy, Method method, Object... args){      //1.负责注解的解析      ServiceMethod serviceMethod = loadServiceMethod(method);      //2.负责与OKHttp3的对接,处理同步和异步发送网络请求      OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args);      //3.第二层面纱,下文再做解释      return serviceMethod.callAdapter.adapt(okHttpCall);  }</code></pre>    <p>这个方法里,做了三件事情:</p>    <ol>     <li>解析方法的注解,比如 get/post、url、Headers 等,解析的结果存放于 ServiceMethod 对象中。</li>     <li>创建 OkHttpCall ,该对象负责与OKHttp3对接,处理同步或异步的网络请求。</li>     <li>这是第二层神秘面纱的谜底,下文再做解释。</li>    </ol>    <p>至此,我们揭开了Retrofit的第一层神秘面纱,这是一位简单而优雅的小女子,表面朴实无华,恬静清新,令人迫不及待想要揭开她的第二层面纱,知晓她的内心世界。</p>    <h2><strong>变幻无穷的适配</strong></h2>    <p>首先来回顾下上文提到的第二个神秘之处,同样是获取用户信息的网络请求,可以返回 Call<User> 或 Observable<User> ,这是如何做到的?</p>    <p>上文中, invoke() 做的第三件事情便是获取返回值,如下:</p>    <pre>  <code class="language-java">return serviceMethod.callAdapter.adapt(okHttpCall);</code></pre>    <p>其中, adapt 是适配的意思,其目的是变废为宝,将指定的输入类型适配成实际需要的输出类型,来查看下它的源码:</p>    <pre>  <code class="language-java">public interface CallAdapter<T> {    <R> T adapt(Call<R> call);  }</code></pre>    <p>我们重点关注下 adapt 的输入输出。这里的设计非常大胆,输入是有约束的,即 Call 类型, <strong> 而输出为泛型 T ,也就是说输出是没有任何约束的 </strong> ,如此一来,扩展性极强,可以指定任何的类型。</p>    <p>剩下来的问题便是:</p>    <ol>     <li> <h3><sub>adapt 由哪个对象触发,即 CallAdapter 的具体实现是什么?</sub></h3> </li>     <li> <h3><sub>输入类型 Call 的具体实现是什么?</sub></h3> </li>     <li> <h3><sub>输出类型 T 的具体实现是什么?</sub></h3> </li>    </ol>    <p>这里直接给出答案,如下两图:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/1ae99df69b135b98839f8fb956f65ac0.png"></p>    <p style="text-align:center">默认方式的适配</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/e3c89bec45ba2fbd5a9af44ba74ddbb1.png"></p>    <p style="text-align:center">RxJava方式的适配</p>    <ul>     <li> <h3><sub>通过上面两图,可以看到,输入对象是固定的,即 OkHttpCall 对象,网络请求实际上便是由该对象发起的。</sub></h3> </li>     <li> <h3><sub>默认方式的输出对象是 ExecutorCallbackCall ,它持有 OkHttpCall 对象,指定了网络请求执行结束后的回调函数在UI线程中执行。</sub></h3> </li>     <li> <h3><sub>RxJava方式的输出对象是 Observable ,持有该对象便具备函数式编程的能力。</sub></h3> </li>     <li> <h3><sub>adapt() 的调用者——适配器 CallAdapter 是动态配置的,默认方式无需配置,如果使用RxJava,则需要配置适配器的工厂对象,如下代码:</sub></h3> <pre>  <code class="language-java">Retrofit retrofit = new Retrofit.Builder()    .baseUrl(BASE_URL)    .addConverterFactory(GsonConverterFactory.create())    .addCallAdapterFactory(RxJavaCallAdapterFactory.create())    .build();</code></pre> </li>    </ul>    <p>这样设计简直是解耦得一塌糊涂,我们可以任意变换调用方式,也可以任意扩展 CallAdapter 来定义新的调用方式,而框架无需做任何调整。比如 <a href="/misc/goto?guid=4958532891412312811" rel="nofollow,noindex">JakeWharton</a> 前几天开源的RxJava2的适配—— <a href="/misc/goto?guid=4959714139190311538" rel="nofollow,noindex">retrofit2-rxjava2-adapter</a> ,比如针对Google函数式编程库 Agera的适配—— <a href="/misc/goto?guid=4959714139277828910" rel="nofollow,noindex">retrofit-agera-call-adapter</a> ,简直OCP得一塌糊涂。</p>    <p>至此,第二层神秘面纱缓缓而落,这是一位心灵手巧的奇女子,你给她一针一线,她还你一幅刺绣,刺绣上可以是锦绣山河,也可以是紫气东来;你给她新鲜的食材,她变幻出色香味俱全的美味佳肴。</p>    <h2><strong>总结</strong></h2>    <p>动态代理、适配是Retrofit的核心,理解清楚这两点,再去梳理框架的其他细节,方可事半功倍。除此之外,框架内部使用了大量的Builder模式和Factory模式,这两个模式使得创建对象和获取对象更有条理,更清晰易懂。值得一提的是,这个框架配备了完整的单元测试用例,非常值得我们学习。</p>    <h2><strong>参考文章</strong></h2>    <p><a href="/misc/goto?guid=4958837204152834453" rel="nofollow,noindex">http://square.github.io/retrofit/</a></p>    <p><a href="/misc/goto?guid=4958964956869128717" rel="nofollow,noindex">https://github.com/square/retrofit</a></p>    <p> </p>    <p>来自:http://www.jianshu.com/p/0730fc155996</p>    <p> </p>