Android - 与 Retrofit 源码握手

image.png

Retrofit 是一个负责网络调度的框架,它负责对 Request 和 Response 的处理,底层的网络层是由 OkHttp 处理。

基本使用示例

Retrofit retrofit = new Retrofit.Builder()
                .baseUrl("https://api.github.com/")//主机
                .build();

GitHubService api = retrofit.create(GitHubService.class);
api.getRepos("valenti").enqueue(new Callback<List<Repo>>() {
    @Override
    public void onResponse(Call<List<Repo>> call, Response<List<Repo>> response) {
        Log.e(TAG, "success!" + gson.toJson(response.body()));
    }

    @Override
    public void onFailure(Call<List<Repo>> call, Throwable t) {
        Log.e(TAG, "failure!");
    }
});

enqueue 为异步函数,excute 为同步函数。

GithubServer 为接口,定义为:

public interface GitHubService {
    @GET("users/{user}/repos") //path
    Call<List<Repo>> getRepos(@Path("user")String user);
}

Repo 为对应 bean。

源码

Retrofit 的核心为 create 函数,GitHubService.class 这个参数传进经历了什么?一起来看看。

校验过程

内部首先经过 Utils.validateServiceInterface() 方法,这个方法内部逻辑为:

static <T> void validateServiceInterface(Class<T> service) {
    if (!service.isInterface()) {
      throw new IllegalArgumentException("API declarations must be interfaces.");
    }
    if (service.getInterfaces().length > 0) {
      throw new IllegalArgumentException("API interfaces must not extend other interfaces.");
    }
  }

首先验证该 service 是否为 Interface,service.getInterfaces().length > 0 用来判定该 Interface 是否继承其他 Interface。
该方法就是对 GitHubService.class 进行简单的结构校验。

接下来为 eagerlyValidateMethods() 函数,该函数的作用是对 GitHubService 的内部声明合法性进行验证,那么为什么需要做一层该配置呢?假如你进行了该配置,那么在你的 Interface(也就是 GitHubService)被创建的第一时间就把所有的验证都做完了,这很利于开发者的调试工作,但是不利于性能。

核心部分

真正的核心部分就是 return 后的代码,完整代码如下:

return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
    new InvocationHandler() {
        private final Platform platform = Platform.get();
        private final Object[] emptyArgs = new Object[0];
        @Override public Object invoke(Object proxy, Method method, @Nullable Object[] args)
            throws Throwable {
            if (method.getDeclaringClass() == Object.class){
              return method.invoke(this, args);
            }
            if (platform.isDefaultMethod(method)) {
              return platform.invokeDefaultMethod(method, service, proxy, args);
            }
            return loadServiceMethod(method).invoke(args != null ? args : emptyArgs);
    }
});

Proxy.newProxyInstance() 为一个动态代理,这个方法中有三个参数,第一个参数 service.getClassLoader(),表示创建任何类的时候都需要提供一个类加载器用于把类(GitHubService)加载进来,所以我们不需要对这个 ClassLoader 进行任何操作;第二个参数是我们提供的一系列 Interface,然后 newProxyInstance() 方法会把所有的 Interface 全部实现到一个对象里。
但是,它是如何将我们仅仅声明但未曾在任何地方实现的 api 接口给实现了?就是在第三个参数 new InvocationHandler() {...} 中的回调 invoke() 中实现的。
动态代理是为运行时生成的代理类,其模样大概为(借用 GitHubService 的例子):

public class GitHubService extends Proxy implements GitHubService {

    InvocationHandler handler = new InvocationHandler() {
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            ...
            return ...;
        }
    };
    @Override
    public Call<List<Repo>> getRepos() {
        try {
            Method method = GitHubService.class.getMethod("getRepos");
            return (Call<List<Repo>>) handler.invoke(this, method, args);
        } catch (Throwable e) {
            e.printStackTrace();
        }
        ...
        return ...;
    }
    ...
}

'...' 部分为其他实现,并且上述代码和动态代理反编译看到的动态代理类结构有偏差,只是大体意思相同。

到这里大概明白了动态代理的工作内容,首先创建一个 GitHubService 接口的实例对象,然后接口中的每一个方法也会实现,然后调用 handler 的 invoke 方法,将 getRepos() 这个方法信息传进去,这体现了 Retrofit 最核心的部分,Interface 中声明的 api 接口就是如此实现的。

也可看出,当调用 api 接口的时候 invoke 方法会被调用一次。
那么 invoke 方法中做了什么?

invoke() 的内部

invoke 方法中第一个判断体 method.getDeclaringClass() == Object.class 首先进行了一次 method(也就是例子中的 getRepo()) 的判断,判断该方法是在哪个类中声明的,假如是 toString()hashCode()... 方法的话不会对它进行任何操作,这并不是 Retrofit 关注的重点。

下面一个判断体 platform.isDefaultMethod(method)platform 为不同的系统平台,不同的系统平台有着不同的行为,isDefaultMethod()Java 8 新特性(众所周知在之前 Java 中的接口是不能直接实现的,必须经由实现该接口的类来实现,Java 8 后允许)。

前两层判断都是为了保证兼容性,不是 Interface(GitHubService)里声明的方法该怎么调用还怎么调用。

接下来 loadServiceMethod() 是 invoke 实际做的内容,getRepo 函数最终的返回为一个我们自己的 Call,那么 Retrofit 中的返回的 Call 是如何转换为我们自定义 Call 的?
源码中 CallAdapter 就是完成这个工作的,追寻下面的足迹得到了 CallAdapter 的对象:

image.png

nextCallAdapter() 函数中有这样一段代码:

CallAdapter<?, ?> adapter = callAdapterFactories.get(i).get(returnType, annotations, this);

callAdapterFactories 的生成为:

List<CallAdapter.Factory> callAdapterFactories = new ArrayList<>(this.callAdapterFactories);
callAdapterFactories.addAll(platform.defaultCallAdapterFactories(callbackExecutor));

看到了熟悉的身影 platform,上面说过不同的平台会有不同的操作行为,其实就是根据不同的平台环境生成对应的 callAdapterFactory,Android 下默认的 callAdapterFactory 为 ExecutorCallAdapterFactory

在 ExecutorCallAdapterFactory 中能返回一个 CallAdapter 对象:

@Override public @Nullable CallAdapter<?, ?> get(
      Type returnType, Annotation[] annotations, Retrofit retrofit) {
    if (getRawType(returnType) != Call.class) {
      return null;
    }
    final Type responseType = Utils.getCallResponseType(returnType);
    return new CallAdapter<Object, Call<?>>() {
      @Override public Type responseType() {
        return responseType;
    }

      @Override public Call<Object> adapt(Call<Object> call){
        return new ExecutorCallbackCall<>(callbackExecutor, call);
      }
   };
}

在这里完成了目标 Call 的转换,与其说转换更不如说是适配。 HttpServiceMethod 中的Type responseType 就是适配目标的 Call ,也就是 Call<List<Repo>>。

并且在 ExecutorCallAdapterFactory 中还发现了在使用过程中成功失败的回调:

@Override public void enqueue(final Callback<T> callback) {
      checkNotNull(callback, "callback == null");
      delegate.enqueue(new Callback<T>() {
        @Override public void onResponse(Call<T> call, final Response<T> response) {
          callbackExecutor.execute(new Runnable() {
            @Override public void run() {
              if (delegate.isCanceled()) {
               callback.onFailure(ExecutorCallbackCall.this, new IOException("Canceled"));
              } else {
                callback.onResponse(ExecutorCallbackCall.this, response);
              }
            }
          });
        }
        @Override public void onFailure(Call<T> call, final Throwable t) {
          callbackExecutor.execute(new Runnable() {
            @Override public void run() {
              callback.onFailure(ExecutorCallbackCall.this, t);
          }
       });
     }
  });
}

在这里有一个代理 callbackExector 是他去执行了请求的成功与失败,在 Android 环境下返回的是 MainThreadExecutor(),MainThreadExecutor 中的 excute() 做了代理,response 并没有直接在外部的 onResponse 和 onFailure 中处理,而是通过 excute() 交由 handler 去做,在这里将网络代码切换到了前台

static class MainThreadExecutor implements Executor {
    private final Handler handler = new Handler(Looper.getMainLooper());
    @Override public void execute(Runnable r) {
        handler.post(r);
    }
}

在这里整理下思路,callbackExecutor 交给工厂 platform.defaultCallAdapterFactory() 处理,这个工厂创建一个个 CallAdapter,他会为 Interface(例子中的 GitHubService) 中的每个 api(例子中的 getRepos())创建一个 CallAdapter,也就是如下对应关系:
每个 api 对应一个 Call,每个 Call 对应一个 CallAdapter,CallAdapter 进行了线程的切换,将后台线程切换到前台主线程,当然 CallAdapter 不仅仅有切换线程这一个作用,还能做 RxJava 的适配。

其他细节

Retrofit 结构简单,但是细节处理巧妙又复杂,很值得考究。

HttpServiceMethod 中的 Converter

Converter<ResponseBody, ResponseT> responseConverter =
        createResponseConverter(retrofit, method, responseType);

这句话的作用是什么?由于 Retrofit 的底层为 OkHttp,最终我们想要的结果是得到 Call<List<Repo>> 而不是 Call<ResponseBody>,转换的过程就是 Converter 来实现。Converter 除了将结果转换为具体的对象,还有有关 Request 相关的转换,假如 GitHubService 中 api 定义如下:

@GET("users/{user}repos")
Call<List<Repo>> getRepos(@Path("user") Integer user_id);

若参数的拼接需要将 Integer 转换成 String 才能进行,那么 Converter 可以完成这项工作。

或者上传图片:

@POST("users/{user}repos")
@Multipart
Call<User> uploadAvatar(@Path("user") Integer user_id, @Part("image")File file);

file 转换成 requestBody 的过程也能通过 Converter 处理。

方法注解的解读

RequestFactory 为一个专门负责 Request 处理的工厂,在调用 HttpServiceMethod 的构造方法的时候,创建 RequestFactory 工厂实例,通过私有函数 parseMethodAnnotation() 对 api 注解进行解析。

在这里有意思的是,在这里会判断方法用的是否规范,比如 api 为 GET 但是却又在里面加了 Body,它会报错提示。在这里可以看出,Retrofit 的使用对 HTTP 规范有更高的要求,同时能让开发者更简便的去使用 Retrofit。

参数注解的解读

同时 Request 的 Parameter 也是在 RequestFactory 中处理,调用路径为:


image.png

在这里可以看到解读每一个 parameter 的 annotation,如 Url、Path ... 解析完成后这些参数的 annotation 会被暂存起来,目的是为了创建目标 Call(例子中的 Call<List<Repo>>)用。

所以回到最初的 loadServiceMethod() 它的作用就是对 GitHubService 中定义的 api 进行解读罢。

enqueue() 的解读

其关键部分在于:

call = rawCall = createRawCall();

首先这个 call 是 Retrofit 中的 Call —— OkHttpCall,是外层的一个包装,真正进行网络请求的是 OkHttp,在这里得到的就是 OkHttp 的Call:

private okhttp3.Call createRawCall() throws IOException {
    okhttp3.Call call = callFactory.newCall(requestFactory.create(args));
    if (call == null) {
      throw new NullPointerException("Call.Factory returned null.");
    }
    return call;
  }

这也就是说,Retrofit 不做网络请求,它的请求层工作是 OkHttp 来完成,Retrofit 负责包装、解析、转换、适配等一些列操作。

注意:上文说的 OkHttp 的 Call 不是 OkHttpCall。

网络请求的工作为:

call.enqueue(new okhttp3.Callback() {
    @Override public void onResponse(okhttp3.Call call, okhttp3.Response rawResponse) {
    Response<T> response;
    try {
        response = parseResponse(rawResponse);
    } catch (Throwable e) {
        throwIfFatal(e);
        callFailure(e);
        return;
    }
    try {
        callback.onResponse(OkHttpCall.this, response);
    } catch (Throwable t) {
        t.printStackTrace();
    }
}

这个 call 为 OkHttp 的 Call,在这里调用 OkHttp Call 的 enqueue (),然后对 response 进行解析,然后交给 callback,这个 callback 可能是开发者传的,也可能是 callAdapter。

parseResponse()

在这个函数中,更加能体现 Retrofit 和 OkHttp 的关系,关于状态码的判断:

if (code < 200 || code >= 300) {
      try {
        ResponseBody bufferedBody = Utils.buffer(rawBody);
        return Response.error(bufferedBody, rawResponse);
      } finally {
        rawBody.close();
      }
}

除 2xx 全都报错,301、302 等的自动跳转 OkHttp 会处理,Retrofit 不会处理。

if (code == 204 || code == 205) {
      rawBody.close();
      return Response.success(null, rawResponse);
}

204、205 会返回一个空对象。

关于解析的操作在:

T body = responseConverter.convert(catchingBody);

如我们想要什么类型,它就解析成什么类型,如示例 GitHubService getRepos() 返回 Call 中的 List<Repo> 类型(是 List<Repo> 不是 Call<List<Repo>>)。

所以 enqueue() 内部的工作流程为:


image.png

关于 RxJava 的适配

首先:

implementation 'com.squareup.retrofit2:adapter-rxjava2:2.5.0'

然后:

Retrofit retrofit = new Retrofit.Builder()
                .baseUrl("your host")//主机
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .build();

这里添加的 Adapter 并不会影响前面:

@GET("users/{user}repos")
Call<List<Repo>> getRepos(@Path("user") String user);

这段代码的正常适配,若还有:

@GET("users/{user}repos")
Observable<List<Repo>> getReposRx(@Path("user") String user);

能够正确适配到 Observable 中,因为 Retrofit 内部有多个 Adapter,那再来贴一次 nextCallAdapter() 函数中的代码:

CallAdapter<?, ?> adapter = callAdapterFactories.get(i).get(returnType, annotations, this);

可发现 callAdapterFactories 为 List 型,即是多个 Adapter 并存的关系,而不是后来者替换的关系。

前面提到 CallAdapter 配合 callbackExecutor 进行了线程的切换,而用了 RxJava 的适配呢,则是另外一套实现,具体的实现逻辑在 RxJava2CallAdapteradapt() 函数当中。

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

推荐阅读更多精彩内容