草庐IT

读懂 Retrofit 原理~

Drew_MyINTYRE 2023-09-24 原文

什么是 REST ful API?

一句话概括 REST ful API:在我们使用 HTTP 协议做数据传输时应当遵守 HTTP 的规矩,包括请求方法、资源类型、Uri 格式等等..

为什么将请求设置为(接口+注解)形式?

迪米特法则:也称之为最小知道原则,即模块之间尽量减少不必要的依赖,即降低模块间的耦合性。

门面模式:基于迪米特法则拓展出来的一种设计模式,旨在将复杂的模块/系统访问入口控制的更加单一。举个例子:现要做一个获取图片功能,优先从本地缓存获取,没有缓存从网络获取随后再加入到本地缓存,假如不做任何处理,那每获取一张图片都要写一遍缓存逻辑,写的越多出错的可能就越高,其实调用者只是想获取一张图片而已,具体如何获取他不需要关心。此时可以通过门面模式将缓存功能做一个封装,只暴露出一个获取图片入口,这样调用者使用起来更加方便而且安全性更高。其实函数式编程也是门面模式的产物。

为什么通过门面模式设计 ApiService

Retrofit 做一次请求大致流程如下:

interface ApiService {
    /**
     * 获取首页数据
     */
    @GET("/article/list/{page}/json")
    suspend fun getHomeList(@Path("page") pageNo: Int)
    : ApiResponse<ArticleBean>
}

//构建Retrofit
val retrofit = Retrofit.Builder().build()

//创建ApiService实例
val apiService =retrofit.create(ApiService::class.java)

//发起请求(这里用的是suspend会自动发起请求,Java中可通过返回的call请求)
apiService.getHomeList(1)

我们都知道 Retrofit 只不过是对 OkHttp 做了封装。如果直接使用 OkHttp,当在构造 Request 时要做很多繁琐的工作,最要命的是 Request 可能在多处被构造(ViewModel、Repository...),写的越分散出错时排查的难度就越高。而 Retrofit 通过注解的形式将 Request 需要的必要信息全依附在方法上(还是个抽象方法,尽量撇除一切多余信息),作为使用者只需要调用对应方法即可实现请求。至于如何解析、构造、发起请求 Retrofit 内部会做处理,调用者不想也不需要知道,所以 Retrofit 通过门面模式帮调用者屏蔽了一些无用信息,只暴露出唯一入口,让调用者更专注于业务开发。像我们常用的 Room、GreenDao 也使用了这种模式。

动态代理其实不是工具

Retrofit 构建如下所示:

Retrofit.Builder()
    .client(okHttpClient)
    .addConverterFactory(GsonConverterFactory.create())
    .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
    .baseUrl(ApiConstants.BASE_URL)
    .build()

很典型的构建者模式,可以配置 OkHttpGsonRxJava 等等,最后通过build() 做构建操作,跟一下 build() 代码:

# Retrofit.class

public Retrofit build() {

        // 1. CallAdapter 工厂集合
        List<CallAdapter.Factory> callAdapterFactories = new ArrayList<>(this.callAdapterFactories);
        callAdapterFactories.addAll(platform.defaultCallAdapterFactories(callbackExecutor));

        // 2. Converter 工厂集合
        List<Converter.Factory> converterFactories =
                new ArrayList<>(
                        1 + this.converterFactories.size() + platform.defaultConverterFactoriesSize());
        converterFactories.add(new BuiltInConverters());
        converterFactories.addAll(this.converterFactories);
        converterFactories.addAll(platform.defaultConverterFactories());

        return new Retrofit(
              callFactory,
                baseUrl,
                unmodifiableList(converterFactories),
                unmodifiableList(callAdapterFactories),
                callbackExecutor,
                validateEagerly);
    }

什么是动态代理?

动态代理是可以在运行期动态创建某个 interface 的实例,我们通过 Proxy.newProxyInstance 产生的代理类,当调用接口的任何方法时,都会被InvocationHandler#invoke 方法拦截,同时,在这个方法中可以拿到所传入的参数等,依照参数值再做相应的处理。

每次 CRUD 都会手动做一次上报操作,这显然是模版代码,如何解决?下面来看动态代理:

interface ToDo {
    fun buyHouse()
}

/**
 * 中介
 */
class Middlemen(private val any: Any) : InvocationHandler {
    override fun invoke(proxy: Any?, method: Method?, args: Array<out Any>?): Any? {
        return method?.invoke(any, *(args ?: arrayOfNulls<Any>(0)))
    }
}

/**
 * 买家
 */
class Buyer : ToDo {
    override fun buyHouse() {
        print("中介,请帮我买房子")
    }
}

// 最后利用代理对象实现购房的动作:
fun main(args: Array<String>) {
        val toDo: ToDo = Buyer()
        val dynamicMid = Proxy.newProxyInstance(
            toDo.javaClass.classLoader, toDo.javaClass.interfaces,
            Middlemen(toDo)
        ) as ToDo
        dynamicMid.buyHouse()
    }

动态代理最主要的部分在于代理对象实现 InvocationHandler,并重写 invoke 方法。 当代理对象代理了委托者的要求,不管要求有多少,当代理执行时,都会走进 invoke() 方法中。这是重点,圈起来后面要考。

动态代理获取 ApiService

# Retrofit.class

  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 (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);
              }
            });
  }

invoke() 这是一个代理方法,调用 ApiService中的任一方法并执行,其中参数 methodargs 代表 ApiService 对应的方法和参数。返回值中有一个isDefaultMethod,这里如果是 Java8 的默认方法直接执行,毕竟我们只需要代理 ApiService 中方法即可。经过反复筛选最后重任落在了loadServiceMethod,这也是 Retrofit 中最核心的一个方法,下面我们来跟一下:

loadServiceMethod(method) 它主要是将网络请求方法中的信息进行初步的处理,我们在创建 api service 具体接口时,会加上注解( @GET@POST@PUT...),参数( @Path@Query ... )等,该方法就是对接口中的注解、参数等进行解析,解析接口后又生成了一个 RequestFactory 请求工厂对象,并且利用这个 RequestFactory 对象创建了一个 CallAdapter

CallAdapter: 适配器,我们默认定义 API Service 方法的返回值为 Call 类型,但是有时候会自定义返回类型,例如和 RxJava 相结合,返回Observable 或者 Single 类型的时候应该怎么处理?CallAdapter 的作用就是帮助开发者去适配这些返回类型,你定义了什么类型的数据,就可以通过CallAdapter#adapt 进行返回。

# Retrofit.class
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 对象是通过其静态方法 parseAnnotations 实现的,再跟一下这个方法:

# HttpServiceMethod.class

static <ResponseT, ReturnT> HttpServiceMethod<ResponseT, ReturnT> parseAnnotations(
            Retrofit retrofit, Method method, RequestFactory requestFactory) {
        boolean isKotlinSuspendFunction = requestFactory.isKotlinSuspendFunction;
        boolean continuationWantsResponse = false;
        boolean continuationBodyNullable = false;

        Annotation[] annotations = method.getAnnotations();
        Type adapterType;
        //1.获取adapterType,默认为method返回值类型
        if (isKotlinSuspendFunction) {
            Type[] parameterTypes = method.getGenericParameterTypes();
            Type responseType =
                    Utils.getParameterLowerBound(
                            0, (ParameterizedType) parameterTypes[parameterTypes.length - 1]);
            if (getRawType(responseType) == Response.class && responseType instanceof ParameterizedType) {
                // Unwrap the actual body type from Response<T>.
                responseType = Utils.getParameterUpperBound(0, (ParameterizedType) responseType);
                continuationWantsResponse = true;
            } else {
            }
            adapterType = new Utils.ParameterizedTypeImpl(null, Call.class, responseType);
            annotations = SkipCallbackExecutorImpl.ensurePresent(annotations);
        } else {
            adapterType = method.getGenericReturnType();
        }
        //2.创建CallAdapter
        CallAdapter<ResponseT, ReturnT> callAdapter =
                createCallAdapter(retrofit, method, adapterType, annotations);
        Type responseType = callAdapter.responseType();
        //3.创建responseConverter
        Converter<ResponseBody, ResponseT> responseConverter =
                createResponseConverter(retrofit, method, responseType);

        okhttp3.Call.Factory callFactory = retrofit.callFactory;
        //4.创建HttpServiceMethod类型具体实例
        if (!isKotlinSuspendFunction) {
            return new HttpServiceMethod.CallAdapted<>(requestFactory, callFactory, responseConverter, callAdapter);
        }
        //兼容kotlin suspend方法
        else if (continuationWantsResponse) {
            //noinspection unchecked Kotlin compiler guarantees ReturnT to be Object.
            return (HttpServiceMethod<ResponseT, ReturnT>)
                    new HttpServiceMethod.SuspendForResponse<>(
                            requestFactory,
                            callFactory,
                            responseConverter,
                            (CallAdapter<ResponseT, Call<ResponseT>>) callAdapter);
        } else {
            //noinspection unchecked Kotlin compiler guarantees ReturnT to be Object.
            return (HttpServiceMethod<ResponseT, ReturnT>)
                    new HttpServiceMethod.SuspendForBody<>(
                            requestFactory,
                            callFactory,
                            responseConverter,
                            (CallAdapter<ResponseT, Call<ResponseT>>) callAdapter,
                            continuationBodyNullable);
        }
    }
# ServiceMethod.class
 static <T> ServiceMethod<T> parseAnnotations(Retrofit retrofit, Method method) {
        // ⚠️
        RequestFactory requestFactory =
            RequestFactory.parseAnnotations(retrofit, method);
        Type returnType = method.getGenericReturnType();
        ...
        // ⚠️
        return HttpServiceMethod.parseAnnotations(retrofit,
                method, requestFactory);
    }
# HttpServiceMethod.class
@Override
  final @Nullable ReturnT invoke(Object[] args) {
    Call<ResponseT> call = new OkHttpCall<>(requestFactory, args, callFactory, responseConverter);
    return adapt(call, args);
  }

OkHttpCall 其实是对 OkHttp 中的 realCall 进行了一层包装,realCall 是如何创建的?

先创建 OkHttpClientRequest 对象,以 Request 来创建一个 RealCall 对象,利用它执行异步 enqueue 或者同步 execute 操作将请求发送出去,并监听请求失败或者成功的反馈 Callback

val okHttpClient = OkHttpClient()
val request: Request = Request.Builder()
    .url("https://cn.bing.com/")
    .build()

okHttpClient.newCall(request).enqueue(object : Callback{
    override fun onFailure(call: Call, e: IOException) {
    }

    override fun onResponse(call: Call, response: Response) {
    }
})

这里有三个主要的类需要说明一下:OkHttpClientRequest 以及 RealCall

  • OkHttpClient: 相当于配置中⼼,可用于发送 HTTP 请求并读取其响应。它的配置有很多,例如 connectTimeout:建⽴连接(TCP 或 TLS)的超时时间,readTimeout :发起请求到读到响应数据的超时时间,Dispatcher:调度器,⽤于调度后台发起的⽹络请求,等等。还有其他配置可查看源码。

  • Request: 一个主要设置网络请求 Url、请求方法(GET、POST......)、请求头、请求 body 的请求类。

  • RealCallRealCall 是由 newCall(Request) 方法返回,是 OkHttp 执行请求最核心的一个类之一,用作连接 OkHttp 的应用程序层和网络层,也就是将 OkHttpClientRequest 结合起来,发起异步和同步请求。

loadServiceMethod(method).invoke(args);, loadServiceMethod(method) 返回的是 HttpServiceMethod, invoke() 创建了一个 OkHttpCall 实例,它内部其实就是对 OkHttp 的一系列操作。

我们再来看看 OkHttpCall 中网络请求的细节:

  @Override
  public void enqueue(final Callback<T> callback) {
    Objects.requireNonNull(callback, "callback == null");

    okhttp3.Call call;
    synchronized (this) {
      ...
      if (call == null && failure == null) {
        try {
          call = rawCall = createRawCall();
        }
      }
    ...
    call.enqueue(
        new okhttp3.Callback() {
          @Override
          public void onResponse(okhttp3.Call call, okhttp3.Response rawResponse) {
            Response<T> response;
            try {
              //解析请求返回值
              response = parseResponse(rawResponse);
            }
              ...
            try {
              callback.onResponse(OkHttpCall.this, response);
            }

          @Override
          public void onFailure(okhttp3.Call call, IOException e) {
            callFailure(e);
          }

          private void callFailure(Throwable e) {
            try {
              callback.onFailure(OkHttpCall.this, e);
            }
          }
        });
  }

分析上面的源码:

1,内部首先调用了一个 createRawCall() 方法,创建 rawCall,这个 rawCall 其实指代的就是 Okhttp3 的 call,也就是 OkHttp 进行网络请求调用的一个调度器;

2,创建好 OkHttp 的 call 后,就开始调用 enqueue 进行异步请求,发现在异步请求内响应的回调属于 okhttp3.Callback,所返回来的结果,也都是 okhttp3.Response,到这里就可以大概知道了,retrofit 的网络请求其实还是由 OkHttp 来实现。

3,okhttp3.Response 这个响应不方便开发者直接使用,所以 retrofit 在收到结果后,又对响应结果进行新一轮的解析 response = parseResponse(rawResponse),以 Response 对象的形式返回给开发者。

另外还有最后一行: adapt(call, args) ; 它的内部其实是由一个适配器CallAdapted 来调用,如下:

  protected ReturnT adapt(Call<ResponseT> call, Object[] args) {
      return callAdapter.adapt(call);
    }

adapt 是一个抽象方法,里面传入了上面刚创建的 OkHttpCall 作为参数。adapt 即 adapter,适配器的意思,那我们可以猜测一下,传入 OkHttpCall,是不是就是为了对 OkHttpCall 再进行适配的工作?但,OkHttpCall 已经可以进行网络请求了,为什么还需要使用 CallAdapted 进行再次的适配呢? ⁉️这个问题还是先记下,抛到后面解答。

从上面分析知道,Retrofit 之所以调用简便,是因为内部对 @GET@POST@Path 等注解以及请求参数进行了解析,让开发者只需要关心自己新增的请求方法是不是符合规范。而上述动态代理最主要做的事情就是创建了 CallAdaptedCallAdapted 其实只是一个适配器,主要需要了解的是它适配的是什么? 是否还记得我们在上面抛出了一个关于 CallAdapted 的问题:那就先来看看他是如何创建的?⁉️

abstract class HttpServiceMethod<ResponseT, ReturnT> extends ServiceMethod<ReturnT> {

  static <ResponseT, ReturnT> HttpServiceMethod<ResponseT, ReturnT> parseAnnotations(
      Retrofit retrofit, Method method, RequestFactory requestFactory) {

    Type adapterType;
    if (isKotlinSuspendFunction) {
      Type[] parameterTypes = method.getGenericParameterTypes();
      Type responseType =
          Utils.getParameterLowerBound(
              0, (ParameterizedType) parameterTypes[parameterTypes.length - 1]);
      if (getRawType(responseType) == Response.class && responseType instanceof ParameterizedType) {
        // Unwrap the actual body type from Response<T>.
        responseType = Utils.getParameterUpperBound(0, (ParameterizedType) responseType);
        continuationWantsResponse = true;
      }

      adapterType = new Utils.ParameterizedTypeImpl(null, Call.class, responseType);
      annotations = SkipCallbackExecutorImpl.ensurePresent(annotations);
    } else {
      adapterType = method.getGenericReturnType();
    }

    CallAdapter<ResponseT, ReturnT> callAdapter =
        createCallAdapter(retrofit, method, adapterType, annotations);
  ...

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

    okhttp3.Call.Factory callFactory = retrofit.callFactory;
    if (!isKotlinSuspendFunction) {
      return new CallAdapted<>(requestFactory, callFactory, responseConverter, callAdapter);
    }

public CallAdapter<?, ?> nextCallAdapter(
      @Nullable CallAdapter.Factory skipPast, Type returnType, Annotation[] annotations) {
    int start = callAdapterFactories.indexOf(skipPast) + 1;
    for (int i = start, count = callAdapterFactories.size(); i < count; i++) {
      CallAdapter<?, ?> adapter = callAdapterFactories.get(i).get(returnType, annotations, this);
      if (adapter != null) {
        return adapter;
      }
    }

CallAdapter 是根据 returnTypeannotations 的类型,从 callAdapterFactories 工厂中进行查找,从而返回所对应的网络请求适配器,这里 returnType 指的是网络请求接口里方法的返回值类型,如 CallObservable 等。annotations 则指代的是注解类型,如 @GET@POST 等。

这里也提到了一个适配器工厂 callAdapterFactories,不同的 CallAdapter 就是从这个工厂中查询出来的,有查找那就必定有添加,那适配器 CallAdapter 是怎么添加到工厂类中的? ⁉️callAdapterFactories 这个变量是属于 Retrofit 类,跟踪发现是由 Retrofit 构造函数传入,也就是 Retrofit 初始化时进行了赋值。

Retrofit 的初始化是由一种建造者模式来创建,在 Retrofitbuild() 方法中,找到了适配器工厂对其适配器的添加:

 public Retrofit build() {
     ...
      Executor callbackExecutor = this.callbackExecutor;
      if (callbackExecutor == null) {
        callbackExecutor = platform.defaultCallbackExecutor();
      }

      List<CallAdapter.Factory> callAdapterFactories = new ArrayList<>(this.callAdapterFactories);
      //添加适配器callAdapter
      callAdapterFactories.addAll(platform.defaultCallAdapterFactories(callbackExecutor));
     ...
      return new Retrofit(
          callFactory,
          baseUrl,
          unmodifiableList(converterFactories),
          unmodifiableList(callAdapterFactories),
          callbackExecutor,
          validateEagerly);
    }

platform.defaultCallAdapterFactories 指的是 Android 平台的一个默认的适配器工厂,当我们不使用自定义适配器工厂时,则添加的就是这默认的工厂。这里提到了自定义适配器工厂,其实我们在使用 Retrofit 的时候,有时候会和RxJava 结合,例如在创建 Retrofit 时,也会 addCallAdapterFactory,将RxJava2CallAdapterFactory 添加到 callAdapterFactories 中。

mRetrofit = new Retrofit.Builder()
                .client(okHttpClient)
                .baseUrl(baseUrl)
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .build();

添加调用适配器工厂的目的就是支持 Call 以外的服务方法返回类型,如支持ObservableSingle 返回类型等。 在 callAdapterFactories 集合器添加一个默认适配器工厂时,也附带传进去了一个参数 callbackExecutorcallbackExecutor 是 Java8 或者 Android 平台的一个默认线程调度器,它的作用涉及到一个线程切换的问题,也就是后续需要分析的 Retrofit 是如何将子线程切换到主线程?⁉️

在上面源码创建 CallAdapter时,有一个对象也同时被创建,那就是Converter

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

Converter 数据装换器。它的添加和获取方式和 CallAdapter 类似。在Retrofit 初始化时添加到 List<Converter.Factory> 工厂集合,根据类型 Type 取出不同的 Converter,对返回的网络响应,做出数据的转换,例如转换成实体类。基本逻辑与 CallAdapter 类似。

mRetrofit = new Retrofit.Builder()
                .client(mOkHttpClient)
                .baseUrl(baseUrl)
                .addConverterFactory(GsonConverterFactory.create()
                .build();

总结一下上面讲的:

1,一开始动态代理中调用 loadServiceMethod 方法,解析接口方法中的注解,参数,头部信息等;

2,依据接口方法的返回类型,从适配器工厂集合里进行查询,生成相应的适配器 CallAdapter,区分是 RxJavaObservableSingle 还是 Call 或者其他类型,(适配器工厂集合的数据是由构建 RetrofitaddCallAdapterFactory() 添加,如无自定义,则添加 Android 平台默认适配器)。以相同的方式取出数据转换器 Converter

3,利用上面生成的 CallAdapter,调用 invoke 方法,创建 OkHttpCall 对象,即针对请求信息,利用 OkHttp 进行异步或者同步网络请求,并且对响应结果进行实体类转换;

4,创建好 OkHttpCall 后,又利用上面查询到的适配器 CallAdapter 调用adapt,返回 RxJavaObservableSingle 或者 Call 对象。

Retrofit 是如何将子线程切换到主线程?

在添加默认适配器工厂 defaultCallAdapterFactories 时,将 callbackExecutor 作为了一个参数,那么它的具体实现也就是在这个默认适配器工厂中。 我们来看下 callbackExecutor 在里面做了些啥。

static final class ExecutorCallbackCall<T> implements Call<T> {
    final Executor callbackExecutor;
    final Call<T> 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(
                  () -> {
                    if (delegate.isCanceled()) {
                      // Emulate OkHttp's behavior of throwing/delivering an IOException on
                      // cancellation.
                      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(() -> callback.onFailure(ExecutorCallbackCall.this, t));
            }
          });
    }

在上述代码里了解到,callbackExecutorExecutor,一个线程调度器。在Call 的 enqueue 实现里执行了一个异步网络请求 delegate.enqueue,在请求的响应 onResponseonFailureExecutor 也同样执行了一个线程,这里就有个疑问,为什么要在一个异步请求里又调用一个线程?我们知道callbackExecutor 是一个线程调度器,那他内部到底实现的是什么?
默认 callbackExecutor 的创建在 Retrofit 的初始化中,callbackExecutor = platform.defaultCallbackExecutor();

static final class Android extends Platform {

    @Override
    public Executor defaultCallbackExecutor() {
      return new MainThreadExecutor();
    }

    static final class MainThreadExecutor implements Executor {
      private final Handler handler = new Handler(Looper.getMainLooper());

      @Override
      public void execute(Runnable r) {
        handler.post(r);
      }
    }
  }
}

platform 是一个 Android 平台,defaultCallbackExecutor 内部其实调用的是 new MainThreadExecutor() ,很清楚的看到, handler.post(r) 内部使用Handler将响应抛到了主线程。这就是 Retrofit 将子线程切换到主线程的核心所在。

Retrofit 为什么要使用动态代理?

动态代理的优势是什么?不用暴露真实的委托者,根据不同的委托创建不同的代理,通过代理去做事情。

那 Retrofit 弥补了OkHttp 的哪些缺点呢?

OkHttp 在使用的时候,请求参数的配置是不是很繁琐,尤其当有一些表单提交时,又臭又长,而 Retrofit 就是弥补了这个缺点,利用 @GET、@POST、@Path、@Body 等注解以及一些参数很简便的就构造出了请求。

当 Retrofit 创建了不同的接口,动态代理就发挥出了作用。每当不同接口方法执行时,动态代理都会拦截该请求,对接口中的注解,参数进行解析,构建出不同的 Request,最后则交给 OkHttp 去真正执行。

Retrofit 结合动态代理,不用关心真正的接口方法,对符合规范的接口进行统一化的管理,以统一的方式解析注解和参数,拼接成 request。

总结:

Retrofit 其实是 OkHttp 的封装类,内部网络请求靠的还是 OkHttp,那 Retrofit 封装后改变了什么?

  • 接口请求更加简便,标注注解 @GET、@POST、@Path、@Body 等就形成一个网络请求;

  • 默认帮助开发者解析 responseBody,另外还可以自定义解析策略;

  • Retrofit 帮助开发者进行线程切换;

  • Retrofit 带给开发者更多的权限,可自定义适配网络请求。

有关读懂 Retrofit 原理~的更多相关文章

  1. 【Unity游戏破解】外挂原理分析 - 2

    文章目录认识unity打包目录结构游戏逆向流程Unity游戏攻击面可被攻击原因mono的打包建议方案锁血飞天无限金币攻击力翻倍以上统称内存挂透视自瞄压枪瞬移内购破解Unity游戏防御开发时注意数据安全接入第三方反作弊系统外挂检测思路狠人自爆实战查看目录结构用il2cppdumper例子2-森林whoishe后记认识unity打包目录结构dll一般很大,因为里面是所有的游戏功能编译成的二进制码游戏逆向流程开发人员代码被编译打包到GameAssembly.dll中使用il2ppDumper工具,并借助游戏名_Data\il2cpp_data\Metadata\global-metadata.dat

  2. Slowloris DoS攻击的原理与简单实现 - 2

    前言    Slowloris攻击是我在李华峰老师的书——《MetasploitWeb 渗透测试实战》里面看的,感觉既简单又使用,现在这种攻击是很容易被防护的啦。不过我也不敢真刀实战的去试,只是拿个靶机玩玩罢了。         废话还是写在结语里面吧。(划掉)结语可以不看(划掉)Slowloris攻击的原理        Slowloris是一种资源消耗类DoS攻击,它利用部分HTTP请求进行操作。也叫做慢速攻击,这里的慢速并不是说发动攻击慢,而是访问一条链接的速度慢。Slowloris攻击的功能是打开与目标Web服务器的连接,然后尽可能长时间的保持这些连接打开。如果由多台电脑同时发起Slo

  3. [蓝桥杯单片机]学习笔记——串口通信的基本原理与应用 - 2

    目录一、原理部分1、什么是串行通信(1)并行通信与串行通信(2)串行通信的制式(3)串行通信的主要方式  2、配置串口(1)SCON和PCON:串行口1的控制寄存器(2)SBUF:串行口数据缓冲寄存器 (3)AUXR:辅助寄存器​编辑(4)ES、PS:与串行口1中断相关的寄存器(5)波特率设置  3、串口框架编写二、程序案例一、原理部分1、什么是串行通信(1)并行通信与串行通信微控制器与外部设备的数据通信,根据连线结构和传送方式的不同,可以分为两种:并行通信和串行通信。并行通信:数据的各位同时发送与接收,每个数据位使用一条导线,这种方式传输快,但是需要多条导线进行信号传输。串行通信:数据一位一

  4. ruby - # Ruby 中识别方法约定的基本原理/历史是什么? - 2

    例如,我一直看到称为String#split的方法,但从未见过String.split,这似乎更合乎逻辑。或者甚至可能是String::split,因为您可以认为#split位于String的命名空间中。当假定/隐含类(#split)时,我什至单独看到了该方法。我知道这是ri中识别方法的方式。哪个先出现?例如,这是为了区分方法和字段吗?我还听说这有助于区分实例方法和类方法。但这从哪里开始呢? 最佳答案 不同之处在于您如何访问这些方法。类方法使用::分隔符来表示消息可以发送到类/模块对象,而实例方法使用#分隔符表示消息可以发送到实例对

  5. H264压缩原理 - 2

    1、为什么压缩的原始数据一般采用YUV格式(1)利用人对图片感觉的生理特性,对于亮度信息比较敏感,对于色度信息不太敏感,所以视频编码是将Y分量和UV分量分开来编码,并且可以减少UV分量.2、视频压缩原理(1)空间冗余:图像相邻像素之间的相关性,比如一帧图片被划分成多个16x16的块之后,相邻的块之间有很多明显的相似性。(2)时间冗余:时间相差较近的两张图片变化较小。(3)视觉冗余:我们的眼睛对某些细节不太敏感,对图像中的高频信息的敏感度小于低频信息,可以去除一些高频信息。(4)编码冗余:一幅图片中不同像素出现的概率是不同的,对于出现次数较多的像素,用少的位数来编码,对于出现次数较少的像素,用多

  6. Python——程序的运行原理 - 2

    Python程序运行原理Python是一种脚本语言,编辑完成的程序,也称源代码,可以直接运行。从计算机的角度看,Python程序的运行过程包含两个步骤:解释器将源代码翻译成字节码(即中间码),然后由虚拟机解释执行。Python程序文件的扩展名通常为.py。在执行时,首先由Python解释器将.py文件中的源代码翻译成中间码,这个中间码是一个扩展名为.pyc的文件,再由Python虚拟机(PythonVirtualMachine,PVM)逐条将中间码翻译成机器指令执行。需要说明的是,pyc文件保存在Python安装目录的pycache文件夹下,如果Python无法在用户的计算机上写人字节码,字节

  7. mysql - Rails 数据库连接池的工作原理 - 2

    我正在学习Rails数据库连接池概念。在Rails应用程序中,我将池大小定义为5。我对连接池大小的理解如下。当服务器启动时,rails会自动创建n个在database.yml文件中定义的连接。在我的例子中,它将创建5个连接,因为池大小为5。在每个http请求上,如果需要访问数据库,rails将使用连接池中的可用连接来处理请求。但我的问题是,如果我一次达到1000个请求,那么大部分请求将无法访问数据库连接,因为我的连接池大小只有5个。我上面对rails连接池的理解对吗??谢谢, 最佳答案 目的:数据库连接不是线程安全的;所以Activ

  8. ElasticSearch——刷盘原理流程 - 2

    ElasticSearch——刷盘原理流程刷盘原理流程名词和操作解释相关设置刷盘原理流程整个过程会分成几步:数据会同时写入buffer缓冲区和translog日志文件buffer缓冲区满了或者到时间了(默认1s),就会将其中的数据转换成新的segment并写入系统文件缓存,这一步叫refresh其中后台会自动合并小的segment成大的segment;这一步叫段合并当translog达到大小的阈值(默认512M)或者flush默认时长(30m),则会执行flush操作:内存中数据写入新的segment放入缓存(清空内存区)一个commitpoint写入磁盘,表示哪些segment已写入磁盘将缓

  9. ruby-on-rails - 了解 establish_connection 在 ActiveRecord 中的工作原理 - 2

    此代码取自ActiveRecord2.3.14的gem类ConnectionHandlerdefestablish_connection(name,spec)@connection_pools[name]=ConnectionAdapters::ConnectionPool.new(spec)end似乎每次ruby​​在模型上调用establish_connection时,它都会创建一个新的连接池。我的问题:如果我有5个模型使用establish_connection连接到同一个数据库,Rails是否足够智能以选择一个已经存在的池而不是创建一个具有相同连接凭据的新池?如果我的5个模型是

  10. 51单片机(郭天祥版)——键盘检测原理及应用实现 - 2

    实验中我们使用的是52单片机目录前言一、单片机是什么?二、实验步骤1.独立键盘检测1.2代码如下(示例):1.3图片1.4视频2.矩阵键盘检测2.2代码如下(示例):2.3图片2.4视频总结:以上就是今天要讲的内容,本文仅仅简单介绍了单片机键盘检测的应用实现,而单片机键盘检测相关理论可以参考教材进行学习前言文章内主要概念引自郭天祥老师《新概念51单片机C语言版》一书主要展示郭天祥老师书中第四章键盘检测原理及应用实现。分为仿真、实体两部分。一、单片机是什么?单片机就是在一块硅片上集成了微处理器、存储器及各种输入/输出接口的芯片,这样一块芯片就具有了计算机的属性,因而被成为单片微型计算机,简称单片

随机推荐