Retrofit2源码学习笔记(一)

作者:@荒井
所分析的源代码基于Retrofit 2.3.0版本。

一般我在刚接触一个库时,会先大致了解一下它解决什么问题,使用了什么技术等等,这之后我会动手写一个小例子并运行起来看看效果,那么首先我来写一个例子。

根据官网描述,使用Retrofit2时,是把HTTP请求写成Java接口形式,例如:

public interface ArticleService {
    @GET("users/{user}/articles")
    Call<ResponseBody> listArticles(@Path("user") String user);
}

然后,构建一个Retrofit对象,并使用它生成ArticleService接口的实现,例如:

Retrofit retrofit = new Retrofit.Builder()
    .baseUrl("article.sample.com")
    .build();
ArticleService service = retrofit.create(ArticleService.class);

接下来,使用生成的"service"对象,调用其中的方法得到一个Call<ResponseBody>类型对象"articles",代码如下:

Call<ResponseBody> articles = service.listArticles("arai");

最后我将这个请求加入队列,并等待返回结果回调:

articles.enqueue(new Callback<ResponseBody>() {
    @Override
    public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
        //成功。
    }

    @Override
    public void onFailure(Call<ResponseBody> call, Throwable t) {
        //失败。
    }
});

如果请求发送成功,即可在onResponse回调方法中拿到返回的数据,如果失败则会回调onFailure方法。这样就是一个比较完整和基本的Retrofit2使用例子了。为了更好地熟悉和使用Retrofit2这个库,我准备稍微深入地分析其源代码,那么这篇文章打算从Retrofit.create(Class<T> service)方法开始,一步一步揭开其神秘的面纱。由于这个方法的篇幅不算长,先大致看一下它的全貌:

public <T> T create(final Class<T> service) {
    /* 代码段 1 */
    Utils.validateServiceInterface(service);

    /* 代码段 2 */
    if (validateEagerly) {
        eagerlyValidateMethods(service);
    }

    /* 代码段 3 */
    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, @Nullable 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);
            }
            ServiceMethod<Object, Object> serviceMethod = (ServiceMethod<Object, Object>) loadServiceMethod(method);
            OkHttpCall<Object> okHttpCall = new OkHttpCall<>(serviceMethod, args);
            return serviceMethod.callAdapter.adapt(okHttpCall);
        }
    });
}

我把整个方法分为3个代码段,分为用代码段1、2、3注释。

代码段1非常简单,看名字就知道,验证service是否是一个interface,除此之外,如果点进方法查看,会发现它还限制了service不可再继承其它interface,否则也会抛出异常。

代码段2,根据validateEagerly的值决定是否进入if,这个validateEagerly是Retrofit类的一个布尔常量,还记得如何得到Retrofit类的实例吗,没错,用的是Retrofit的内部类Builder的build()方法来构造的,此处不贴代码,直接给出结论,validateEagerly的值为默认值false,这里不会进入if条件内执行。

那么怎样让其执行if内代码,它又做了什么呢?为了一探究竟,我在构建Retrofit对象时设validateEagerly的值为true:

Retrofit retrofit = new Retrofit.Builder()
    .baseUrl("article.sample.com")
    .validateEagerly(true)
    .build();

然后看看这个if括号里面的eagerlyValidateMethods(Class<?> service)方法:

private void eagerlyValidateMethods(Class<?> service) {
    Platform platform = Platform.get();
    for (Method method : service.getDeclaredMethods()) {
        if (!platform.isDefaultMethod(method)) {
            loadServiceMethod(method);
        }
    }
}

这个方法首先会获取当前的平台,Retrofit2中定义了2种特殊平台:Android和Java8,显然我所在的是Android平台。接下来遍历service中声明的方法,在这里发现Android.isDefaultMethod(Method method)是直接返回false,也就是会进入if块内,并对每一个method调用loadServiceMethod(Method method)方法。

代码段2说到这里,让我们先短暂地跳出来,回顾一下。使用Retrofit2时,开发者定义好代表网络请求的interface,然后使用Retrofit.create()方法来获取实现这个interface的对象,这里会遇到比如分析注解的操作,比如之前的例子中的listArticles方法就有注解@GET("users/{user}/articles"),这个过程是稍微慢一些的,Retrofit2为interface中的每一个method创建一个对应的ServiceMethod对象,保存这个过程中的一些信息,以便后面复用。那么根据validateEagerly这个名字,好像是要尽早执行这个操作的意思,此处暂时先记着有这样一回事,后面会搞明白的。

/* 代码段 3 */
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, @Nullable Object[] args) throws Throwable {
        // If the method is a method from Object then defer to normal invocation.
        /* 代码段 3.1 */
        if (method.getDeclaringClass() == Object.class) {
            return method.invoke(this, args);
        }
        /* 代码段 3.2 */
        if (platform.isDefaultMethod(method)) {
            return platform.invokeDefaultMethod(method, service, proxy, args);
        }
        /* 代码段 3.3 */
        ServiceMethod<Object, Object> serviceMethod = (ServiceMethod<Object, Object>) loadServiceMethod(method);
        /* 代码段 3.4 */
        OkHttpCall<Object> okHttpCall = new OkHttpCall<>(serviceMethod, args);
        /* 代码段 3.5 */
        return serviceMethod.callAdapter.adapt(okHttpCall);
    }
});

接下来看代码段3,这里用到了Java的动态代理,直接返回动态代理对象,根据Retrofit.create(final Class<T> service)方法的定义,它返回的是一个实现了Service接口的对象。现在我们重点关注Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)方法的第三个参数,当我在实际调用返回对象中的方法时,会触发这个InvocationHandler对象"h"中的invoke(Object proxy, Method method, Object[] args)方法,在最开始的Retrofit使用例子中已经得知,调用返回的是Call<ResponseBody>类的对象,那么我分析一下调用过程,为了方便分析,我又把它分成若干个注释标记的代码段。

代码段3.1非常简单,判断方法存在于用户定义的interface中还是Object类中,定义在Object类中的方法是不需要代理的,所以这里判断false才会往下面执行,如果是true则直接调用。

代码段3.2的判断方法在之前的代码段2处已经分析过,判断条件会返回false,这里会直接跳过。那么如果没有跳过,会调用直接调用interface中的default方法,这是在Java8中才加入的特性,即假如我在之前的例子ArticleService中加入一个default方法的话,会直接传入参数并执行这个方法。

代码段3.3,在这里果然又遇到了loadServiceMethod(Method method)方法,这正是在分析代码段2时没有分析完的那个点。之前我说过,Retrofit2会为interface中的每一个method建立一个ServiceMethod<?, ?>对象,并存储起来。这个对象中包含了对method的分析结果,比如它的返回值、它含有的注解等。loadServiceMethod(Method method)方法的逻辑是先从Retrofit.serviceMethodCache中拿取这个方法所对应的ServiceMethod对象,如果拿到就直接返回,拿不到则构建这个对象,并存储到Retrofit.serviceMethodCache中。

ServiceMethod对象的构建过程还是稍微有些复杂的,它会分析method的全部注解、返回值的合法性等,并保证所定义的method能提供一个网络请求所必须的信息,同时还会获取这个method的Converter和CallAdapter,这两个概念在Retrofit2中很重要,在最开始的例子中,我所定义的interface中的method,返回的类型是Call<ResponseBody>,ResponseBody即代表HTTP请求所返回的Body,通过解析它得到我们要的数据。为了减轻开发者的负担,Retrofit2提供了CallAdapter和Converter这两个概念,可以将Call和ResponseBody转为其它类型,这让我们十分受益,例如使用Retrofit2+Rxjava开发时,我们可以将Service中的方法像下面那样定义,就可以只关注拿到数据后怎么展示,而不用去解析ResponseBody,并且可以直接写出Rxjava式的代码。

public interface ArticleService {
    @GET("users/{user}/articles")
    Observable<ArticleData> listArticles(@Path("user") String user);
}

解释完loadServiceMethod(Method method)方法都做了什么,回到分析代码段2时留下的问题。是否设置validateEagerly的区别,在于设置validateEagerly为true的话,ServiceMethod对象会在Retrofit.create()时就构建,而默认情况false,会在第一次调用方法时构建,至于哪个好,我猜了一下,可能未必对,我觉得还要视情况而定,如果一个interface中的method是在APP运行过程中必然要执行的方法,可能提早构建好一些,如果并不是APP运行期间必须要调用的方法,那可能默认false好一些。我又特意查找了网上对这一块有描述的一些文章,觉得有一种说法比较靠谱,即设validateEagerly为true会使method定义的正确性在调用Retrofit.create()就得到验证,这样方便开发时进行测试。

代码段3.4,通过上一步骤得到的serviceMethod对象和传入invoke()方法的参数,建立okHttpCall对象,OkHttpCall<T>类实现了Call<T>接口,这个类主要的作用就是得到它之后,便可以将其加入请求队列,发送网络请求。

代码段3.5,刚才代码段3.3分析中已经说过,CallAdapter的作用是将Call<T>转换为其它类型,那么这里由于我没有任何设置,源码会使用默认的CallAdapter,不进行任何转换,所得到的还是Call<T>类型,并直接返回。

那么通过Retrofit.create()方法得到一个实现interface的动态代理对象的大致过程到这里就写完了,休息一下。

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

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,368评论 25 707
  • 前言 使用Retrofit已经一段时间了,这货挺好用的,还很特别,特别是使用接口来定义请求方式,这用法让我对它的源...
    带心情去旅行阅读 3,348评论 3 21
  • 一、什么是Retrofit A type-safe HTTP client for Android and Jav...
    andcoder阅读 742评论 2 3
  • 安卓开发领域中,很多重要的问题都有很好的开源解决方案,例如Square公司提供网络请求 OkHttp , Retr...
    aaron688阅读 1,900评论 1 20
  • 第一天在简书开始写文章,而且以后要坚持天天写! 今天想写一下我对司马懿的看法,其实由于《三国演义》贯穿了整个90后...
    路扬crow阅读 203评论 0 0