Retrofit的封装之道

目录

Retrofit简介

怎么读源码

Retrofit使用示例

①:建立顶级工具类

②:动态代理生成操作对象实例

③:调用代理对象的方法

④:操作Call对象

⑤:打印结果

小结


Retrofit简介

Retrofit是目前使用广泛的一款网络框架。底层通过封装OkHttp实现网络访问,并且具有高度解耦的特性。

之前使用OkHttp我们需要自己封装Request,生成Call对象,操作Call对象,自己处理结果。这些步骤耦合在一起,不仅让代码混乱,而且也加大了维护成本。

Retrofit可以说是OkHttp的加强版,可以完成所有OkHttp所有的功能,还有很多增强的功能。比如相应处理,返回对象多样化等等。并且超级解耦。

出于对Retrofit底层实现的好奇,我阅读了它的源码。现在来聊聊这次读源码的收获吧。

怎么读源码

对于读源码,我认为是一件比较枯燥的事,就像之前读Spring的源码,既佩服它的完美架构,又对如此庞大的代码量感到烦躁。在读了几个开源框架的源码之后,我总结出了读源码的技巧。

1.先大概了解该框架的工作原理

2.带着问题和目标去读

3.记录自己的阅读过程(方法调用流程)

4.按功能或模块去读

在读Retrofit的源码之前,我了解到Retrofit底层是通过封装OkHttp实现的,并且它的解耦方式是通过添加注解实现的。有了这些理论依据。我阅读源码的目标就定下来了。两个问题:1.怎么封装okhttp。2.注解处理和解耦的过程

Retrofit使用示例

public class RetrofitTest {

    public static void main(String[] args) throws Exception{
        //①
        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl("https://**************/")
                .addConverterFactory(GsonConverterFactory.create(new Gson()))
                .build();
        //②
        PersonalProtocol personalProtocol = retrofit.create(PersonalProtocol.class);
        //③
        Call<ResponseBean> call = personalProtocol.login("15*********","123456");
        //④
        Response<ResponseBean> execute = call.execute();
        //⑤
        System.out.println(execute.body());

    }
}

这是一段使用Retrofit完成网络请求的简单示例,业务就是一个登录功能。

探索Retrofit封装之道的旅程就从这段代码开始

①:建立顶级工具类

首先要创建一个Retrofit示例,可以在这个对象中设置baseUrl等参数,还能加入很多第三方的解析器和适配器。上面的代码中我自己加入了JSON解析的解析器,如果相应是JSON结构,可以自动转换为参数提供的对象示例。

还可以加入适配器,可以让操作返回值不在是固定的Response<T>类型。比如使用RxJava,可以让返回值变为Observable<T>,RxJava可以直接操作该对象进行一些方便的异步操作。

对于以上所说的解析器和适配器,不添加也没有关系,Retrofit有自己默认的一套策略,这里就用到了策略模式和适配器模式

在这个顶级工具类中有个变量名为callFactory。这个变量在build()方法中初始化。

 public Retrofit build() {
            if (this.baseUrl == null) {
                throw new IllegalStateException("Base URL required.");
            } else {
                //设置callFactory,如果没有手动第三方callFactory就用OkHttp框架中的OkHttpClient
                Factory callFactory = this.callFactory;
                if (callFactory == null) {
                    callFactory = new OkHttpClient();
                }

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

                //设置适配器,如果没有手动加入第三方适配器就用默认值
                List<retrofit2.CallAdapter.Factory> callAdapterFactories = new ArrayList(this.callAdapterFactories);
                callAdapterFactories.add(this.platform.defaultCallAdapterFactory(callbackExecutor));
                //设置解析器,如果没有手动加入第三方解析器就用默认值
                List<retrofit2.Converter.Factory> converterFactories = new ArrayList(1 + this.converterFactories.size());
                converterFactories.add(new BuiltInConverters());
                converterFactories.addAll(this.converterFactories);
                return new Retrofit((Factory)callFactory, this.baseUrl, Collections.unmodifiableList(converterFactories), Collections.unmodifiableList(callAdapterFactories), callbackExecutor, this.validateEagerly);
            }
        }

看到这里大家就明白了,Retrofit底层默认使用的网络请求工具是OkHttp.。

②:动态代理生成操作对象实例

public interface PersonalProtocol {
    
    @FormUrlEncoded
    @POST("login.action")
    Call<ResponseBean> login(@Field("userphone") String userphone, @Field("password") String password);
}

这是网络请求的具体接口,这里用到了很多注解,每个注解的功能这里就不在赘述了。这里的设计和MyBatis非常像,把动作的描述和动作的执行的分离开来。执行的部分由框架帮我们完成,开发者只需要用特定的方式描述行为动作即可。

直接使用这个接口肯定是不行的,接口无法直接实例化。这里框架会使用反射和动态代理技术帮我们生成具体可以使用的对象。

就在这个代码中完成。retrofit.create(PersonalProtocol.class)

来看看这个代码究竟是怎么做的吧。

public <T> T create(final Class<T> service) {
//只列出核心代码
        return Proxy.newProxyInstance(service.getClassLoader(), new Class[]{service}, new InvocationHandler() {

            public Object invoke(Object proxy, Method method, @Nullable Object[] args) throws Throwable {
                    
                    ServiceMethod<Object, Object> serviceMethod = Retrofit.this.loadServiceMethod(method);
                    OkHttpCall<Object> okHttpCall = new OkHttpCall(serviceMethod, args);
                    return serviceMethod.adapt(okHttpCall);
                }
            }
        });
    }

以上代码并不是全部的方法代码,只是核心代码。create方法确实通过动态代理返回了一个代理对象,这里用到了代理模式。

③:调用代理对象的方法

使用代理对象的方法会调用invoke方法获得一个call对象。熟悉OkHttp框架的同学都觉得这个对象很眼熟,使用它可以进行异步或同步的网络请求,虽然这个call对象和OkHttp的call对象不相等,但是确实是要通过它来进行网络请求的。

接下来看看在invoke里面Retrofit怎么返回call对象。

可以看到在代理对象的invoke方法中有三行代码。这是Retrofit核心的三段代码。

首先 ServiceMethod<Object, Object> serviceMethod = Retrofit.this.loadServiceMethod(method);

这段代码通过执行的method返回了一个ServiceMethod。这个对象很重要。Retrofit框架在invoke方法中使用传入的方法名生成了这个ServiceMethod对象。在ServiceMethod对象中包含了一个网络请求所需的所有参数和对该次请求的描述。

了解ServiceMethod对象分为两个部分。

1.用什么方法生成ServiceMethod对象。

Retrofit.this.loadServiceMethod()这个方法生成

ServiceMethod<?, ?> loadServiceMethod(Method method) {
        ServiceMethod<?, ?> result = (ServiceMethod)this.serviceMethodCache.get(method);
        if (result != null) {
            return result;
        } else {
            //获取Retrofit的ServiceMethod缓存
            Map var3 = this.serviceMethodCache;
            synchronized(this.serviceMethodCache) {
                //从缓存中获取ServiceMethod对象
                result = (ServiceMethod)this.serviceMethodCache.get(method);
                if (result == null) {
                    //如果缓存中没有,就自己创建一个,并加入缓存
                    result = (new retrofit2.ServiceMethod.Builder(this, method)).build();
                    this.serviceMethodCache.put(method, result);
                }

                return result;
            }
        }
    }

这里Retrofit使用了一个ServiceMethod缓存,ServiceMethod作为一个可执行的,动态生成的对象。如果每次调用方法都要根据方法动态生成对应的ServiceMethod对象,那效率也太低了。于是就有了serviceMethodCache,它通过一个map容器把已经生成好的serviceMethod和原始method保存,下次如果这个method再来获取ServiceMethod对象就返回缓存中的值。

如果第一次通过Method获取ServiceMethod对象,则会使用new retrofit2.ServiceMethod.Builder(this, method)).build()这个流程去创建对象,并加入缓存,这里为了防止缓存穿透,用了同步双重检查锁,只能有一个线程去解析生成ServiceMethod,其他线程只能通过缓存获得该ServiceMethod。

2.ServiceMethod这个对象本身

先看看ServiceMethod的成员变量吧。

final class ServiceMethod<R, T> {
    static final String PARAM = "[a-zA-Z][a-zA-Z0-9_-]*";
    static final Pattern PARAM_URL_REGEX = Pattern.compile("\\{([a-zA-Z][a-zA-Z0-9_-]*)\\}");
    static final Pattern PARAM_NAME_REGEX = Pattern.compile("[a-zA-Z][a-zA-Z0-9_-]*");
    private final Factory callFactory;
    private final CallAdapter<R, T> callAdapter;
    private final HttpUrl baseUrl;
    private final Converter<ResponseBody, R> responseConverter;
    private final String httpMethod;
    private final String relativeUrl;
    private final Headers headers;
    private final MediaType contentType;
    private final boolean hasBody;
    private final boolean isFormEncoded;
    private final boolean isMultipart;
    private final ParameterHandler<?>[] parameterHandlers;

    ...
}

光看变量名就有很多熟悉的,Headers,contentType等等都是非常常见的网络请求参数,这里还包括了之前所说的响应解析器和结果适配器等等。可以说ServiceMethod这个类就是进行网络请求直接工具类。

在看看ServiceMethod.Builder(this, method)).build()这行代码。

首先通过Builder来创建一个ServiceMethod对象,都是略过一些变量的初始化工作。

接着看它的build()方法,这是最为核心的一个方法,也是本文主要分析的一个方法。

从字面意思来看build()方法就是返回一个ServiceMethod类型对象,来看看它做了哪些操作。

public ServiceMethod build() {
            //获取适配器
            this.callAdapter = this.createCallAdapter();
            this.responseType = this.callAdapter.responseType();
            //获得相应解析器
            this.responseConverter = this.createResponseConverter();
            //获取方法注解
            Annotation[] var1 = this.methodAnnotations;
            int p = var1.length;
            //处理参数注解
            for(int var3 = 0; var3 < p; ++var3) {
                 Annotation annotation = var1[var3];
                 this.parseMethodAnnotation(annotation);
            }
            //获得形参注解
            int parameterCount = this.parameterAnnotationsArray.length;
            this.parameterHandlers = new ParameterHandler[parameterCount] 
            for(p = 0; p < parameterCount; ++p) {
                  //处理形参注解
                  this.parameterHandlers[p] = this.parseParameter(p, parameterType, parameterAnnotations);
            }
            //全部操作正常结束,返回ServiceMethod实例
            return new ServiceMethod(this);
}

这个方法比较大,我截取了核心部分,大体上只有4块内容。

(1):获取适配器,这里建议大家看看RxJava,这个异步框架现在非常火,和Retrofit搭配也很好用。

(2):获取响应解析器,在最上面的示例代码我传入了Gson解析器用来解析JSON,这里会获取到它。

(3):处理方法注解

(4):处理形参注解

前两块都是一个获取对象的过程,没什么好说的。后两个也没什么好说的,现在基于注解的框架太多了,基本操作都是通过反射获取注解,根据注解加入特定的逻辑。大家有兴趣可以看看方法内部,对注解的使用做了严格的限制,如果我们使用了错误的注解,框架会报错。

现在我们就得到了一个完整的ServiceMethod的对象,里面封装着各种请求网络的操作代码。返回到了invoke方法里边。这里可以停下来看看之前的流程,一下看太多会乱。

继续看invoke方法的代码。

OkHttpCall<Object> okHttpCall = new OkHttpCall(serviceMethod, args);

这个代码又把serviceMethod对象和实参传入,获得一个OkHttpCall对象。

然后通过serviceMethod.adapt(okHttpCall);返回一个Call对象

T adapt(retrofit2.Call<R> call) {
        return this.callAdapter.adapt(call);
}

在adapt方法中使用适配器返回一个Call对象,Retrofit默认的CallAdapter对象的adapt方法会把传入的call对象原封不动的返回。

到此为止invoke方法执行完毕,我们得到了一个Call对象。

④:操作Call对象

现在我们拥有了代理对象返回的Call对象,要注意,这个Call对象和OkHttp框架的Call对象不是同一个。

但是我们也是通过调用它的execute方法或enqueue方法进行同步和异步请求的。

示例代码中使用的同步请求,调用了execute方法。

这里的Call的对象是它的一个实现类OkHttpCall,实在invoke方法中创建的那个。

来看看它的execute方法。

    public retrofit2.Response<T> execute() throws IOException {
        okhttp3.Call call;
        synchronized(this) {
            //execute部分代码
            //获取OkHttp的Call对象       
            call = this.rawCall;
            if (call == null) {
                //如果为空就创建一个
                call = this.rawCall = this.createRawCall();
            }
        }
        //操作OkHttp框架的Call对象,并包装返回值
        return this.parseResponse(call.execute());
    }

这里只贴出了核心代码。

首先获取OkHttp框架里的Call对象,如果没有的话就自己创建一个。

先看看创建Call的过程在继续往下看吧。

private okhttp3.Call createRawCall() throws IOException {
        //核心代码
        okhttp3.Call call = this.serviceMethod.toCall(this.args);
        return call;
 }

调用了ServiceMethod类中的toCall方法。

Call toCall(@Nullable Object... args) throws IOException {
        //创建一个RequestBuilder对象
        RequestBuilder requestBuilder = new RequestBuilder(this.httpMethod, this.baseUrl, this.relativeUrl, this.headers, this.contentType, this.hasBody, this.isFormEncoded, this.isMultipart);
    
        ParameterHandler<Object>[] handlers = this.parameterHandlers;
        int argumentCount = args != null ? args.length : 0;
        if (argumentCount != handlers.length) {
            throw new IllegalArgumentException("Argument count (" + argumentCount + ") doesn't match expected count (" + handlers.length + ")");
        } else {
            for(int p = 0; p < argumentCount; ++p) {
                //把header设置到requestBuilder中
                handlers[p].apply(requestBuilder, args[p]);
            }
            //返回Call对象
            return this.callFactory.newCall(requestBuilder.build());
        }
    }

之前说过ServiceMethod中存有一个请求的所有参数和这个请求的描述,这里利用ServiceMethod其中的参数去初始化Call。

这里要先建立一个RequestBuilder去生成Request。不知道大家还记不记得OkHttp中Call的生成方法,是通过一个request对象生成的,这里也不例外。可以看看requestBuilder.build()方法。

Request build() {
        Builder urlBuilder = this.urlBuilder;
        HttpUrl url;
        if (urlBuilder != null) {
            //设置URL
            url = urlBuilder.build();
        } else {
            url = this.baseUrl.resolve(this.relativeUrl);
            if (url == null) {
                throw new IllegalArgumentException("Malformed URL. Base: " + this.baseUrl + ", Relative: " + this.relativeUrl);
            }
        }
        //设置请求体
        RequestBody body = this.body;
        if (body == null) {
            if (this.formBuilder != null) {
                body = this.formBuilder.build();
            } else if (this.multipartBuilder != null) {
                body = this.multipartBuilder.build();
            } else if (this.hasBody) {
                body = RequestBody.create((MediaType)null, new byte[0]);
            }
        }
        //设置ContentType
        MediaType contentType = this.contentType;
        if (contentType != null) {
            if (body != null) {
                body = new RequestBuilder.ContentTypeOverridingRequestBody((RequestBody)body, contentType);
            } else {
                this.requestBuilder.addHeader("Content-Type", contentType.toString());
            }
        }
        //返回OkHttp中的request
        return this.requestBuilder.url(url).method(this.method, (RequestBody)body).build();
    }

build()方法中帮我们实例化了一个Request并返回。

之后再利用CallFactory对象的newCall方法创建一个Call对象。

一开始的时候说过如果我们没有手动设置CallFactory的话,Retrofit的默认CallFactory就是OkHttpClient。这一块的代码其实Retrofit通过之前解析注解得到的信息帮我们创建了一个Request对象,然后通过内置的OkHttpClient封装了一个Call对象。

这里一路回到OkHttpCall的execute方法中,现在我们有了名正言顺的OkHttp的Call对象了。

最下边的一行代码this.parseResponse(call.execute())

这里call.execute()这个就不说了,OkHttp框架的同步请求,返回一个response对象。

然后parseResponse会对请求的结果进行一个判断,如果请求成功了就调用解析器把结果解析为设定的类型,并包装为Retrofit的Response类型返回。至此一个网络请求结束。

⑤:打印结果

这个没什么说的,这里我使用的Idea去解读源码的,因此就用System.out.println打印结果了。

小结

Retrofit的源码分析到这里也完结了。Retrofit请求网络的方式千变万化,我只分析其中一个,只是想让大家明白Retrofit的基本工作原理。简单概括就是通过动态代理生成代理对象,在invoke方法里通过方法上的各种注解解析出该次请求的各种参数并保存在ServiceMethod中,然后通过ServiceMethod生成Retrofit的Call对象,在这个Call的execute方法中会通过ServiceMethod保存的请求信息来构建一个OkHttp框架里的Call对象,然后操作这个Call对象来完成网络请求。

开篇在读源码之前是由两个目标的,相信大家现在也明白了Retrofit是怎么封装OkHttp的,也了解它解析注解和解耦的过程。

最后谢谢大家能读到这里,源码分析我也是最近才开始写,写的不好的地方希望大家多多包涵。

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值