Retrofit源码解析(一)主流程

398 阅读7分钟

简介

官网
Type-safe HTTP client for Android and Java by Square, Inc

Square公司为Android/Java开发的类型安全HTTP客户端。

  • Square公司:开源了很多组件库,做Android开发肯定是绕不过的。比如说现在通用的okhttp。
  • 类型安全:Java是类型安全的语言,但如果你的代码里面充斥着Object对象,那必定是不安全的。
  • Android HTTP客户端:封装了网络请求,Android开发网络库。


在使用Retrofit时候,我们还需要引入okHttp库。为啥有了okHttp库,我们还要用Retrofit呢?

简单网络请求示例

下面是三组示例代码,实现网络请求。 完整代码在这里

  • OKHttp
  • Retrofit
  • Retrofit+RxJava

OkHttp请求

val request = Request.Builder()
    .url("https://api.github.com/users/$userName").get().build()
val okHttpClient = OkHttpClient()
var call = okHttpClient.newCall(request)
call.enqueue(object : okhttp3.Callback {
    override fun onFailure(call: okhttp3.Call, e: IOException) {}
    override fun onResponse(call: okhttp3.Call, response: okhttp3.Response) {
        if (response.code == 200) {
            var responseString = response.body?.string();
            var user = Gson().fromJson(responseString, UserBean::class.java)
            Handler(Looper.getMainLooper()).post { tv.text = "OkHttp: \n ${user.url}" }
        }
    }
})

Retrofit

//定义一个接口类
public interface IService {
    //通过注解定义我们的网络请求方法。返回 retrofit2.call
    @GET("users/{user}")
    Call<UserBean> userInfo(@Path("user") String user);

    //返回 RxJava  Single
    @GET("users/{user}")
    Single<UserBean> userInfoRx(@Path("user") String user);
}

构建一个服务实例

//
private var service = Retrofit.Builder()
    .baseUrl("https://api.github.com/")
    .addConverterFactory(GsonConverterFactory.create())
    .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
    .build().create(IService::class.java)

Retrofit

service.userInfo(userName).enqueue(object : Callback<UserBean> {
    override fun onFailure(call: Call<UserBean>, t: Throwable) {}
    override fun onResponse(call: Call<UserBean>, response: Response<UserBean>) {
        tv.text = "retrofit: \n ${response.body()?.url}"
    }
})

Retrofit+RxJava:

service.userInfoRx(userName)
    .subscribeOn(Schedulers.io())
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(Consumer { tv.text = "retrofitAndRxJava: \n ${it.url }"  })

使用流程

流程图

对象在流程中传递。箭头是执行的操作。

截屏2020-04-28下午2.25.31.png
粗点的对标下。

OkHttpRetrofit
构建requestRequest(params)Method(params)
构建callokHttp3.callretrofit2.Call
执行calloKhttp3.Responseretrofit2.response
转换业务对象手动配置自动化
线程切换手动配置自动化
结束UIUI

Retrofit相对于OkHttp

Retrofit相对于OkHttp使用的不同:

request构建

OkHttp构建一个Request,需要手动配置Url/get/Post/RequestBody 等等。
Retrofit不需要我们构建Request,取而代之的是,定义一个method接口方法,利用method注解,提前定义好了部分参数,比如get/post/url等等。写程序,从某种意义上来说,我们一直在封装函数,调用函数。这个函数有输入和输出,而需要不用关心它的细节。
Retrofit正是通过这种方式,帮我们屏蔽了构建Request的细节,你不用关心这是Post还是get。只需要关心入参和出参。当然在我们实际应用OkHttp过程中,也会将OkHttp请求的细节封装在函数中。

Retrofit使用注解的高明之处在哪里?我们要去思考这个问题

  • 业务对象转换

网络请求返回的是一个JSON字符串。
OkHttp有一个显式转换的过程,而Retrofit内部实现了自动转换。我们猜测与addConverterFactory有关。

  • 线程切换

OkHttp的callback在工作线程执行,刷新UI需要切换到主线程,而Retrofit内部实现了自动切换线程。

  • 配置与请求分离

接口即为配置,将OkHttp请求中一串配置参数剥离开来,同时可以配置数据解析等额外功能。

Retrofit做的封装我们都可以做,但是通用性上就大打折扣。试想我们基于业务封装了一套函数。能复制给别人,能复制给别的App么,不能!假设封装一套要1人日,那如果有1万套呢?Retrofit提高了多少生产力?突然觉得Retrofit,开源精神很伟大,不是么。

Retrofit内部原理

Retrofit帮我们实现了OkHttp的部分过程,内部也必然是按照OkHttp的流程在走的。

源码流程图

对应第二简单示例。

截屏2020-04-28下午4.23.11.png

流程图注意点

retrofit2.OkHttpCall是分割点,整体来看分成了两部分: 

  • 通过Retrofit/HttpServiceMethod,解析Method注解,构建一个retrofit2.OkHttpCall

OkHttpCall包含了构建OkHttp3.Request和 ResponseConvert的必要参数。

  • OkHttpCall并不直接生成OkHttp3.Call,而是在OkHttpCall.equeue时生成。

OkHttpCall内部完成了OkHttp3.Call的构建/equeue/convert。ExecutorCallback实现了线程切换。 红色标记:红色部分即为Okhttp的使用流程。

过程源码解读

上面的流程图,我们省略了Retrofit初始化,这里其实是做了一系列自定义配置(baseUrl,factory,callbackExecutor...)。

1.构建retrofit2.OkHttpCall

Retrofit构造IService接口实例

分两步,第一步:构造Retrofit实例,一个配置or构造工厂

private var retrofit:Retrofit = Retrofit.Builder()
    .baseUrl("https://api.github.com/")
    .addConverterFactory(GsonConverterFactory.create())
    .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
    .build()
public final class Retrofit {
  
    //loadServiceMethod会用到,简单的ServiceMethod对象缓存
  private final Map<Method, ServiceMethod<?>> serviceMethodCache = 
        new ConcurrentHashMap<>();
  
  	//通常我们的子域名是固定的几个,
  	//这个baseUrl就很细节了,不用我们在定义IService时,写大量的重复域名前缀
  final HttpUrl baseUrl;
    
    //入参okHttp3.Request构建okHttp3.Call的工厂,OkHttpClient就是一个Call.Factory
  final okhttp3.Call.Factory callFactory;
    
    //converter,不仅response可以转,request也可以转。
    //不仅有GsonConverterFactory,
    //还有ProtoConverterFactory,SimpleXmlConverterFactory等等
  final List<Converter.Factory> converterFactories;
  
 	//不仅有Platform中定义的DefaultCallAdapterFactory,还有RxJava2CallAdapterFactory等等
  final List<CallAdapter.Factory> callAdapterFactories;
    
   	//Platform中定义为这个MainThreadExecutor。
    //其实就是封装Handler(Looper.getMainLooper())
    //在ExecutorCallbackCall用到了,切换主线程用的
  final @Nullable Executor callbackExecutor;
    //如果为true,则在创建IService动态代理时,把IService的所有Method转换成ServiceMethod
    //否则每个Method只有在第一次使用是转换成ServiceMethod。
  final boolean validateEagerly;
    
    //常用的Builder设计模式,构建Retrofit
  public static final class Builder {
      public Builder() {this(Platform.get()); }
      public Builder baseUrl(URL baseUrl)
      public Builder callFactory(okhttp3.Call.Factory factory)
      public Builder addConverterFactory(Converter.Factory factory)
      public Builder addCallAdapterFactory(CallAdapter.Factory factory)
      public Builder callbackExecutor(Executor executor)
      public Builder validateEagerly(boolean validateEagerly)
      public Retrofit build() 
  }
}

Retrofit的create方法,帮我们构建了一个ISerivce接口的动态代理实例。
调用接口方法实际都会走到loadServiceMethod(method).invoke(xxx)

private var service:IService = retrofit.create(IService::class.java)
//核心代码
class Retrofit{
  public <T> T create(final Class<T> service) {
    return (T) Proxy.newProxyInstance(
        service.getClassLoader(), new Class<?>[] { service },
        new InvocationHandler() {
            //当我们调用IService Proxy对象方法时,都会走到这个invoke中
          @Override public @Nullable Object invoke(Object proxy, Method method,
              @Nullable Object[] args) throws Throwable {
            return loadServiceMethod(method).invoke(args != null ? args : emptyArgs);
          }
        });
  }
  //省略了缓存的代码
  ServiceMethod<?> loadServiceMethod(Method method) {
    return ServiceMethod.parseAnnotations(this, method);
}

ServiceMethod<?> loadServiceMethod(Method method)

当我们调用IService.userInfo方法时,会进入InvocationHandler.invoke方法中,再进入ServiceMethod.parseAnnotations,得到一个CallAdapted

abstract class ServiceMethod<T> {
  static <T> ServiceMethod<T> parseAnnotations(Retrofit retrofit, Method method) {
      //还得去HttpServiceMethod里面,最后返回的是一个CallAdapted(继承了HttpServiceMethod)
    return HttpServiceMethod.parseAnnotations(retrofit, method, requestFactory);
  }
  //InvocationHandler.invoke中用到了ServiceMethod.invoke
  abstract @Nullable T invoke(Object[] args);
}

代码有点长,但是逻辑清楚的。

abstract class HttpServiceMethod<ResponseT, ReturnT> 
    extends ServiceMethod<ReturnT> {
  /**
   * 识别方法注解,构造可复用的服务方法(使用Http),因为使用了反射,最好一次构造,多次复用。
   * 这里我们省略了Kotlin相关代码,省略了构造方法
   */
  static <ResponseT, ReturnT> HttpServiceMethod<ResponseT, ReturnT> parseAnnotations(
      Retrofit retrofit, Method method, RequestFactory requestFactory) {
    Annotation[] annotations = method.getAnnotations();
     
      //1.CallAdapter作用在于将原始的响应Call<T>转换成你想要的类型。
      //例如当我们添加RxJava2CallAdapterFactory,同时定义了RxJava的Single<T> ,retrofit就会帮我们将Call<T>转换成Single<T>,
//默认返回DefaultCallAdapterFactory$CallAdapter内部实例
    CallAdapter<ResponseT, ReturnT> callAdapter = 
          retrofit.callAdapter(method.getGenericReturnType(), annotations);
     
      //2.从retrofit构建responseConverter,实际返回的GsonResponseBodyConverter。
    Type responseType = callAdapter.responseType();
    Converter<ResponseBody, ResponseT> responseConverter =
        retrofit.responseBodyConverter(responseType, annotations);
	 
	
      //3.脑容量不够大没关系,记住这些都是跟Retrofit配置那些Factory有关就好。先走主流程。
      //callFactory,默认就是我们的OKhttpClient
    return new CallAdapted<>(
        requestFactory, retrofit.callFactory, responseConverter, callAdapter);
   
  }

CallAdapted.invoke

invoke 做了两件事情 new OkHttpCall和 adapt

  • 1.new OkHttpCall很好理解了,你要构建okHttp3.call/解析数据就得需要这些啊,需要啥传啥。
  • 2.adapt实际是在DefaultCallAdapterFactory.$CallAdapter中将OkHttpCall套了一层ExecutorCallbackCall,方便用Executor切换到主线程。
@Override final @Nullable ReturnT invoke(Object[] args) {
   Call<ResponseT> call =  new OkHttpCall<>(requestFactory, args, callFactory, responseConverter);
   return adapt(call, args);
 }

到上面的流程为止,我们构建了一个retrofit2.OkHttpCall。它具备了以下能力的必要配置

  • 构建okhttp3.Call,
  • 转换okhttp3.Response,
  • 回调可以切换到主线程

2.OkHttpCall.equeue执行过程

//T是我们返回的业务数据类
//OkHttpCall的代码很简单,该配置的东西都HttpServiceMethod中弄好了。
final class OkHttpCall<T> implements Call<T> {
   
  private @Nullable okhttp3.Call rawCall;
    //HttpServiceMethod在构建OkHttpCall时传进来的。
  OkHttpCall(RequestFactory requestFactory, Object[] args,
      okhttp3.Call.Factory callFactory, Converter<ResponseBody, T> responseConverter);

  @Override public void enqueue(final Callback<T> callback) {
     //构建okhttp3.Call
    okhttp3.Call call= rawCall = callFactory.newCall(requestFactory.create(args));
      //加入请求队列
    call.enqueue(new okhttp3.Callback() {
      @Override public void onResponse(okhttp3.Call call, okhttp3.Response rawResponse) {
          //原始response转换成泛型Response
        Response<T> response = parseResponse(rawResponse);
         callback.onResponse(OkHttpCall.this, response);
      }
    });
  }

  Response<T> parseResponse(okhttp3.Response rawResponse) throws IOException {
    ResponseBody rawBody = rawResponse.body();

    rawResponse = rawResponse.newBuilder()
        .body(new NoContentResponseBody(rawBody.contentType(), rawBody.contentLength()))
        .build();

    ExceptionCatchingResponseBody catchingBody  = new ExceptionCatchingResponseBody(rawBody);
      //此时的responseConverter为GsonResponseBodyConverter
    T body = responseConverter.convert(catchingBody);
    return Response.success(body, rawResponse);
   
  }
}

ExecutorCallbackCall

如果是DefaultCallAdapterFactory$CallAdapter adapt出来的是ExecutorCallbackCall内部的callbackExecutor做线程切换。如果是Android,参考MainThreadExecutor
 ExecutorCallbackCall -> OkHttpCall -> okHttp3.Call

static final class ExecutorCallbackCall<T> implements Call<T> {
    final Executor callbackExecutor;
    final Call<T> delegate;

    ExecutorCallbackCall(Executor callbackExecutor, Call<T> delegate) {
      this.callbackExecutor = callbackExecutor;
      this.delegate = delegate;
    }

    @Override public void enqueue(final Callback<T> callback) {
      delegate.enqueue(new Callback<T>() {
        @Override public void onResponse(Call<T> call, final Response<T> response) {
          callbackExecutor.execute(new Runnable() {
            @Override public void run() {
                callback.onResponse(ExecutorCallbackCall.this, response);
            }
          });
        }
      });
    }
  }

RxJava2CallAdapter

还记得RxJava2CallAdapterFactory,此时OkHttpCall会被封装进 CallExecuteObservable。当发生订阅时,会调用call.execute().

final class RxJava2CallAdapter<R> implements CallAdapter<R, Object> {

  @Override public Object adapt(Call<R> call) {
    Observable<Response<R>> responseObservable =  new CallExecuteObservable<>(call);

    Observable<?> observable = new BodyObservable<>(responseObservable);
    if (isSingle) {
      return observable.singleOrError();
    }
  }
}
final class CallExecuteObservable<T> extends Observable<Response<T>> {
  private final Call<T> originalCall;

  CallExecuteObservable(Call<T> originalCall) {
    this.originalCall = originalCall;
  }

  @Override protected void subscribeActual(Observer<? super Response<T>> observer) {
    // Since Call is a one-shot type, clone it for each new observer.
    Call<T> call = originalCall.clone();
    Response<T> response = call.execute();
    observer.onNext(response);
  }
  ...
}

总结下来,我们发现主流程的核心类,就两个 HttpServiceMethod,OkHttpCall

  • HttpServiceMethod:构建OkHttpCall,传入所有后续请求需要的所有参数
  • OkHttpCall:构建和执行okHttp3.call,用您想要的方式将execute封装,完善其链路

例如:线程切换,reponse数据对象转换,RxJava响应式变换。

到这里主流程就分析完成了。

Factory视角

读完主流程之后,我有种万物皆可配的感觉。几乎每一步都可以做配置。
我们需要与RxJava结合,那就配一个RxJava2CallAdapterFactory
我们的返回数据可能是XML,那就配一个SimpleXmlConverterFactory
默认的OkHttpClient不满意了,可以新写一个OkHttpClient加各种Timeout限制/拦截器等等
request构建call,没有给我们配置的空间,但是也预留了代码,待开发
callbackExecutor,也提供了线程切换的能力。

启示

  • 动态代理,注解,反射,委托,builder,adapter,factory,面向接口编程,解耦。看都会,就是写不好。
  • 我们业务代码是对现实的抽象,而这个库是对抽象的抽象。
  • 开源共享在于解决共性问题。一个数学难题的解决?一个重复功能的提炼?
  • 为了减少重复造轮子,为了提高大家的生产力。