惠州网站小程序建设,sem培训班学费哪个好,美食推广平台有哪些,企业网络托管公司前言
在实际项目中往往是使用Retrofit来做网络请求工作。Retrofit采用RESTful风格#xff0c;本质上只是对OkHttp进行封装#xff0c;今天我们根据几个问题来进一步学习一下Retrofit的源码与设计思想。
1. 使用方法
直接看一下官方介绍的使用方法。
public final class S…前言
在实际项目中往往是使用Retrofit来做网络请求工作。Retrofit采用RESTful风格本质上只是对OkHttp进行封装今天我们根据几个问题来进一步学习一下Retrofit的源码与设计思想。
1. 使用方法
直接看一下官方介绍的使用方法。
public final class SimpleService {public static final String API_URL https://api.github.com;public static class Contributor {public final String login;public final int contributions;public Contributor(String login, int contributions) {this.login login;this.contributions contributions;}}public interface GitHub {GET(/repos/{owner}/{repo}/contributors)CallListContributor contributors(Path(owner) String owner, Path(repo) String repo);}public static void main(String... args) throws IOException {// Create a very simple REST adapter which points the GitHub API.Retrofit retrofit new Retrofit.Builder().baseUrl(API_URL).client(new OkHttpClient().newBuilder().connectTimeout(10, TimeUnit.SECONDS).build()).addConverterFactory(GsonConverterFactory.create()).build();// Create an instance of our GitHub API interface.GitHub github retrofit.create(GitHub.class);// Create a call instance for looking up Retrofit contributors.CallListContributor call github.contributors(square, retrofit);// Fetch and print a list of the contributors to the library.ListContributor contributors call.execute().body();for (Contributor contributor : contributors) {System.out.println(contributor.login ( contributor.contributions ));}}
}可以简单的概括成三步
构建 retrofit 实例。构建 API 接口实例。执行请求解析响应。
2. 流程解析
我们按照它的使用方法来分析一下它的流程。
2.1 构建 Retrofit 实例
从使用方法可以看出是使用建造者模式来构建实例。
Retrofit retrofit new Retrofit.Builder().baseUrl(API_URL).client(new OkHttpClient().newBuilder().connectTimeout(10, TimeUnit.SECONDS).build()).addConverterFactory(GsonConverterFactory.create()).addCallAdapterFactory(RxJavaCallAdapterFactory.create()).build();这一步就不具体展开了看几个参数。
public static final class Builder {//实际的请求调用如 okhttp3.OkHttpClientprivate Nullable okhttp3.Call.Factory callFactory;//基础URL如域名private Nullable HttpUrl baseUrl;//数据转换器列表private final ListConverter.Factory converterFactories new ArrayList();//请求适配器列表private final ListCallAdapter.Factory callAdapterFactories new ArrayList();2.2 构建 API 接口实例
按照官方的使用方法介绍我们会将我们的API方法放在一个接口中然后通过注解来设置请求参数。在使用时通过retrofit.create(ClassT)方法将这个接口实例化然后调用其方法。 如
public interface GitHub {GET(/repos/{owner}/{repo}/contributors)CallListContributor contributors(Path(owner) String owner, Path(repo) String repo);
}//实例化API接口
GitHub github retrofit.create(GitHub.class);
//调用接口中某条API
CallListContributor call github.contributors(square, retrofit);看一下源码
public T T create(final ClassT service) {//验证 api servicevalidateServiceInterface(service);return (T)//这里采用了动态代理模式 service 就是被代理类//todo 为什么要采用动态代理有什么好处吗用别的行不行Proxy.newProxyInstance(service.getClassLoader(),new Class?[] {service},new InvocationHandler() {private final Object[] emptyArgs new Object[0];Overridepublic 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;Platform platform Platform.get();//如果不是系统默认方法通过loadServiceMethod()方法返回一个ServiceMethod并调用invoke方法return platform.isDefaultMethod(method)? platform.invokeDefaultMethod(method, service, proxy, args): loadServiceMethod(method).invoke(args);}});}做了两件事
验证我们的API接口类。利用动态代理在运行期间实例化API接口。 private void validateServiceInterface(Class? service) {//service 必须是 interface否则抛出异常if (!service.isInterface()) {throw new IllegalArgumentException(API declarations must be interfaces.);}...省略代码...//是否立即验证API接口中的所有方法由用户设置默认为falseif (validateEagerly) {Platform platform Platform.get();//遍历 service 中定义的所有方法for (Method method : service.getDeclaredMethods()) {//如果该方法不是系统默认方法且方法修饰符不是静态方法就执行loadServiceMethod方法if (!platform.isDefaultMethod(method) !Modifier.isStatic(method.getModifiers())) {//加载请求方法。loadServiceMethod(method);}}}}从这我们也可以看出我们的API方法必须方法接口中。如果开始验证接口会遍历其声明的所有方法过滤掉系统默认方法与静态方法然后执行loadServiceMethod(method)。 扩充一下 getMethods(): 返回由类或接口声明的以及从超类和超接口继承的所有公共方法。 getDeclaredMethods(): 返回类声明的方法包括 public, protected, default (package)但不包括继承的方法。所以相对比于 getMethods 方法getDeclaredMethods速度更快尤其是在复杂的类中如在Activity类中。 最终都是通过loadServiceMethod(method) 方法来加载一个ServiceMethod。
看一下HttpServiceMethod.parseAnnotations()方法我将其简化了一下如下
HttpServiceMethod.javastatic ResponseT, ReturnT HttpServiceMethodResponseT, ReturnT parseAnnotations(Retrofit retrofit, Method method, RequestFactory requestFactory) {//获取方法的注解信息Annotation[] annotations method.getAnnotations();//适配器类型就是Retrofit.addCallAdapterFactory()添加的类型。Type adapterType;//方法的返回类型adapterType method.getGenericReturnType();//实例化一个 CallAdapter 对象CallAdapterResponseT, ReturnT callAdapter createCallAdapter(retrofit, method, adapterType, annotations);//检查 responseType如果不合格则抛出异常Type responseType callAdapter.responseType();//实例化一个Converter对象将 okhttp3.ResponseBody 转换成 ResponseT 类型ConverterResponseBody, ResponseT responseConverter createResponseConverter(retrofit, method, responseType);okhttp3.Call.Factory callFactory retrofit.callFactory;//不是kotlin挂起方法返回 CallAdapted其实也就是调用 callAdapter.adapter 方法return new CallAdapted(requestFactory, callFactory, responseConverter, callAdapter);}实例化了 ServiceMethod 后调用invoke方法。
HttpServiceMethod.javaOverridefinal Nullable ReturnT invoke(Object[] args) {//新建一个 OkHttpCall 请求CallResponseT call new OkHttpCall(requestFactory, args, callFactory, responseConverter);//然后调用 adapt 方法CallAdapted 有重写 adapt 方法然后调用 callAdapter.adapt(call) 方法return adapt(call, args);}protected abstract Nullable ReturnT adapt(CallResponseT call, Object[] args);从上述代码中可以看出invoke方法就是实例化一个Call请求然后调用adapter方法在这里adapter是一个抽象方法所以具体实现方法就需要看它的具体实现类CallAdapter。 这里的 CallAdapter 就是通过.addCallAdapterFactory()方法所添加的CallAdapter以及根据平台默认提供的DefaultCallAdapterFactory中的CallAdapter执行其adapter方法最终返回CallObject。
2.3 执行请求解析响应
在上一步中我们对API接口进行了实例化通过CallAdapter对请求进行适配最终得到一个CallObject对象。
接着下一步就是执行这个CallObject请求最终得到我们想要的Object对象。
例如一开始使用方法中所介绍的
//已经得到了CallListContributor对象执行call得到ListContributor
ListContributor contributors call.execute().body();调用 execute 执行同步请求获取到Response然后获取其请求体。
OkHttpCall.javaOverridepublic ResponseT execute() throws IOException {okhttp3.Call call;synchronized (this) {//判断请求是否已经被执行如果已被执行则抛出异常if (executed) throw new IllegalStateException(Already executed.);executed true;//获取最原始的请求通过createRawCall()创建okhttp3.Callcall getRawCall();}if (canceled) {call.cancel();}//执行请求并且解析响应将okhttp3.response 转换成 retrofit2.responsereturn parseResponse(call.execute());}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;}/*** 解析响应就是就okhttp3.response 转换成 retrofit2.response*/ResponseT parseResponse(okhttp3.Response rawResponse) throws IOException {...省略代码...try {//利用converter转换成我们期望的类型T body responseConverter.convert(catchingBody);return Response.success(body, rawResponse);} catch (RuntimeException e) {...省略代码...}从源码中也可以看出请求的实际工作还是通过okhttp来完成的这边Retrofit就是负责请求与响应转换工作将retrofit2.Call转换成okhttp3.Call将okhttp3.response转换成retrofit2.response。
3. 为什么要引入CallAdapter与Converter?
如果你熟悉okHttp的话你应该知道当我们请求的时候要先通过OkHttpClient.newCall(request)方法将request转换成Call对象然后再执行这个Call对象拿到response。
但是Retrofit不光光只支持Call他还可以将请求适配成Observable类型方便与RxJava2结合起来一起使用。这就是通过CallAdapter来进行适配工作的例如通过默认的DefaultCallAdapterFactory将请求转换成CallObject通过RxJava2CallAdapter将请求转换成ObservableObject。
回到okHttp大部分业务情况下我们在拿到响应体后都会将其进行反序列化成对象方便调用。显然Retrofit就考虑到了这一点所以他默认提供了GsonConverterFactory来帮助我们做这一步反序列化工作。这就是通过Converter来完成的同时它也支持用户进行自定义。
4. CallAdapter 是如何工作的
作为请求适配器我们将CallAdapter工作流程分为三步添加、匹配、工作。
添加
可以通过addCallAdapterFactory(CallAdapter.Factory)方法来添加请求适配器工厂类添加成功后会被保存在callAdapterFactories列表中。另外Retrofit会根据Platform来添加默认的请求适配器例如DefaultCallAdapterFactory等等同样也加入到callAdapterFactories列表中。
匹配
思考一下所有添加的请求适配器都会被保存在callAdapterFactories列表中那在实际请求中是如何匹配出相对应的适配器的呢
在HttpServiceMethod.parseAnnotations()方法中我们有实例化一个CallAdapter对象。(具体流程就不再次展开了请回头看 2.2 构建 API 接口实例 中所介绍内容。)
HttpServiceMethod.javastatic ResponseT, ReturnT HttpServiceMethodResponseT, ReturnT parseAnnotations(Retrofit retrofit, Method method, RequestFactory requestFactory) {//实例化一个 CallAdapter 对象CallAdapterResponseT, ReturnT callAdapter createCallAdapter(retrofit, method, adapterType, annotations);···省略代码···//不是kotlin挂起方法返回 CallAdapted其实也就是调用 callAdapter.adapter 方法return new CallAdapted(requestFactory, callFactory, responseConverter, callAdapter);}匹配工作其实就在createCallAdapter()方法中一步步走下来最终到Retrofit.nextCallAdapter()方法中
Retrofit.javapublic 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) {//通过方法的返回值类型与注解信息来找到匹配的CallAdapterCallAdapter?, ? adapter callAdapterFactories.get(i).get(returnType, annotations, this);if (adapter ! null) {return adapter;}}···省略代码···//如果找不到匹配的CallAdapter则抛出异常throw new IllegalArgumentException(builder.toString());}简单概括一下就是通过方法的返回值类型与注解信息遍历callAdapterFactories列表找到匹配的CallAdapter如果找不到则抛出IllegalArgumentException异常。
工作
找到匹配的CallAdapter后剩下就是看看他是如何工作的。
如上一步匹配过程所介绍在找到匹配的callAdapter后会通过它来实例化一个CallAdapted对象。 static final class CallAdaptedResponseT, ReturnT extends HttpServiceMethodResponseT, ReturnT {private final CallAdapterResponseT, ReturnT callAdapter;CallAdapted(RequestFactory requestFactory,okhttp3.Call.Factory callFactory,ConverterResponseBody, ResponseT responseConverter,CallAdapterResponseT, ReturnT callAdapter) {//将responseConverter传给父类。super(requestFactory, callFactory, responseConverter);this.callAdapter callAdapter;}Overrideprotected ReturnT adapt(CallResponseT call, Object[] args) {return callAdapter.adapt(call);}}CallAdapted很简单就是继承了HttpServiceMethod然后复写了adapt方法。也就是说最终执行的其实就是我们上一步匹配到的CallAdapter对象的adapt方法。
比如匹配到的是DefaultCallAdapterFactory中的CallAdapter最终执行的就是其adapt方法具体代码细节这边就不展示了有兴趣同学请自行查阅。
另外我这边展示的是不支持kotlin挂起函数的情况当然即使是kotlin挂起函数过程也是一样的也是执行其子类的adapt方法。 5. Converter 是如何工作的
作为数据转换器我们同样将Converter工作流程分为三步添加、匹配、工作。
添加
可以通过addConverterFactory(Converter.Factory)方法来添加数据装换器工厂类添加成功后会被保存在converterFactories列表中。另外Retrofit会根据Platform来添加默认的数据转换器例如OptionalConverterFactory同样也加入到converterFactories列表中。
匹配
跟上述所介绍的 4. CallAdapter 是如何工作的 一样同样在HttpServiceMethod.parseAnnotations()方法中会实例化一个Converter对象。
匹配工作其实就在createResponseConverter()方法中一步步走下来最终到Retrofit.nextResponseBodyConverter()方法中 public T ConverterResponseBody, T nextResponseBodyConverter(Nullable Converter.Factory skipPast, Type type, Annotation[] annotations) {int start converterFactories.indexOf(skipPast) 1;for (int i start, count converterFactories.size(); i count; i) {//通过转换类型与注解信息来找到匹配的ConverterConverterResponseBody, ? converter converterFactories.get(i).responseBodyConverter(type, annotations, this);if (converter ! null) {//noinspection uncheckedreturn (ConverterResponseBody, T) converter;}}···省略代码···//如果找不到匹配的Converter则抛出异常throw new IllegalArgumentException(builder.toString());}简单概括一下就是通过转换类型与注解信息遍历converterFactories列表找到匹配的Converter如果找不到则抛出IllegalArgumentException异常。
工作
跟上述所介绍的 4. CallAdapter 是如何工作的 一样我们找到匹配的Converter后通过它来实例化一个CallAdapted。但不同的是我们会将responseConverter传给父类也就是HttpServiceMethod然后当其调用invoke方法时我们通过responseConverter来实例化一个OkHttpCall对象最终将这个OkHttpCall对象传给adapter方法执行。
最终当执行请求时OkHttpCall执行parseResponse来解析响应调用responseConverter.convert()方法将ResponseBody数据转换我们想要的类型。
6. 说说使用到了哪些设计模式
动态代理模式
Retrofit内部通过动态代理反射来拿到用户定义在接口中的请求参数从而来构建实际请求。具体细节这边就不再次展开了可以回头查看 2.2 构建 API 接口实例 这一部分内容。
为什么要使用动态代理来获取API方法
不知道你们有没有这个疑问为什么我们的API方法需要定义在interface中呢又为什么要通过动态代理反射的形式来拿到请求参数呢
Retrofit按照RESTful风格设计并通过注解来定义API方法的请求参数并将这些API方法放到interface中。因为interface是不能被实例化的所以这里采用动态代理在运行期间实例化API接口获取到方法的请求参数。
再进一步
解耦将实际业务与Retrofit隔离开来。用户只需通过注解方法来定义请求参数而实际请求的构建则通过Retrofit内部来实现。
此时再反过来看为何放在interface中相信你心中已有答案了吧。
策略模式
当针对同一类型问题有多种处理方式仅仅是具体行为有差别时就可以使用策略模式。
例如Retrofit中的请求适配器
抽象策略CallAdapter。策略具体实现DefaultCallAdapterFactory.get()、RxJava2CallAdapter。
即提供默认的请求适配器也支持用户自定义符合开闭原则达到很好的可扩展性。
适配器模式
Retrofit会帮我们构建实际请求内部通过默认的DefaultCallAdapterFactory来将请求转换成CallObject同时Retrofit也支持其它平台比如为了适配RxJava特性将请求转换成ObservableObject。
Target(目标角色): CallObject, ObservableObject。adaptee(需要适配的对象): OkHttpCall。adapter(适配器)DefaultCallAdapterFactory.get()、RxJava2CallAdapter。
工厂方法模式
我们以Converter来举例。
抽象工厂Converter.Factory。具体工厂GsonConverterFactory、BuiltInConverters等等。抽象产品Converter。具体产品GsonResponseBodyConverter、GsonRequestBodyConverter、ToStringConverter等等。
这边就不具体展开分析各个类了有兴趣的同学可自行查阅。
建造者模式
在构建Retrofit实例的时候就用到了建造者模式。建造者模式在开源库中的出现的次数真的很频繁为了适配不同的用户的各种需求需提供各种各样的参数与方法来供用户自行选择所以使用建造者模式之所以很常见是因为这样很合理。
7. 使用过程中踩过什么坑
关于BaseUrl的使用曾经踩过坑某天我将baseUrl改了一下然后发现请求接口时服务器一直返回404但是当我尝试用Postman去调试接口的时候发现接口是好的也就推测出来是我的代码出问题了。
最终发现问题出在拼接成完整的URL时api被删除了。
Base URL: http://example.com/api/
Endpoint: /foo/bar/
Result: http://example.com/foo/bar/正确的使用方式为Endpoint不以斜杠开头。
Base URL: http://example.com/api/
Endpoint: foo/bar/
Result: http://example.com/api/foo/bar/总结
本文我们以几个问题的形式展开来对Retrofit源码及设计思想进行解析相信你对源码有了进一步的了解。Retrofit本质只是对okHttp进行封装出发点肯定是让网络请求变得更加容易考虑适配各种用户需求Jake Wharton大神用了很多设计模式真的太让人膜拜了。
到此关于Retrofit的源码解析就结束啦。
推荐更多Android学习笔记参考
Android 性能优化篇https://qr18.cn/FVlo89 Android 车载篇https://qr18.cn/F05ZCM Android 逆向安全学习笔记https://qr18.cn/CQ5TcL Android Framework底层原理篇https://qr18.cn/AQpN4J Android 音视频篇https://qr18.cn/Ei3VPD Jetpack全家桶篇内含Composehttps://qr18.cn/A0gajp Kotlin 篇https://qr18.cn/CdjtAF Gradle 篇https://qr18.cn/DzrmMB OkHttp 源码解析笔记https://qr18.cn/Cw0pBD Flutter 篇https://qr18.cn/DIvKma Android 八大知识体https://qr18.cn/CyxarU Android 核心笔记https://qr21.cn/CaZQLo Android 往年面试题锦https://qr18.cn/CKV8OZ 2023年最新Android 面试题集https://qr18.cn/CgxrRy Android 车载开发岗位面试习题https://qr18.cn/FTlyCJ 音视频面试题锦https://qr18.cn/AcV6Ap