浅析 Retrofit 2 源码

最近新上了一个项目,用到了 Retrofit 2 + RxJava 与服务器进行交互。使用了一段时间后,对于其 Retrofit 的简洁、灵活印象深刻。尤其好奇的是其定义一个 HTTP 请求的方式,仅仅通过一个接口定义加上几个注解就完成了。虽然能大概推测到,一定是通过什么方法 将接口方法和注解包含的信息转化成了一个 HTTP Request ,但是对于背后具体的实现还是不清楚。于是就抽空研究了一下源码,发现代码量不大,但是设计思想真是精妙,值得好好学习一下。

为了让讲解更加的具体,将 用一个项目中实际用到的接口作为示例 一步步的进行分析,看看 Retrofit 是如何进行一个 HTTP 请求的。当然了,因为实际的 HTTP
请求处理是依赖于 okhttp 的,所以这一部分就不深入讲解了,以后有时间再单独开一篇来讲 okhttp 吧。

0. Retrofit 项目结构

  • http包:定义各种 @Get , @Post 等注解;
  • 两个核心类:RetrofitMethodHandler
    Retrofit 的主要逻辑和配置基本上通过这两个类来完成,所以后面的分析也主要围绕着两个类展开。其他的类涉及到的时候再简单介绍
    Retrofit 项目结构

1. 示例接口

public interface AppApi {
     /**
     * 根据用户id返回用户基本信息
     * @param uid 用户id
     * @return
     */
    @GET("api/v1/user/{uid}/")
    Observable<User> getUser( @Path("uid") String uid );
}

这个接口功能非常简单,以用户id uid 作为参数发起请求,服务器会返回一个 json 字符串,这个字符串将被自动解析成一个 User 对象。当然前面提到用到了 RxJava,所以返回被转换成了一个可以产生 User 对象的 Observable,不过并不影响我们的分析。

2. 使用前的基本配置

使用 Retrofit 发起调用前的一些初始化配置如下。

  OkHttpClient httpClient = new OkHttpClient.Builder().build();
    
  Retrofit appRetrofit = new Retrofit.Builder()
          .client(httpClient)
          .baseUrl(BuildConfig.API_ENDPOINT)
          .addConverterFactory(GsonConverterFactory.create())
          .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
          .build();

发起一个 HTTP 请求

  AppApi appApi = appRetrofit.create(AppApi.class);

  Observable<User> observable = appApi.getUser("1234567");

  observable
          .subscribeOn(Schedulers.io())
          .observeOn(AndroidSchedulers.mainThread())
          .subscribe(new Action1<User>() {
              @Override
              public void call(User user) {
              //do something with user                    
          }
  });

上面这段代码的头两句背后的逻辑就是 Retrofit 的核心所在,接下来我们就一步步进行分析。

3. 创建动态代理对象

在上一节中,发起 HTTP 请求的第一句, 只调用了一个 Retrofit.create() 方法。

AppApi appApi = appRetrofit.create(AppApi.class);

Retrofit.create()方法的源码如下:

  public <T> T create(final Class<T> service) {
    Utils.validateServiceInterface(service);
    if (validateEagerly) {
      eagerlyValidateMethods(service);
    }
    return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
        new InvocationHandler() {
          private final Platform platform = Platform.get();

          @Override public Object invoke(Object proxy, Method method, Object... args)
              throws Throwable {
            // If the method is a method from Object then defer to normal invocation.
            if (method.getDeclaringClass() == Object.class) {
              return method.invoke(this, args);
            }
            if (platform.isDefaultMethod(method)) {
              return platform.invokeDefaultMethod(method, service, proxy, args);
            }
            return loadMethodHandler(method).invoke(args);
          }
        });  
  }

通过源码我们可以看到,这里实际的返回是通过
Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler invocationHandler)
构造出来的一个 动态代理对象。这个方法前两个参数没什么好书偶的,第一个参数指定加载代理对象的 ClassLoader, 第二个参数就是需要被动态代理的接口数组,重点是第三个参数,它是一个 InvocationHandler 接口,需要实现它的 invoke() 方法。当通过动态代理对象调用接口的方法时,实际上最终就会调用 invoke() 方法。

关于 动态代理 的详细介绍可以参考这篇文章。这里引用其中关于动态代理的一个概述:

Java 动态代理机制的出现,使得 Java 开发人员不用手工编写代理类,只要简单地指定一组接口及委托类对象,便能动态地获得代理类。代理类会负责将所有的方法调用分派到委托对象上反射执行,在分派执行的过程中,开发人员还可以按需调整委托类对象及其功能,这是一套非常灵活有弹性的代理框架。

如果一时还无法理解动态代理的概念,那么现在只需要记住两点:

  • AppApi appApi = appRetrofit.create(AppApi.class); 这里的 appApi 已经是一个动态代理对象;
  • appApi.getUser("1234567"); 动态代理对象调用的方法,最终都会去到 InvocationHandlerinvoke() 方法里。

那么再回到 Retrofit.create() 方法,我们重点要关注的就是 InvocationHandler.invoke() 方法的实现。前面两个 if 判断可以直接略过,关键就在最后一句 loadMethodHandler(method).invoke(args)
loadMethodHandler() 方法的源码如下:

  MethodHandler loadMethodHandler(Method method) {
    MethodHandler handler;
    synchronized (methodHandlerCache) {
      handler = methodHandlerCache.get(method);
      if (handler == null) {
        handler = MethodHandler.create(this, method);
        methodHandlerCache.put(method, handler);
      }
    }
    return handler;
  }

这个方法就是为我们的接口中的每一个方法生成一个 MethodHandlermethodHandlerCache 就是一个用来缓存 MethodHandler 对象的 Map,对于已经调用过的 method 直接从 methodHandlerCache 中取。
可以看到关键是 MethodHandler.create(this, method) 这句,生成了MethodHandler 对象。接下来就来分析 MethodHandler 的源码。

4. MethodHandler

首先要了解下 MethodHandler 类包含的几个属性:

  • okhttp3.Call.Factory callFactory:
    okhttp 库中定义的接口,用来获得一个 okhttp.Call 对象。callFactory 在默认情况下被初始化为一个 OkHttpClient;
  • RequestFactory requestFactory:
    包含一个 create() 方法,根据各种参数,构造一个 okhttp.Request
  • CallAdapter<?> callAdapter:
    Retrofit 库中定义的接口,用来把一个 okhttp.Call 对象转化为其他类型。比如Retrofit.Builder.addCallAdapterFactory(RxJavaCallAdapterFactory.create()) 就配置了一个RxJavaCallAdapterFactory, 把一个 Call 转换为 Observable;
  • Converter<ResponseBody, ?> responseConverter:
    Retrofit 库中定义的接口,用来解析 HTTP 响应和请求。Retrofit.Builder.addConverterFactory(GsonConverterFactory.create()) 就配置了一个GsonConverterFactory,可以把一个 json 格式的字符串响应转化为相应的对象。默认情况下会配置一个 BuiltInConverters ,只是简单的转换为字符串。

介绍完几个关键属性,再来看 MethodHandler.create(Retrofit retrofit, Method method)方法:

1.   static MethodHandler create(Retrofit retrofit, Method method) {
2.     CallAdapter<?> callAdapter = createCallAdapter(method, retrofit);
3.     Type responseType = callAdapter.responseType();
4.     if (responseType == Response.class || responseType == okhttp3.Response.class) {
5.       throw Utils.methodError(method, "'"
6.           + Types.getRawType(responseType).getName()
7.           + "' is not a valid response body type. Did you mean ResponseBody?");
8.     }
9.     Converter<ResponseBody, ?> responseConverter = createResponseConverter(method, retrofit, responseType);
10.    RequestFactory requestFactory = RequestFactoryParser.parse(method, responseType, retrofit);
11.    return new MethodHandler(retrofit.callFactory(), requestFactory, callAdapter, responseConverter);
12.  }

可以发现就是根据传入的 retrofit 参数 和 method 参数来完成了前面提到的几个属性的初始化, nothing special. 唯一需要注意的是 第10行:

RequestFactory requestFactory = RequestFactoryParser.parse(method, responseType, retrofit);```
这里的 `RequestFactoryParser` 就是用来根据方法的注解、方法的参数等来解析出构造 `Request` 需要的信息,比如请求头部,请求方法,请求参数等。比较简单,就不深入了。

到这里,回顾一下我们的调用链:
  1. Retrofit.create() 构造动态代理对象;(` AppApi appApi = appRetrofit.create(AppApi.class);`)
  2. 动态代理对象调用方法 (`appApi.getUser("1234567");`)
  3. 方法调用实际被转化为 `InvocationHandler.invoke()`
  4. `loadMethodHandler()` 构造一个 `MethodHandler`

现在,离最终的调用只剩最后一步了 就是 `MethodHandler.invoke()` ,其实这个方法只有一句话...
```java
  Object invoke(Object... args) {
    return callAdapter.adapt(new OkHttpCall<>(callFactory, requestFactory, args, responseConverter));
  }

在这里终于见到了 HTTP 调用的真身 OkHttpCall, 而 callAdapter 对于我们的例子来说就是
RxJavaCallAdapterFactory.ResponseCallAdapter, 其定义的 adapt() 方法会把 OkHttpCall 转化为 Observable,这点本节开头已经讲过。

至此,一个从定义API接口到生成API请求的完整流程就走完了。不过要注意下,这里实际上还没有发起 HTTP 请求,只是准备好了发起一个 HTTP 请求所需要的一切。实际的请求在调用 OkHttpCall.execute() 或 enqueue() 的时候才会发起 ( 对于 Observable 类型的,是在Observable.subscribe() 的时候才发起 )

5. 结语

通过上面的分析可以看到,动态代理是 Retrofit 的核心所在,大量使用 Factory 模式使得 Retrofit 具备了模块化,可灵活配置的能力,而通过注解定义 HTTP 请求,使得代码可读性变得更高。真的是一旦上手,别无所求啊!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,445评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,889评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,047评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,760评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,745评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,638评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,011评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,669评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,923评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,655评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,740评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,406评论 4 320
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,995评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,961评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,023评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,483评论 2 342

推荐阅读更多精彩内容