Retrofit原理

一、前言

Retrofit是基于OkHttp封装的一个框架,使其更加方便使用。目前也是使用较多的网络框架,这里对其原理进行简单记录。这里需要注意的是Retrofit框架只是对OkHttp的二次封装,实际的网络请求依然还是用OkHttp进行的。因为OkHttp的网络请求只处理网络请求,对于实际开发中涉及的其余逻辑并不关心,比如线程切换,数据解析等等。另外使用Retrofit还可以对api的接口进行良好的管理,因此在此基础上研发了Retrofit框架。

二、简单使用

​ 这里简单记录一个使用例子,更多的例子可以查看官方文档和demo。

添加依赖

    implementation 'com.squareup.okhttp3:okhttp:4.9.3'
    implementation 'com.squareup.retrofit2:retrofit:2.9.0'
    implementation 'com.squareup.retrofit2:converter-scalars:2.9.0'
    implementation 'com.squareup.retrofit2:converter-gson:2.9.0'

使用方式:

interface ServerApi {
    @POST("s?cl=3&tn=baidutop10&fr=top1000&wd=战火中的中国驻乌克兰大使馆&rsv_idx=2&rsv_dl=fyb_n_homepage&sa=fyb_n_homepage&hisfilter=1")
    @FormUrlEncoded
    fun login(@Field("id") userId: String): Call<String>
  
    @GET("s?cl=3&tn=baidutop10&fr=top1000&wd=战火中的中国驻乌克兰大使馆&rsv_idx=2&rsv_dl=fyb_n_homepage&sa=fyb_n_homepage&hisfilter=1")
    fun loginObserver(): Observable<String>
}
class MainActivity : AppCompatActivity() {  
  private val httpUrl = "http://www.baidu.com"
	private fun startRetrofitNet(){
        val retrofit = Retrofit.Builder()
            .baseUrl(httpUrl)
            .addConverterFactory(ScalarsConverterFactory.create())//添加gson解析
            .addConverterFactory(GsonConverterFactory.create())//添加字符串解析
      			//.addCallAdapterFactory() 添加适配器转换
            .build()
        val serverApi = retrofit.create(ServerApi::class.java)
        val loginCall = serverApi.login("123")
//        loginCall.execute()//同步方法
        loginCall.enqueue(object : Callback<String>{
            override fun onResponse(call: Call<String>, response: Response<String>) {
                val result = response.body()
                Log.e("YM--->","获取的网络内容:${result}")
                Toast.makeText(this@MainActivity,"--->",Toast.LENGTH_SHORT).show()
            }

            override fun onFailure(call: Call<String>, t: Throwable) {
            }
        })
    }
}

三、整体结构

​ 从上文的使用方式可以看到,Retrofit的使用方式和OkHttp有很多不同的地方:

  1. 网络请求采用RESTful风格和注解的方式进行配置。这种类似于配置文件式的写法使网络请求更好的维护
  2. 在进行网络请求中不像OkHttp那样涉及到其他部分,比如Request。这里都可以使用Retrofit创建的对象进行链式调用进行请求。这是一种基于装饰模式的写法(跟建造者模式不是一回事,区别在于一个是根据多个参数不一样进行动态配置)。
  3. 在创建过程中可以通过配置ConverterFactory进行数据转换。因为有时候传入的数据格式不支持,比如传入一个对象,这时候可以使用转换器转换成字符串,或者将一个字符串在返回的时候转换为一个对象。
  4. 在创建对象的时候通过配置CallAdapterFactoryOkHttpCall对象进行适配。在OkHttpCall是用来对RequestResponse进行处理的。这里可以看上述接口定义了两个不同的返回值,一个是Call<String>,另外一个是RxJava的Observable<String>。使用RxJava的话就可以调用不同的特性,这里就是使用CallAdapterFactory进行转换的。也可以转换成kotlin这些使用方式。Retrofit通过重新处理Call将返回值根据实现的不同的CallAdapterFactory做不同的转换,提高了强大的拓展性。
  5. 可以看到上文的响应值里面直接输出了Toast。要知道请求是子线程的,Toast显示是在主线程的,所以这里自己做了线程切换。也是一个很大的不同点。这个线程切换是在Retrofit默认的DefaultCallAdapterFactory中做的,其它适配器里面没写的话是没有线程切换的。
  6. 还有一个重要的不同点从使用上看的话不好直接看出来,那就是定义接口的实例对象都被缓存起来了,防止多次调用同一个对象都去创建新的接口对象,其它的对象也有类似的操作。这里减少了部分内存占用,提高了部分内存性能(因为这个缓存是定义在Retrofit类里面的,所以实例对象不一样的话等于没有缓存。)。

上面就是跟OkHttp的一些区别,那么对于Retrofit来说,主要有以下几部分组成:

  • Retrofit
  • ConverterFactory
  • CallAdapterFactory
  • Call<T>
  • 定义的网络请求接口类

对于他们的主要功能上文已经解释过了,这里就不再进行解释,下面从最基本的网络请求来记录Retrofit的运行流程。

四、运行流程

​ 使用Retrofit的时候首先要通过建造者模式创建一个实例对象,最终实例对象还是调用的Retrofit的构造函数,所以这里看下构造函数即可知道有哪些参数:

  Retrofit(
      okhttp3.Call.Factory callFactory,
      HttpUrl baseUrl,
      List<Converter.Factory> converterFactories,
      List<CallAdapter.Factory> callAdapterFactories,
      @Nullable Executor callbackExecutor,
      boolean validateEagerly) {
    this.callFactory = callFactory;
    this.baseUrl = baseUrl;
    this.converterFactories = converterFactories; // Copy+unmodifiable at call site.
    this.callAdapterFactories = callAdapterFactories; // Copy+unmodifiable at call site.
    this.callbackExecutor = callbackExecutor;
    this.validateEagerly = validateEagerly;
  }

这里简单说下这几个参数的含义:

  • callFactory: 这个是okhttp的一个接口,OkHttpClient就是实现了这个接口,所以可以传入自定义的OkHttpClient
  • baseUrl:故名思义,就是一个网络请求的基础域名
  • converterFactories:数据转换器集合
  • callAdapterFactories: 适配器集合
  • callbackExecutor: 一个用来调度返回值的回调的线程池,默认是主线程
  • validateEagerly: 这个用的不多,源码的意思是检查配置是否有效。通过源码知道定义的接口类是通过动态代理实现具体类的,但是里面的接口需要在调用的时候才进行检查,而这个参数的配置会提前进行检查,即不通过动态代理就可以执行。

当创建完Retrofit的实例化对象后,需要通过Retrofit::create(final Class<T> service)来创建接口的实例化对象。这里看下这个对象是如何创建的。

public final class Retrofit {  
  ... 
  private final Map<Method, ServiceMethod<?>> serviceMethodCache = new ConcurrentHashMap<>();
  ...
	public <T> T create(final Class<T> service) {
    validateServiceInterface(service);
    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 @Nullable 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);
                }
                args = args != null ? args : emptyArgs;
                return platform.isDefaultMethod(method)
                    ? platform.invokeDefaultMethod(method, service, proxy, args)
                    : loadServiceMethod(method).invoke(args);
              }
            });
  }
  ...
    ServiceMethod<?> loadServiceMethod(Method method) {
    ServiceMethod<?> result = serviceMethodCache.get(method);
    if (result != null) return result;

    synchronized (serviceMethodCache) {
      result = serviceMethodCache.get(method);
      if (result == null) {
        result = ServiceMethod.parseAnnotations(this, method);
        serviceMethodCache.put(method, result);
      }
    }
    return result;
  }
  ...
}

这里可以看到是使用动态代理的方式创建的,如果是系统默认函数就执行默认函数,如果是自己写的,就执行ServiceMethod<?> loadServiceMethod(Method method)函数。然后可以看到通过ServiceMethod.parseAnnotations(this, method);得到结果,然后添加进缓存里面去。这里看下这个写法:

abstract class ServiceMethod<T> {
  static <T> ServiceMethod<T> parseAnnotations(Retrofit retrofit, Method method) {
    RequestFactory requestFactory = RequestFactory.parseAnnotations(retrofit, method);

    Type returnType = method.getGenericReturnType();
    if (Utils.hasUnresolvableType(returnType)) {
      throw methodError(
          method,
          "Method return type must not include a type variable or wildcard: %s",
          returnType);
    }
    if (returnType == void.class) {
      throw methodError(method, "Service methods cannot return void.");
    }

    return HttpServiceMethod.parseAnnotations(retrofit, method, requestFactory);
  }

  abstract @Nullable T invoke(Object[] args);
}

这里拿到相关方法后进行解析注解然后得到相对应的实体,然后在动态代理里面调用loadServiceMethod(method).invoke(args);invoke(args)函数就是这里面的函数。看下具体实现。

abstract class HttpServiceMethod<ResponseT, ReturnT> extends ServiceMethod<ReturnT> {
	...
   @Override
  final @Nullable ReturnT invoke(Object[] args) {
    Call<ResponseT> call = new OkHttpCall<>(requestFactory, args, callFactory, responseConverter);
    return adapt(call, args);
  }
  ...
}

通过这里就知道在具体实现的适配器里面将数据返回,这个数据结构类型由具体的适配器进行构造。默认的就返回默认的Call对象,RxJava就返回Observer<T>对象

五、参考链接

  1. Retrofit
  2. 设计模式-代理模式:
  3. OkHttp原理
  4. restful接口和普通接口有啥区别_RESTful接口
  5. Retrofit 2.0中的validateEagerly()方法(beta 2)

版权声明:本文为Mr_Tony原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。