Android框架源码分析——从设计模式角度看 Retrofit 核心源码

news2024/11/26 19:53:33

Android框架源码分析——从设计模式角度看 Retrofit 核心源码

Retrofit中用到了许多常见的设计模式:代理模式、外观模式、构建者模式等。我们将从这三种设计模式入手,分析 Retrofit2 的核心源码。

1. 宏观 Retrofit 是一个外观模式的设计

外观模式:让开发人员能够用轻松地使用子系统。

OkHttp网络请求框架很大,对于初学开发人员来说使用起来非常繁杂,如果不进行封装,代码会很繁杂。Retrofit 通过外观模式的设计,将 OkHttp3 进行再封装,让用户使用起来更轻松。

OkHttp3网络请求框架有以下缺点:

  1. 用户网络请求的接口配置繁琐,尤其是重复配置复杂的 body,请求头,参数等。
  2. 数据解析过程需要用户手动对 ResponseBody 进行解析。
  3. 无法自动进行线程的切换
  4. 存在嵌套网络请求,会陷入“回调地狱”

Retrofit 对 OkHttp 这个子系统进行了再封装,在使用前后分别加上了一些便捷功能,使得网络请求能够更加便捷:

请添加图片描述

我们接下来逐个了解 Retrofit 是如何实现上述四个核心功能的:

  1. 统一配置网络请求头
  2. 将请求 Request 进行统一适配
  3. 将响应 ResponseBody 进行统一数据类型转换适配
  4. 对核心的 OkHttpCall 交给上层处理(默认处理为将callback切换回主线程执行)也可以交给RxJava的Observable对callback回调进行事件发射。
    本文中只看默认的 DefaultCallAdapterFactory实现线程切换

2. 统一配置网络请求头——构建者模式

构建者模式可以用在对配置的灵活设置:一个系统有一系列的设置,用户可以选择自主配置某些设置,剩下的使用默认设置。

构建者模式使用背景:由于用户可以选择的方案很多,我们无法通过构造方法的重载(reload)进行初始设置。如果用户使用一系列的 setter() 工作量和代码量也不小,这时上述的构建者模式“灵活设置”的优势就体现出来了。

Retrofit 有上面谈到的四个核心功能,这四个功能是可拓展的,具体如何实现,可以通过设置来决定,例如如何对响应 Response 进行数据适配,用户可以选择 Gson、Jacson、SimpleXML 等多种方案,也使用默认方案。

我们来看一下 Retrofit 是如何做到“灵活配置”的:

请添加图片描述

Retrofit中有许多参数可以自定义设置,我们看到源码:

public static final class Builder {
    //Retrofit 运行的平台:Java or Android,通过虚拟机来判断,如果是“Dalvik”就认为是 Android 平台,主要用于选择用哪种方式进行线程切换。(策略模式)
    private final Platform platform;
    //OkHttp3.Call 真正请求角色的创建工厂
    private @Nullable okhttp3.Call.Factory callFactory;
    //当前Retrofit下请求url的前缀,如果一个系统需要访问多个baseUrl的服务器,就需要多个Retrofit
    private @Nullable HttpUrl baseUrl;
    //对响应Response统一适配的工具,将Response通过Gson等方式转为Javabean
    private final List<Converter.Factory> converterFactories = new ArrayList<>();
    //对请求Request统一适配的工具,对 Request进行预处理
    private final List<CallAdapter.Factory> callAdapterFactories = new ArrayList<>();
    //用于线程切换,将回调函数放到响应线程执行,如切换到主线程执行
    private @Nullable Executor callbackExecutor;
    private boolean validateEagerly;
    //...
}

通过构建者模式实例化 Retrofit 示例:

Retrofit retrofit = new Retrofit.Builder()//构建者模式,原类的构造器隐藏,由内部类Builder()来完成构建。
                .baseUrl("https://localhost/")//注意要以/结尾
                .addConverterFactory(GsonConverterFactory.create())
                .build();

3. 将请求体Request 进行统一配置,获得ExecutorCallbackCall<>——代理模式

Retrofit 使用的特点是使用接口和注解进行请求 Request 的统一设置:

public interface IDemoService {
    @GET("user")
    Call<ResponseBody> getData1(@Query("username") String username);
    @Headers({"phone-type:android", "version:1.1.1"})
    @GET("user/emails")
    Call<ResponseBody> getHeadersData();
    @GET("orgs/{page}")
    Call<ResponseBody> getPathData(@Query("username") String username, @Path("page") int page);
     @POST("user/emails")
    @FormUrlEncoded
    Call<ResponseBody> getPostData2(@FieldMap Map<String,Object> map);
    //...
}

IDemoService 扮演者代理模式中的interface接口,它表明了委托人和代理人所共有的能力。开发人员无需了解具体实现类是如何进行操作的,只需要通过代理获得最终结果即可。Retrofit 中,用户通过代理获取生成的请求体Call<>,代理返回的结果是 ExecutorCallbackCall<>类型的请求体Call<>。

请添加图片描述

Retrofit 使用的是动态代理方案。

Retrofit 获得网络请求代理:

IDemoService userServiceProxy = retrofit.create(IDemoService.class);

由代理去生成 ExecutorCallbackCall<>:

//用户调用了代理的getData0()的方法
Call<ResponseBody> data0 = userServiceProxy.getData0();

后续可以使用这个Call<>去进行网络请求:

Call<ResponseBody> executorCallbackCall = userServiceProxy.getData0();
//发起异步网络请求
executorCallbackCall.enqueue(new Callback<ResponseBody>() {
    //返回到这里,默认回调到主线程!
    @Override
    public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {

    }

    @Override
    public void onFailure(Call<ResponseBody> call, Throwable t) {

    }
});

Retrofit 会创建一个IxxxService的代理类:

//[Retrofit.java]
public <T> T create(final Class<T> service) {
    validateServiceInterface(service);
    return (T)
        Proxy.newProxyInstance(
        service.getClassLoader(),
        new Class<?>[] {service},
        new InvocationHandler() {
            //获取到平台为Android
            private final Platform platform = Platform.get();  
            @Override
            public @Nullable Object invoke(Object proxy, Method method, @Nullable Object[] args)
                throws Throwable {                
                return loadServiceMethod(method).invoke(args);
            }
        });
}

这里使用了动态代理,Proxy.newProxyInstance() 返回一个 IxxxService 的代理类对象。当用户调用 IxxxService 的如 getData0() 方法时,会调用上述的 invoke()方法。传递的参数为:proxy:代理本身,method:代理对象调用的方法,args:参数。

例如我们调用了 IxxxService.getData0(),那么这个invoke()传入的参数为:

  1. proxy: IxxxService对象
  2. method:getData0()的方法对象
  3. args: 空数组,由于我们没有传递参数

Retrofit 调用 loadServiceMethod(method) ,loadServiceMethod()返回一个 ServiceMethod对象,然后调用这个对象的invoke()方法,最后给用户返回一个 由OkHttpCall<>适配而来的ExecutorCallbackCall<> 对象。

在产生ServiceMethod对象的过程中,还根据Retrofit的设置,构建了:

  1. 请求适配器,将OkHttpCall<>适配为 ExecutorCallbackCall<>给上层使用
  2. 数据转换器,可以将数据进行解析,如GsonConverterFactory产出的转换器可以完成:Gson<->Javabean。

产生 ServiceMethod 对象的入口 loadServiceMethod:

ServiceMethod<?> loadServiceMethod(Method method) {
    //线程安全DCL检查
    //使用缓存保存之前生成的 ServiceManager,减轻了反射带来的性能损耗
    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;
}

由于过程利用到反射,会损耗性能,所以使用 serviceMethodCache进行反射后结果的缓存,减少反射性能损耗带来的负面影响。

abstract class ServiceMethod<T> {
    static <T> ServiceMethod<T> parseAnnotations(Retrofit retrofit, Method method) {
		//初始化 requestFactory
        RequestFactory requestFactory = RequestFactory.parseAnnotations(retrofit, method);
		//记录返回类型
        Type returnType = method.getGenericReturnType();
		//
        return HttpServiceMethod.parseAnnotations(retrofit, method, requestFactory);
    }

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

默认返回的是 ServiceMethod 的实现类: HttpServiceMethod,在实例化它之前,先实例化了 请求适配工具 CallAdapter 和数据转换工具 Converter

//[HttpServiceMethod.java]
//核心成员变量:
private final RequestFactory requestFactory;
private final okhttp3.Call.Factory callFactory;
private final Converter<ResponseBody, ResponseT> responseConverter;

//核心方法:
static <ResponseT, ReturnT> HttpServiceMethod<ResponseT, ReturnT> parseAnnotations(
      Retrofit retrofit, Method method, RequestFactory requestFactory) {
    //获得method的返回值类型
    adapterType = method.getGenericReturnType();
    //1.获取方法上的注解
    Annotation[] annotations = method.getAnnotations();
    //2.创建请求统一适配工具:CallAdapter
    CallAdapter<ResponseT, ReturnT> callAdapter =
        //调用 Retrofit.callAdapter()
        createCallAdapter(retrofit, method, adapterType, annotations);
    //3.创建数据转换工具:Converter
    Converter<ResponseBody, ResponseT> responseConverter =
        //调用 Retrofit.responseBodyConverter()
        createResponseConverter(retrofit, method, responseType);
    //SuspendForResponse、SuspendForBody都是HttpServiceMethod的子类,主要实现了 adapt()方法。
    //如果是响应返回过程中
    if(continuationWantsResponse){
        return new SuspendForResponse<>(...);
    }else{
        //如果是请求过程中
        return new SuspendForBody<>(...);
    }
}

由于Retrofit中保存了各种工厂的设置,所以HTTPServiceMethod将工厂生产的具体实现,交给了 Retrofit 来做。

我们来看一下 Retrofit 是如何创建请求体适配工具 CallAdapter和数据转换工具Converter的:

//[Retrofit.java]
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实例:
        CallAdapter<?, ?> adapter = callAdapterFactories.get(i).get(returnType, annotations, this);
        if (adapter != null) {
            return adapter;
        }
    }
}

public <T> Converter<T, RequestBody> nextRequestBodyConverter(
    @Nullable Converter.Factory skipPast,
    Type type,
    Annotation[] parameterAnnotations,
    Annotation[] methodAnnotations) {
    
    int start = converterFactories.indexOf(skipPast) + 1;
    for (int i = start, count = converterFactories.size(); i < count; i++) {
        Converter.Factory factory = converterFactories.get(i);
        //由工厂创建 响应数据适配工具Converter实例:
        Converter<?, RequestBody> converter =
            factory.requestBodyConverter(type, parameterAnnotations, methodAnnotations, this);
        if (converter != null) {
            //noinspection unchecked
            return (Converter<T, RequestBody>) converter;
        }
    }
}

他们都由工厂来构建,我们先看一下请求适配工具 CallAdapter 是如何构建的:

//[DefaultCallAdapterFactory.java]
public @Nullable CallAdapter<?, ?> get(
    Type returnType, Annotation[] annotations, Retrofit retrofit) {
    
    final Type responseType = Utils.getParameterUpperBound(0, (ParameterizedType) returnType);

    final Executor executor =
        Utils.isAnnotationPresent(annotations, SkipCallbackExecutor.class)
        ? null
        : callbackExecutor;

    return new CallAdapter<Object, Call<?>>() {
        @Override
        public Type responseType() {
            return responseType;
        }
		
        //请求体适配器用来对传入的请求体进行统一适配,适配给Retrofit使用
        @Override
        public Call<Object> adapt(Call<Object> call) {
            return executor == null ? call : new ExecutorCallbackCall<>(executor, call);
        }
    };
}

可以发现,请求体适配器的核心作用是:将传入的Call<>都适配成了 ExecutorCallbackCall<>对象给上层使用。

实例化 请求体适配器CallAdapter 之后,我们再看看 数据转换器Converter是怎么由工厂 ConverterFactory生成的:

//[Converter.java]
public @Nullable Converter<?, RequestBody> requestBodyConverter(
    Type type,
    Annotation[] parameterAnnotations,
    Annotation[] methodAnnotations,
    Retrofit retrofit) {
    return null;
}

我们发现这是一个空实现,需要具体的工厂来完成,工厂的选择可以是默认的,也可以是用户给 Retrofit 初始化时设置的工厂:例如设置了 GsonConverterFactory 工厂用于生产 Gson 的数据转换器:

Retrofit retrofit = new Retrofit.Builder()
                .baseUrl("https://localhost/")//注意要以/结尾
                .addConverterFactory(GsonConverterFactory.create())//设置Converter的构建工厂
                .build();

由于数据转换器需要完成 json 到 javabean 的转换,以及 javabean 到 json的转换,所以数据转换器的convert()方法有两种实现:

请求过程中:从 javabean 转换到 json:

//[GsonResponseBodyConverter<T>.java]
@Override public T convert(ResponseBody value) throws IOException {
    JsonReader jsonReader = gson.newJsonReader(value.charStream());
    try {
        return adapter.read(jsonReader);
    } finally {
        value.close();
    }
}

响应过程中:从 javabean 到 json:

//[GsonRequestBodyConverter<T>.java]
@Override public RequestBody convert(T value) throws IOException {
    Buffer buffer = new Buffer();
    Writer writer = new OutputStreamWriter(buffer.outputStream(), UTF_8);
    JsonWriter jsonWriter = gson.newJsonWriter(writer);
    adapter.write(jsonWriter, value);
    jsonWriter.close();
    return RequestBody.create(MEDIA_TYPE, buffer.readByteString());
}

请求体适配器 CallAdapter 和数据转换器 Converter 构造好之后,loadServiceMethod()返回一个ServiceManager,用户调用 loadServiceMethod().invoke() 方法。

如果是请求过程,返回的是 HttpServiceMethod 的子类 SuspendForBody<>,如果是响应过程,返回的是 SuspendForResponse<>。

看到 HttpServiceMethod 的 invoke() 方法:

//[HttpServiceMethod.java]
@Override
final @Nullable ReturnT invoke(Object[] args) {
    Call<ResponseT> call = new OkHttpCall<>(requestFactory, args, callFactory, responseConverter);
    return adapt(call, args);
}
//这个 adapt() 是个空实现,由子类 SuspendForBody 和 SuspendForResponse 实现
protected abstract @Nullable ReturnT adapt(Call<ResponseT> call, Object[] args);

两个子类的adapt()方法都一样:

//[SuspendForBody.java][SuspendForResponse.java]
@Override
protected Object adapt(Call<ResponseT> call, Object[] args) {
    call = callAdapter.adapt(call);
    return call;
}

至此,上面的流程图就走通了:

  1. 代理类 userServiceProxy 调用了 getData0(),实际是通过 InvocationHandler的invoke()方法,让委托人去完成具体任务
  2. 委托人先根据 Retrofit 中的设置,创建好请求体适配器 CallAdapter 和数据转换器 Converter.
  3. 委托人返回请求体时,本来返回的是 OkHttpCall<>,但是返回过程中经过了请求体适配器CallAdapter,被适配为了 ExecutorCallbackCall<>返回给用户。

4. 发起网络请求 —— 外观模式

我们到了请求体 ExecutorCallbackCall<> 就可以发起网络请求了。但是我们回顾上面的流程,并没有发现 okhttp3.Call 的产生,那为什么 ExecutorCallbackCall<>可以发起网络请求呢? 答案很简单,因为它封装的 OkHttpCall<> 中有 okhttp3.Call.Factory 可以用来构建真正的请求体:

请添加图片描述

为什么这么设计呢?因为构建请求体需要:

  1. 解析接口中定义的注解
  2. 将传入的参数根据后端要求,转换为 json 等格式
  3. 拼接好请求头、baseUrl等
  4. 最后拼接整合成为请求体Call<>需要的RequestBody

这个过程非常繁琐,框架的设计就是为了让开发人员更简单地使用子系统,所以上述两个构建过程,对上层来说设计为透明的,可以极大提高开发效率。

Retrofit 的外观设计模式将以上复杂的需求封装在了内部,成为了“黑盒”。“黑盒”将任务分别交给了 ExecutorCallbackCall 和 OkHttpCall:

  1. ExecutorCallbackCall 负责将回传的数据进行线程切换,交还给用户;
  2. OkHttpCall 负责构建请求体、发起网络请求、响应Response的数据适配转换;

ExecutorCallbackCall的enqueue()为调用入口:

Retrofit retrofit = new Retrofit.Builder()
    .baseUrl("https://localhost/")//注意要以/结尾
    .addConverterFactory(GsonConverterFactory.create())
    .build();
IDemoService userServiceProxy = retrofit.create(IDemoService.class);
Call<ResponseBody> executorCallbackCall = userServiceProxy.getData0();
//调用入口
executorCallbackCall.enqueue(new Callback<ResponseBody>() {
    @Override
    public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {

    }

    @Override
    public void onFailure(Call<ResponseBody> call, Throwable t) {

    }
});

用户传入了回调接口 new Callback() 用于获取网络请求返回的数据。executorCallbackCall.enqueue() 进入到“黑盒”进行一系列的构建。它主要完成了:

  1. 让OkHttpCall.enqueue()去进行请求体构建和发起网络请求
  2. 用 Callback 回调接口收取从 OkHttpCall 回传的数据
  3. **线程切换:**将上层传来的 Callback接口的回调函数 放在设定的线程环境中执行

我们看到其enqueue() 方法:

//[ExecutorCallbackCall<T>.java]
static final class ExecutorCallbackCall<T> implements Call<T> {
    final Executor callbackExecutor;
    final Call<T> delegate;
	//构造函数,在上一章节调用
    //1.切换回调函数执行线程的工具
    //2.传入了OkHttpCall<>
    ExecutorCallbackCall(Executor callbackExecutor, Call<T> delegate) {
        this.callbackExecutor = callbackExecutor;
        this.delegate = delegate;
    }

    @Override
    public void enqueue(final Callback<T> callback) {
        Objects.requireNonNull(callback, "callback == null");
        //OkHttpCall<T>.enqueue()
        delegate.enqueue(
            //回调接口,接收从OkHttpCall<T>回传的数据
            new Callback<T>() {
                @Override
                public void onResponse(Call<T> call, final Response<T> response) {
                    //在这里进行线程切换
                    //step2. 将 Runnable 放到 callbackExecutor设置的线程中执行(默认CallAdapter工厂传入的是MainExecutor,即主线程)
                    callbackExecutor.execute(
                        //step1. 将接口回调的逻辑封装到 Runnable 中
                        () -> {
                            if (delegate.isCanceled()) {
                                 callback.onFailure(ExecutorCallbackCall.this, new IOException("Canceled"));
                            } else {
                                callback.onResponse(ExecutorCallbackCall.this, response);
                            }
                        });
                }
				//其他回调()...
            });
    }

    //同步发起网络请求
    @Override
    public Response<T> execute() throws IOException {
        return delegate.execute();
    }

    //...其他回调 delegate.回调()
}

由此可见,为了减轻用户在使用Okhttp时候总要考虑线程切换的烦恼,Retrofit 将线程切换的功能放到了黑盒中,具体如何切换是一个策略模式,用户只需要在初始化 Retrofit 的时候设置一下就够了。(由于默认线程切换到主线程回调,所以用户甚至都不用设置!)

我们在接下去看 OkHttpCall ,它有三个主要任务:

  1. createRawCall() 构建请求体
  2. okhttp3.call.enqueue() 真正发起网络请求
  3. 对响应数据Response 进行数据适配转换(Convert)

进入到 OkHttpCall 的 enqueue() 方法中:

//[OkHttpCall.java]
@Override
public void enqueue(final Callback<T> callback) {
    okhttp3.Call call;
    Throwable failure;
    //线程安全
    synchronized (this) {
        //1. createRawCall()构建请求体
        call = createRawCall();
    }
	//2. okhttp3.call.enqueue() 真正由okhttp发起网络请求
    call.enqueue(
        new okhttp3.Callback() {
            @Override
            public void onResponse(okhttp3.Call call, okhttp3.Response rawResponse) {
                Response<T> response;
                //3. 对响应数据Response 进行数据适配转换(Convert),这里是例如: gson->javabean 的转换(请求时 javabean->gson 的转换在请求体构建中完成)
                response = parseResponse(rawResponse);
                //将数据转换后的数据通过回调接口回传给ExecutorCallbackCall
                callback.onResponse(OkHttpCall.this, response);
            }
			//其他回调...
        });
}

我们先来看一下第一步,构建请求体:

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

显然构建请求体有两步:

  1. 构建Request
  2. 构建Call

由requestFactory.create() 来构建 Request:

//[RequestFactory.java]
okhttp3.Request create(Object[] args) throws IOException {
    //参数上的注解处理器
    ParameterHandler<Object>[] handlers = (ParameterHandler<Object>[]) parameterHandlers;

    //Call需要用到的请求体
    RequestBuilder requestBuilder =
        new RequestBuilder(...);

    List<Object> argumentList = new ArrayList<>(argumentCount);
    //遍历所有传入参数
    for (int p = 0; p < argumentCount; p++) {
        argumentList.add(args[p]);
        //将传入参数和注解处理器的记录的标记进行结合/应用/绑定
        handlers[p].apply(requestBuilder, args[p]);
    }

    return requestBuilder.get().tag(Invocation.class, new Invocation(method, argumentList)).build();
}

其中 parameterHandlers 是初始化 RequestFactory 时传入的,RequestFactory是在 动态代理 loadServiceMethod() -> ServiceMethod.parseAnnotations()的时候时候初始化的:

RequestFactory的主要任务是:解析方法上的和参数上的注解,并记录下来,成为构建 RequestBody 中需要使用的 key。

//[RequestFactory.java]
//主要任务:解析方法上的和参数上的注解,并记录下来,成为RequestBody 中需要使用的 key
static final class Builder{
    //...
    
    //在这里解析注解!!!
    RequestFactory build(){
        for (Annotation annotation : methodAnnotations) {
            //1. 解析方法上的注解
            parseMethodAnnotation(annotation);
        }
        //..
        
        int parameterCount = parameterAnnotationsArray.length;
        parameterHandlers = new ParameterHandler<?>[parameterCount];
        for (int p = 0, lastParameter = parameterCount - 1; p < parameterCount; p++) {
            parameterHandlers[p] =
                //2. 解析参数上的注解
                parseParameter(p, parameterTypes[p], parameterAnnotationsArray[p], p == lastParameter);
        }
        return new RequestFactory(Builder.this);
    }
}

简单看一下解析方法上注解的实现,和解析参数上注解的实现:

解析方法上的注解(方法上的注解可能有多个,每个都解析一下):

//[RequestFactory.java]
private void parseMethodAnnotation(Annotation annotation) {
    if (annotation instanceof DELETE) {
        parseHttpMethodAndPath("DELETE", ((DELETE) annotation).value(), false);
    } else if (annotation instanceof GET) {
        parseHttpMethodAndPath("GET", ((GET) annotation).value(), false);
    } else if (annotation instanceof HEAD) {
        parseHttpMethodAndPath("HEAD", ((HEAD) annotation).value(), false);
    } 
    //...
    else if (annotation instanceof FormUrlEncoded) {
        if (isMultipart) {
            throw methodError(method, "Only one encoding annotation is allowed.");
        }
        isFormEncoded = true;
    }
    //如果不是 Retrofit 能处理的注解,就不管了
}

解析参数上的注解(参数上的注解也可能有多个,每个都解析一下):

//[RequestFactory.java]
private @Nullable ParameterHandler<?> parseParameter(...){
    for (Annotation annotation : annotations) {
        ParameterHandler<?> annotationAction =
            parseParameterAnnotation(p, parameterType, annotations, annotation);
    }
    //...
}

private ParameterHandler<?> parseParameterAnnotation(...){
    //检验异常、注解间的互斥关系
    if(annotation instanceof Url){
        if (gotUrl) {
          throw parameterError(method, p, "Multiple @Url method annotations found.");
        }
        //...
        gotUrl = true;
        return new ParameterHandler.RelativeUrl(method,p);
    }
}

构建请求体的时候,需要将参数上的注解处理器parameterHandler和传入参数做绑定,调用的是 ParameterHandler<>的 apply 方法,主要任务是将注解意义和参数值结合起来交给 RequestBuilder:

请添加图片描述

我们看到它在 Header in ParameterHandler 中的实现,它主要做了两件事:

  1. 将数据进行转换Convert,如:javabean -> gson
  2. 将注解意义和参数值,填充到 RequestBuilder 中
//[Header in ParameterHandler.java]
static final class Header<T> extends ParameterHandler<T> {
    private final String name;
    private final Converter<T, String> valueConverter;

    Header(String name, Converter<T, String> valueConverter) {
        this.name = Objects.requireNonNull(name, "name == null");
        this.valueConverter = valueConverter;
    }

    @Override
    void apply(RequestBuilder builder, @Nullable T value) throws IOException {
        if (value == null) return; // Skip null values.
		//数据转换器进行 javabean->gson
        String headerValue = valueConverter.convert(value);
        if (headerValue == null) return; // Skip converted but null values.
		//将 key value 放到 requestBuilder 中
        builder.addHeader(name, headerValue);
    }
}

完全构造好后,回到 RequestFacoty.create(),将拼接好的 Request 返回给上层

//[RequestFactory.java]
okhttp3.Request create(Object[] args) throws IOException {
    //参数上的注解处理器
    ParameterHandler<Object>[] handlers = (ParameterHandler<Object>[]) parameterHandlers;

    //Call需要用到的请求体
    RequestBuilder requestBuilder =
        new RequestBuilder(...);

    List<Object> argumentList = new ArrayList<>(argumentCount);
    //遍历所有传入参数
    for (int p = 0; p < argumentCount; p++) {
        argumentList.add(args[p]);
        //将传入参数和注解处理器的记录的标记进行结合/应用/绑定,拼接到requestBuilder 中
        handlers[p].apply(requestBuilder, args[p]);
    }

    return requestBuilder.get().tag(Invocation.class, new Invocation(method, argumentList)).build();
}

接下去就是构建call,并且发送网络请求。回到 OkHttpCall:

//[OkHttpCall.java]
@Override
public void enqueue(final Callback<T> callback) {
    okhttp3.Call call;
    Throwable failure;
    //线程安全
    synchronized (this) {
        //1. createRawCall()构建请求体
        call = createRawCall();
    }
	//2. okhttp3.call.enqueue() 真正由okhttp发起网络请求
    call.enqueue(
        new okhttp3.Callback() {
            @Override
            public void onResponse(okhttp3.Call call, okhttp3.Response rawResponse) {
                Response<T> response;
                //3. 对响应数据Response 进行数据适配转换(Convert),这里是例如: gson->javabean 的转换(请求时 javabean->gson 的转换在请求体构建中完成)
                response = parseResponse(rawResponse);
                //将数据转换后的数据通过回调接口回传给ExecutorCallbackCall
                callback.onResponse(OkHttpCall.this, response);
            }
			//其他回调...
        });
}

至此,网络请求发起完成。后续的线程切换,也在 ExecutorCallbackCall 中讲述过了。当然,线程切换可以支持其他工厂,如Rxjava,那么CallAdapter适配器就可以把 OkHttpCall 转成我们所需的 Observable 类型。

5. 总结

1.数据转换在哪里完成?

在OkHttpCall的enqueue()中,OkHttpCall主要负责三个任务:

  1. 通过RequestFactory解析来的注解,和传入参数,进行 requestBuilder的拼接,构建 okhttp3.Request
  2. 创建 okhttp3.Call 进行网络通信
  3. 将返回数据进行数据转换,返回给上层

从 javabean 到 string(如json)的转换,在ParameterHandler.apply()中完成,或者说是在将参数拼接到 requestBuilder的时候进行的转换

从 string(如json) 到 javabean 的转换,则是在响应数据 onResponse 时候做的,数据转换后再回调给上层。

2.Convert和CallAdapter的作用?

上面已经回答了 Convert 的作用。 CallAdapter主要是对 OkHttpCall的封装,用于上层处理网络请求的发起和结果,例如默认的 ExecutorCallbackCall 默认将上层回到接口转到主线程执行。也可以转成其他形式,例如 RxJava的Observable。

3. 动态代理在Retrofit中主要做了什么事?【获得对OkHttpCall进行再封装的上层对象】

将我们定义的接口Interface创建一个代理,代理中所有方法的实现,都是调用传入参数的 InvocationHandler 的 invoke() 方法,交给委托人来办具体的事务。这里的事务交给了 ServiceMethod,返回一个对OkHttpCall进行封装的类,默认是 DefaultCallAdapterFactory对OkHttpCall封装的ExecutorCallbackCall<>。它是用来做线程切换的。当然也可以返回一个由 RxJavaCallAdapterFactory 设计的 Observable。

4.Retrofit怎么知道接口需要交给哪个CallAdapterFactory做上层处理?【返回值类型】

特别的 Interface 中定义的方法如果返回值类型为 void 是不被允许的。

Interface中定义的方法的返回值类型是用来判断使用Retrofit的 callAdapterFactories集合中哪个 CallAdapterFactory 对该方法处理如果全都无法处理,将会抛出异常!

比如返回值类型为 Call<>,现在有两个工厂: 一个是CompletableFutureCallAdapterFactory,一个是 DefaultCallAdapterFactory,第一个工厂发现返回值类型不是自己要的,返回空不作处理。直到遍历到 DefaultCallAdapterFactory 可以处理返回值类型为 Call<>的方法,就交给它对OkHttpCall包装。

一样的,RxJavaCallAdapterFactory 可以处理返回值为 Observable 的方法。

5. 相比直接用okhttp,使用retrofit有什么优势?
retrofit使用外观/门面设计模式对okhttp进行了再封装。okhttp有几大缺陷:对象头、参数等配置繁杂,返回结果需要手动解析,无法自动进行线程切换,可能会出现回调地狱。
1、retrofit通过构建者模式,将对象头等可以复用的信息保存在成员变量中
2.返回结果的处理使用策略模式让用户使用相关的库,例如 GsonConverterFactory进行javabean和json之间的转换,
3. OkHttpCall 封装简化了参数配置、数据解析,且可以让上层决定如何处理最后的回调接口的数据

6.retrofit用到了动态代理和反射,不会影响效率么?
动态代理和反射解析注解确实耗性能,但retrofit还涉及了缓存,将需要反射解析注解、动态代理才能生成的ServiceMethod进行了缓存,未来通过 Method 对象,就可以找到与它对应的 ServiceMethod对象,进行例如 Call<>、Observable<>等的构建,速度很快,只有两步:1.invoke()生成OkHttpCall() 2. HttpServiceMethod子类的adapt进行适配,装饰为返回值类型。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/332992.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Intel处理器分页机制

分页模式 Intel 64位处理器支持3种分页模式&#xff1a; 32-bit分页PAE分页IA-32e分页 32-bit分页 32-bit分页模式支持两种页面大小&#xff1a;4KB以及4MB。 4KB页面的线性地址转换 4MB页面的线性地址转换 PAE分页模式 PAE分页模式支持两种页面大小&#xff1a;4KB以及…

Java 验证二叉搜索树

验证二叉搜索树中等给你一个二叉树的根节点 root &#xff0c;判断其是否是一个有效的二叉搜索树。有效 二叉搜索树定义如下&#xff1a;节点的左子树只包含 小于 当前节点的数。节点的右子树只包含 大于 当前节点的数。所有左子树和右子树自身必须也是二叉搜索树。示例 1&…

ChatGPT注册流程攻略,含验证码接收(图文步骤)

本文给大家分享一下我成功注册的流程&#xff01; 其实方法都类似&#xff0c;若无海外手机号码可用接验证码的平台&#xff08;ps&#xff1a;我之前使用的是SMS-Activate&#xff09; 必要准备 能够科学上网&#xff08;并且全局模式&#xff09; 能确认登录的电子邮箱&…

ffmpeg硬解码与软解码的压测对比

文章目录ffmpeg硬解码与软解码的压测一、基本知识二、压测实验1. 实验条件及工具说明2. 压测脚本3. 实验数据结果ffmpeg硬解码与软解码的压测 一、基本知识 本文基于intel集显进行压测 软解码&#xff1a;cpu对视频进行解码硬解码&#xff1a;显卡或者多媒体处理芯片对视频进…

Python编程自动化办公案例(1)

作者简介&#xff1a;一名在校计算机学生、每天分享Python的学习经验、和学习笔记。 座右铭&#xff1a;低头赶路&#xff0c;敬事如仪 个人主页&#xff1a;网络豆的主页​​​​​​ 目录 前言 一.使用库讲解 1.xlrd 2.xlwt 二.主要案例 1.批量合并 模板如下&#xf…

Python 如何快速搭建环境?

Python可应用于多平台包括 Linux 和 Mac OS X。 你可以通过终端窗口输入 “python” 命令来查看本地是否已经安装Python以及Python的安装版本。 Unix (Solaris, Linux, FreeBSD, AIX, HP/UX, SunOS, IRIX, 等等。) Win 9x/NT/2000 Macintosh (Intel, PPC, 68K) OS/2 DOS (多个…

67. Python的绝对路径

67. Python的绝对路径 文章目录67. Python的绝对路径1. 准备工作2. 路径3. 绝对路径3.1 概念3.2 查看绝对路径的方法4. 课堂练习5. 用绝对路径读取txt文件6. 加\改写绝对路径6.1 转义字符知识回顾6.2 转义字符改写7. 总结1. 准备工作 对照下图&#xff0c;新建文件夹和txt文件…

小知识点:MySQL 的 redo log、undo log、binlog 以及 Java 监控 binlog

SQL 入库流程 服务器与 MySQL 建立连接依次经过 MySQL 服务器内存中 Server 层的分析器、优化器、执行器执行器根据执行计划操作 InnoDB 引擎InnoDB 从磁盘数据文件中将 data 读到缓冲池中修改之前&#xff0c;会写入 undo log 将 data 存起来然后将缓冲池中的 data 改成 new_d…

数据结构复习(三)顺序表oj

目录 27. 移除元素 26. 删除有序数组中的重复项 88. 合并两个有序数组 27. 移除元素 给你一个数组 nums 和一个值 val&#xff0c;你需要 原地 移除所有数值等于 val 的元素&#xff0c;并返回移除后数组的新长度。 不要使用额外的数组空间&#xff0c;你必须仅使用 O(1) 额外…

多数据库学习之GBase8s查询数据库表元信息常用SQL

多数据库学习之GBase8s查询数据库表元信息常用SQL简介常用SQL创建用户创建数据库及模式获取表元数据其他参考链接简介 背景介绍 GBase 8t是基于IBM informix源代码、编译和测试体系自主研发的交易型数据库产品。 南大通用安全数据库管理系统&#xff08;简称 GBase 8s&#xff…

Linux基础命令2(常见的文件相关命令)

目录 查找文件命令 pwd 显示当前所在的工作目录&#xff08;Print working directory&#xff09; cd 切换命令&#xff08;change directory&#xff09; ls 查看目录下的文件&#xff08;list&#xff09; tree 查看目录下的子目录&#xff08;查看目录结构&#…

Grafana 系列文章(十二):如何使用Loki创建一个用于搜索日志的Grafana仪表板

概述 创建一个简单的 Grafana 仪表板, 以实现对日志的快速搜索. 有经验的直接用 Grafana 的 Explore 功能就可以了. 但是对于没有经验的人, 他们如何能有一个已经预设了简单的标签搜索的仪表板&#xff0c;以帮助一些团队在排除故障时快速找到他们正在寻找的东西。虽然 Expl…

云仓仓储的运行模式是什么?

仓库能够简单地定义为一个规划空间&#xff0c;通常是一个用于处置和贮存货物的大型商业建筑。因而&#xff0c;仓储是指在这样一个规划空间中存储和处置货物所触及的一切过程。仓库中常见的货物包括&#xff1a;;机械零配件、建筑资料、废品农产品、家具和电子产品。仓库中的一…

【面试题】经典面试题:让 a == 1 a == 2 a == 3 成立?

一、问题解析 if (a == 1 && a == 2 && a == 3) {console.log(Win) } 复制代码 如何打印除Win? 看到题目的第一眼,我是蒙蔽的.怎么可能会有如此矛盾的情况发生呢?就相当于一个人怎么可能即是小孩,又是成年人,还是老年人呢? 冷静下来,发现一些端倪。

VHDL语言基础-组合逻辑电路-概述

目录 概述&#xff1a; 组合逻辑电路&#xff1a;——电路无记忆功能 组合逻辑电路的设计方法&#xff1a; 传统的设计方法&#xff1a;采用标准组件进行设计 组合逻辑电路的设计方法&#xff1a; 两种设计方法的比较&#xff1a; 概述&#xff1a; 数字电路按其完成逻辑…

[Android]图片加载库Glide

目录 Glide的介绍 Glide的基本使用 指定图片的格式 Glide占位符 指定图片的大小 过渡动画 图片变换 Generated API Glide的介绍 Glide是一个快速高效的Android图片加载库&#xff0c;可以自动加载网络&#xff0c;本地文件&#xff0c;app资源中的文件&#xff0c;注重于平…

数据存储技术复习(二)未完

module3存储是数据中心内的核心元素。请说明常用的存储选项及其特点。磁盘驱动器&#xff1a;具有很大的存储容量&#xff0c;随机读/写访问闪存驱动器&#xff1a;使用半导体介质&#xff0c;提供高性能&#xff0c;低功耗2&#xff0e;若某磁盘驱动器显示每个磁道有八个扇区&…

标准舆情监测平台解决方案及流程,TOOM舆情监测工作计划有哪些?

舆情监测流程一般包括&#xff1a;数据收集、数据分析、信息汇报三个部分。首先&#xff0c;通过多种途径收集舆情数据&#xff0c;如网络媒体、社交媒体、博客、论坛等;其次&#xff0c;对收集的数据进行分析&#xff0c;统计舆情趋势、舆情类型等;最后&#xff0c;根据舆情分…

【Java】TCP的三次握手和四次挥手

三次握手 TCP三次握手是一个经典的面试题&#xff0c;它指的是TCP在传递数据之前需要进行三次交互才能正式建立连接&#xff0c;并进行数据传递。&#xff08;客户端主动发起的&#xff09;TCP之所以需要三次握手是因为TCP双方都是全双工的。 什么是全双工&#xff1f; TCP任何…

Print: Entry, “:CFBundleIdentifier“, Does Not Exist解决办法

首先执行react-native info查看我的电脑环境是&#xff1a; React Native Environment Info: System: OS: macOS 10.15.5 CPU: (8) x64 Intel(R) Core(TM) i7-4870HQ CPU 2.50GHz Memory: 103.91 MB / 16.00 GB Shell: 5.7.1 - /bin/zsh Binaries: Node: 12.22.12 - ~/.nvm/ve…