Retrofit分析与实现

来源:互联网 发布:淘宝品牌直营是真的吗 编辑:程序博客网 时间:2024/05/21 22:35


今天这篇来自 何以诚 的投稿,细致地分析了retrofit的原理,虽然代码居多,但是静下心来细细品味,还是能够让你收获良多的。最后祝大家周末愉快!


何以诚 的博客地址:http://blog.csdn.net/u013022222


前言


估计很多人和我一样,在接触 retrofit 这个库的时候就被它强大的功能所吸引住了。它不同于传统的网络请求方式的是,retrofit 巧妙的采用接口方式进行网络请求,每次调用接口方法,就是对应一次网络请求,这对于长期和丑陋接口做斗争的程序员来说这简直是莫大的福利啊。然而光是用肯定是不行,我们还得搞清其中的原理,知其why。一番周折之后,我发现自己在阅读源码并实现的过程中已经能作一文,于是写出来分享,算是学习中的心得体会。


示例


在开始之前,我们先看下一段简单的示例:




可以看到,在我们的例子中,我们首先创建了一个 retrofit 对象,这里使用的是Builder模式,创建过程中我们指定了网络请求时的 uri, client,converterFactory (这也是这个库的核心之处),callAdapterFactory。 


之后通过create方法,创建 WeatherApi 示例, 之后每次调用 WeatherApi 的方法都是对应于一次请求。


我们看下WeatherApi




这里都是GET注解,指定请求方式是get,GET中的值会添加在 baseUrl 中, Path注解指定了具体的请求path,它将替换本文中的{path}字符串为具体的值,在我们的例子中,最后的请求 uri 会变成:

  •  http://10.21.59.21:8080/json


好了,这里只是简单的看下示例,也是为我们整个全文做个铺垫,读者在阅读过程中要时常记得返回到此,才能加深理解。当然如果您还不了解该库具体的使用方式,可以参考:

http://square.github.io/retrofit


我在编写博文的过程中就已经思考过了,要想懂这个库为什么如此设计,光看源码肯定不行。首先要做的就是理解它的 Description information, 如果你点击了上面的链接,就可以看到,在它官网首页,就标注了retrofit是一个类型安全的Http客户端(A type-safe HTTP client for Android and Java)。什么叫类型安全呢?为什么要类型安全?这的确有点难懂啊


Type Safe


从上面的示例代码我们就看到了,在进行网络请求的时候,我们制定了它的客户端——OkHttpClient。也就是说,真正的网络请求都是通过 okhttp 实现的。然而,我们都使用过 okhttp,所以我们都应该知道,在获得服务器请求之后,我们都是通过 Response 对象来获得服务器返回的数据的,比如这样:



获得的数据无非就是二进制类型或者字符串类型的。如果服务器返回的是json字符串,我们还得通过gson把它转换成实体类对象。这显然是不够友好的,所以我们要改变。用户无需知道服务器返回的数据具体格式,我们只要知道它最终的类型就好了Book? Person?etc。屏蔽这些细节,专心于业务实现岂不是更好。


而我们的 retrofit 如何做到的呢?那就是通过 converterFactory 来实现了,它主要是负责将服务器返回的数据转换成具体的实例类对象。比如在我们这个例子里面,它的作用就是将服务器返回的json字符串转换成Book实体类对象。


分析实现方式


那么我们现在就根据上面的示例实现一个自己的 retrofit,不过在开始之前还是需要分析一下实现方式。


1:通过 Retrofit.Builder 对象 new 一个 Retrofit 对象,期间需要配置的有:


  • client:因为有时候我们可能需要在请求头加一个 token 头,用来作为访问服务器时验证其身份有效性的凭证。


  • baseUrl:所有的 请求Uri 都是基于它的 。


  • ConverterFactory:一种工厂对象,用于根据用户指定的返回值类型,确定最终将服务器返回数据转换成对应实体类对象的 Converter 类型。在我们的例子里面 GsonConverterFactory 将选用 GsonConverter 来转换。


  • CallAdapterFactory:网络请求之后返回的是对应的 Call< T >类型(这里的T对应于本文的Weather),然而如果我们结合RxJava使用的话,需要把它再做一次修饰,转换成Observable< T >类型。


2:http请求是采用 get,post,还是delete,都需要根据描述接口方法的注解来确定,如果是Get的话,我们就需要生成get方式的Request对象,同理于Post Delete方式。所以我们需要一个RequestFactory,它根据方法的注解描述生成对于的Request对象。


3:通过动态代理,生成接口对应的对象,之后的每次方法 Invoke 都是上述组建之间互相配合。


效果


我们先看下最终要到达的效果:




再看下BookApi:


/**
* Created by chan on 16/6/3.
*/
public interface BookApi {
   @Get("/{book}")
   Call<Book> getBook(@Path("book") String path, @Query("price") String price);}


实现


这里我们先实现创建接口对象,这里使用动态代理技术,如果你还不懂什么是动态代理技术,那么请查阅动态代理:




还是在 Retrofit3 中,我们看下 getMethodHandler 方法:




到这里我们看一看到 ,之后的方法调用其实都是在 MethodHandler 对象中进行了,我们移步到那个类之中去。




可以看到在create函数中我们调用 getCallAdapter 获得CallAdapter对象,之后这个对象只是用于修饰返回值,把Call< T >类型的返回值变成其他类型,但是,我们为了简便,我们只是简单的返回一下就完成适配工作,如下:




在我们的例子里面 m_callAdapterFactory 实际上是 SimpleCallAdapterFactory 对象,我们看下源码:




可以看到这里做了很多检测,看返回值是否没被当前 CallAdapter 修饰,或者判断返回值类型是否为ParameterizedType,移步 SimpleCallAdapter 中:




构造函数对应的 Type Call< T >T的实际类型,比如Call<Book>对应的Type就是 Book 之后传入到CTOR之中。


再回到之前的代码,在MethodHandler的invoke中,我们new了一个 OkHttpCall 传入到 adapt 方法之中:


/**
* 方法调用
* @param args 参数
* @return 获取到服务器返回值后 返回的java对象
* @throws IOException
*/
public Object invoke(Object... args) throws IOException {
   //到这里可以看到callAdapter用于修饰call    return m_callAdapter.adapt(new OkHttpCall<>(m_retrofit3, m_requestFactory, m_converter, args));}


我们看下OkHttpCall:




这里是核心了。execute 代表一次同步调用, 其中涉及的两步我都做了注释。我们可以分别看下 requestFacotry 是如何生成一个 okhttp request对象的 和 converter 是如何把服务器返回值转换成java实体类对象的。


RequestFactory





可以看到这里的业务逻辑还是不复杂的,都是简单的字符串替换,添加,值得注意的是,我们要替换字符串中被大括号包围的字符串 比如 “/a/{b}”,我们必能简单的通过string.replaceAll(“{b}”, “b”)替换,因为 replaceAll 第一个参数是 regexp,要通过”\{b\}”进行转义。


Converter


public class GsonConverter<T> implements IConverter<T> {
   private Gson m_gson;
   private Type m_type;
   
   public GsonConverter(Gson gson, Type type) {        m_gson = gson;        m_type = type;    }
   
   @Override    public T convert(Response response) throws IOException {
       return (T) m_gson.fromJson(response.body().charStream(), m_type);    }}


这里只简单的调用gson的api就可以得到对应的java实体类对象了。


效果图:




源码可以点击最后 阅读原文 查看下载地址。




如果你有好的技术文章想和大家分享,欢迎向我的公众号投稿,投稿具体细节请在公众号主页点击“投稿”菜单查看。


欢迎长按下图 -> 识别图中二维码或者扫一扫关注我的公众号: