技术标签: android
Retrofit 是一个 HTTP 网络请求框架的封装,网络请求的工作本质上是 OkHttp 完成,而 Retrofit 仅负责 网络请求接口的封装,所以你可以将Retrofit 看成是一个对OkHttp 封装的网络请求框架。
本篇文章也不但算介绍Retrofit 的基本用法,而是研究一下Retrofit源码,知其然也要知其所以然。
public interface NetInterface {
@GET("getSomething")
Call<String> getCall(@Field("name") String name);
}
public void testRetrofix(){
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("http://xxxx.com/") //设置网络请求的Url地址
.addConverterFactory(GsonConverterFactory.create()) //设置数据解析器
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.build();
retrofit.create(TestRetrofit.NetInterface.class);
}
首先build 的过程这里先不做分析。其仅仅是配置一些参数而已,在遇到这些参数的时候我们再讲。
retrofit.create(TestRetrofit.NetInterface.class); 创建一个NetInterface 的实例,我们知道NetInterface 是一个接口,其本身是不可以直接实例化的,这里实际是通过Java的动态代理创建了一个实现这个接口的类,Java创建的这个类虽然实现了这个接口,接口里面的每一个方法的实现都是通过InvocationHandler 通过用户哪一个方法被调用了,关于动态代理生成的代理我们放到最下面介绍,下面我们先看看create 的实现。
public <T> T create(final Class<T> service) {
Utils.validateServiceInterface(service);
if (validateEagerly) {
eagerlyValidateMethods(service);
}
//newProxyInstance 创建一个动态代理类
return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] {
service },
new InvocationHandler() {
private final Platform platform = Platform.get();
//method 表示被调用的方法,args 表示传递给这个方法的参数
@Override public Object invoke(Object proxy, Method method, Object... args)
throws Throwable {
// If the method is a method from Object then defer to normal invocation.
if (method.getDeclaringClass() == Object.class) {
//例如 调用toString equal 等Object 的方法绘
return method.invoke(this, args);
}
//android 这里返回false
if (platform.isDefaultMethod(method)) {
return platform.invokeDefaultMethod(method, service, proxy, args);
}
//这个是核心,放到后面将
ServiceMethod serviceMethod = loadServiceMethod(method);
//创建OKHttpCall
OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args);
return serviceMethod.callAdapter.adapt(okHttpCall);
}
});
}
newProxyInstance 的第一个参数是classloader,这里不用过于关注这个对象,只需要知道这是用来加载类的就可以了,第二个参数是个数组,表示动态生成的类需要实现的接口,第三个参数InvocationHandler是一个回调,Java 动态生成的这个代理类会在每一个方法里面都调用InvocationHandler 的invoke 方法通知当前调用的方法(还有不清楚的参见文章的最后)。
例如
TestRetrofit.NetInterface netInterface= retrofit.create(TestRetrofit.NetInterface.class);
//调用
netInterface.getCall("test");
netInterface 就是动态生成的代理类,这个代理类的getCall 方法里面就是直接调用InvocationHandler 的invoke 方法,其余的啥也没干,因此核心内容都在invoke 里面。
invoke内部首先判断用户调用的方法是不是定义在Object 类里面方法,也即是判断调用的是不是equal ,haseCode 等方法,
platform.isDefaultMethod(method) 与系统有关,这里是false,因此无需考虑。
loadServiceMethod 内部主要是通过反射获取当前调用的方法的一些信息,例如形参以及返回类型,然后根据参数类型与注解进行一些处理。
ServiceMethod loadServiceMethod(Method method) {
ServiceMethod result;
synchronized (serviceMethodCache) {
//
result = serviceMethodCache.get(method);
if (result == null) {
//第一次调用会走这里
result = new ServiceMethod.Builder(this, method).build();
serviceMethodCache.put(method, result);
}
}
return result;
}
serviceMethodCache 是ServiceMethod的缓存,因为创建ServiceMethod 会通过反射获取调用方法的信息,我们知道Java的反射存在效率的问题,因此这里使用了缓存。
第一次调用某个方法的时候会创建一个对应的ServiceMethod
```java
//代码有删减
public ServiceMethod build() {
//创建callAdapter,
callAdapter = createCallAdapter();
//responseType 代表调用方法的返回值的泛型
responseType = callAdapter.responseType();
//泛型不可以是Response类型
if (responseType == Response.class || responseType == okhttp3.Response.class) {
responseConverter = createResponseConverter();
//methodAnnotations 是方法的注解,例如GET.POST
for (Annotation annotation : methodAnnotations) {
parseMethodAnnotation(annotation);
}
//parameterAnnotationsArray 是方法的参数上的注解,例如getCall 的
// 第一个参数的注解Field
int parameterCount = parameterAnnotationsArray.length;
parameterHandlers = new ParameterHandler<?>[parameterCount];
for (int p = 0; p < parameterCount; p++) {
Type parameterType = parameterTypes[p];
Annotation[] parameterAnnotations = parameterAnnotationsArray[p];
parameterHandlers[p] = parseParameter(p, parameterType, parameterAnnotations);
}
return new ServiceMethod<>(this);
}
这里结合
@GET(“getSomething”)
Call getCall(@Field(“name”) String name)来解释一个这个方法的内容。
createCallAdapter 会根据getCall的返回类型也就是Call 以及方法的注解也就是 @GET(“getSomething”) 获取一个CallAdaper。CallAdaper作用是将前文中的OkHttpCall 转换为函数的返回类型也即是Call。OkHttpCall 可以简单地看成是对OKhHttp的Call的一层封装,通过OkHttpCall 直接访问网络,返回数据。
下面我们看看createCallAdapter 的实现
private CallAdapter<?> createCallAdapter() {
//获取方法的返回类型,这里还是以getCall为例,
//returnType 表示Call<String>
Type returnType = method.getGenericReturnType();
if (Utils.hasUnresolvableType(returnType)) {
//例如Call<T> 由于T 是不可解析的类型,这里会报错,此处Call的泛型必须是一个
//具体的类
throw methodError(
"Method return type must not include a type variable or wildcard: %s", returnType);
}
//必须有返回类型
if (returnType == void.class) {
throw methodError("Service methods cannot return void.");
}
Annotation[] annotations = method.getAnnotations();
try {
//根据返回类型与注解获取对应的CallAdapter
return retrofit.callAdapter(returnType, annotations);
} catch (RuntimeException e) {
// Wide exception range because factories are user code.
throw methodError(e, "Unable to create call adapter for %s", returnType);
}
}
上面的注释已经很清楚了,下面进入 retrofit.callAdapter(returnType, annotations); 看看
public CallAdapter<?> callAdapter(Type returnType, Annotation[] annotations) {
return nextCallAdapter(null, returnType, annotations);
}
public CallAdapter<?> nextCallAdapter(CallAdapter.Factory skipPast, Type returnType,
Annotation[] annotations) {
//
int start = adapterFactories.indexOf(skipPast) + 1;
for (int i = start, count = adapterFactories.size(); i < count; i++) {
CallAdapter<?> adapter = adapterFactories.get(i).get(returnType, annotations, this);
if (adapter != null) {
return adapter;
}
}
//代码有删减
}
adapterFactories 保存着创建Retrofit的时候注册的CallAdapter.Factory,遍历所有的CallAdapter.Factory 的get 方法 判断能否处理用户调用的这个方法,注意这里找到第一个可用的Factory 就不会继续向下找了。
由于RxJavaCallAdapterFactory 的实现略显复杂,我们这里以默认的CallAdapter.Factory为例子
final class DefaultCallAdapterFactory extends CallAdapter.Factory {
static final CallAdapter.Factory INSTANCE = new DefaultCallAdapterFactory();
@Override
//第一个参数
public CallAdapter<?> get(Type returnType, Annotation[] annotations, Retrofit retrofit) {
if (getRawType(returnType) != Call.class) {
return null;
}
//还是以上面的getCall 为例,returnType 表示Call<String>
//这里getCallResponseType 就是获取Call的泛型也就是String
final Type responseType = Utils.getCallResponseType(returnType);
return new CallAdapter<Call<?>>() {
@Override public Type responseType() {
return responseType;
}
@Override public <R> Call<R> adapt(Call<R> call) {
return call;
}
};
}
}
也就是每一个返回值类型都必须有自己的CallAdapterFactory. 创建完了
在createCallAdapter 之后又调用了createResponseConverter以及处理方法形式参数的注解,
我们这里不分析形式参数的注解的处理,例如Filed 注解,这些都是根据固定的规则处理,有感兴趣的可以自行分析。此处只分析createResponseConverter,
createResponseConverter 返回一个Converter<ResponseBody, T> 对象,这个对象就是将ResponseBody类型转换了T类型,我们知道OKHttp 访问网络返回对象就是ResponseBody类型,而getCal 返回的Call的泛型是String, 这里就需要将ResponseBody 转换为String
private Converter<ResponseBody, T> createResponseConverter() {
Annotation[] annotations = method.getAnnotations();
try {
return retrofit.responseBodyConverter(responseType, annotations);
} catch (RuntimeException e) {
// Wide exception range because factories are user code.
throw methodError(e, "Unable to create converter for %s", responseType);
}
}
retrofit.responseBodyConverter 就不展开看了,其与retrofit.callAdapter(returnType, annotations)的实现类似。
这里以GsonConverterFactory 为例子
private GsonConverterFactory(Gson gson) {
if (gson == null) throw new NullPointerException("gson == null");
this.gson = gson;
}
@Override
public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations,
Retrofit retrofit) {
TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type));
return new GsonResponseBodyConverter<>(gson, adapter);
}
@Override
public Converter<?, RequestBody> requestBodyConverter(Type type,
Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {
TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type));
return new GsonRequestBodyConverter<>(gson, adapter);
}
TestRetrofit.NetInterface netInterface= retrofit.create(TestRetrofit.NetInterface.class);
retrofit2.Call call=netInterface.getCall("test");
call.enqueue(new retrofit2.Callback() {
@Override
public void onResponse(retrofit2.Call call, retrofit2.Response response) {
}
@Override
public void onFailure(retrofit2.Call call, Throwable t) {
}
});
netInterface.getCall 创建Call的过程已经讲完了,下面我们看看具体访问网络以及获取数据之后转换的过程。
这个Call 实际是OkHttpCall
@Override public void enqueue(final Callback<T> callback) {
if (callback == null) throw new NullPointerException("callback == null");
okhttp3.Call call;
Throwable failure;
synchronized (this) {
if (executed) throw new IllegalStateException("Already executed.");
executed = true;
call = rawCall;
failure = creationFailure;
if (call == null && failure == null) {
try {
//创建一个OKHTTP 的Call 也就是RealCall
call = rawCall = createRawCall();
} catch (Throwable t) {
failure = creationFailure = t;
}
}
}
call.enqueue(new okhttp3.Callback() {
@Override public void onResponse(okhttp3.Call call, okhttp3.Response rawResponse)
throws IOException {
Response<T> response;
try {
response = parseResponse(rawResponse);
} catch (Throwable e) {
callFailure(e);
return;
}
callSuccess(response);
}
//部分回调被删除
private void callSuccess(Response<T> response) {
try {
callback.onResponse(OkHttpCall.this, response);
} catch (Throwable t) {
t.printStackTrace();
}
}
});
}
可以看到这里实际就是创建一个OKHTTP的Call,然后通过这个Call 访问网络,对于OKHTTP这一部分不熟悉的可以参考我前面的文章。
当网络返回数据成功之后会调用parseResponse 来进行数据转换。
Response<T> parseResponse(okhttp3.Response rawResponse) throws IOException {
ResponseBody rawBody = rawResponse.body();
// Remove the body's source (the only stateful object) so we can pass the response along.
rawResponse = rawResponse.newBuilder()
.body(new NoContentResponseBody(rawBody.contentType(), rawBody.contentLength()))
.build();
//代码有删减
ExceptionCatchingRequestBody catchingBody = new ExceptionCatchingRequestBody(rawBody);
try {
//ServiceMethod 参见前面的创建过程
T body = serviceMethod.toResponse(catchingBody);
return Response.success(body, rawResponse);
} catch (RuntimeException e) {
// If the underlying source threw an exception, propagate that rather than indicating it was
// a runtime exception.
catchingBody.throwIfCaught();
throw e;
}
}
/** Builds a method return value from an HTTP response body. */
T toResponse(ResponseBody body) throws IOException {
return responseConverter.convert(body);
}
responseConverter 就是前面的createResponseConverter 内部创建的ResponseConverter ,也就是GsonResponseBodyConverter,我们看看GsonResponseBodyConverter 的conver 是将ResponseBody 转换为String类型。
//
TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type));
@Override public T convert(ResponseBody value) throws IOException {
JsonReader jsonReader = gson.newJsonReader(value.charStream());
try {
//
return adapter.read(jsonReader);
} finally {
value.close();
}
}
到了这里一切就很显然了,convert 方法内通过Gson 将服务器返回的json数据转换了一个对象。
文章浏览阅读3.4k次,点赞8次,收藏42次。一、什么是内部类?or 内部类的概念内部类是定义在另一个类中的类;下面类TestB是类TestA的内部类。即内部类对象引用了实例化该内部对象的外围类对象。public class TestA{ class TestB {}}二、 为什么需要内部类?or 内部类有什么作用?1、 内部类方法可以访问该类定义所在的作用域中的数据,包括私有数据。2、内部类可以对同一个包中的其他类隐藏起来。3、 当想要定义一个回调函数且不想编写大量代码时,使用匿名内部类比较便捷。三、 内部类的分类成员内部_成员内部类和局部内部类的区别
文章浏览阅读118次。分布式系统要求拆分分布式思想的实质搭配要求分布式系统要求按照某些特定的规则将项目进行拆分。如果将一个项目的所有模板功能都写到一起,当某个模块出现问题时将直接导致整个服务器出现问题。拆分按照业务拆分为不同的服务器,有效的降低系统架构的耦合性在业务拆分的基础上可按照代码层级进行拆分(view、controller、service、pojo)分布式思想的实质分布式思想的实质是为了系统的..._分布式系统运维工具
文章浏览阅读174次。1.数据源准备2.数据处理step1:数据表处理应用函数:①VLOOKUP函数; ② CONCATENATE函数终表:step2:数据透视表统计分析(1) 透视表汇总不同渠道用户数, 金额(2)透视表汇总不同日期购买用户数,金额(3)透视表汇总不同用户购买订单数,金额step3:讲第二步结果可视化, 比如, 柱形图(1)不同渠道用户数, 金额(2)不同日期..._exce l趋势分析数据量
文章浏览阅读3.3k次。堡垒机可以为企业实现服务器、网络设备、数据库、安全设备等的集中管控和安全可靠运行,帮助IT运维人员提高工作效率。通俗来说,就是用来控制哪些人可以登录哪些资产(事先防范和事中控制),以及录像记录登录资产后做了什么事情(事后溯源)。由于堡垒机内部保存着企业所有的设备资产和权限关系,是企业内部信息安全的重要一环。但目前出现的以下问题产生了很大安全隐患:密码设置过于简单,容易被暴力破解;为方便记忆,设置统一的密码,一旦单点被破,极易引发全面危机。在单一的静态密码验证机制下,登录密码是堡垒机安全的唯一_horizon宁盾双因素配置
文章浏览阅读7.7k次,点赞4次,收藏16次。Chrome作为一款挺不错的浏览器,其有着诸多的优良特性,并且支持跨平台。其支持(Windows、Linux、Mac OS X、BSD、Android),在绝大多数情况下,其的安装都很简单,但有时会由于网络原因,无法安装,所以在这里总结下Chrome的安装。Windows下的安装:在线安装:离线安装:Linux下的安装:在线安装:离线安装:..._chrome linux debian离线安装依赖
文章浏览阅读153次。中国发达城市榜单每天都在刷新,但无非是北上广轮流坐庄。北京拥有最顶尖的文化资源,上海是“摩登”的国际化大都市,广州是活力四射的千年商都。GDP和发展潜力是衡量城市的数字指...
文章浏览阅读3.3k次。前言spark在java使用比较少,多是scala的用法,我这里介绍一下我在项目中使用的代码配置详细算法的使用请点击我主页列表查看版本jar版本说明spark3.0.1scala2.12这个版本注意和spark版本对应,只是为了引jar包springboot版本2.3.2.RELEASEmaven<!-- spark --> <dependency> <gro_使用java调用spark注册进去的程序
文章浏览阅读4.8k次。汽车零部件开发工具巨头V公司全套bootloader中UDS协议栈源代码,自己完成底层外设驱动开发后,集成即可使用,代码精简高效,大厂出品有量产保证。:139800617636213023darcy169_uds协议栈 源代码
文章浏览阅读4.6k次,点赞20次,收藏148次。AUTOSAR基础篇之OS(下)前言首先,请问大家几个小小的问题,你清楚:你知道多核OS在什么场景下使用吗?多核系统OS又是如何协同启动或者关闭的呢?AUTOSAR OS存在哪些功能安全等方面的要求呢?多核OS之间的启动关闭与单核相比又存在哪些异同呢?。。。。。。今天,我们来一起探索并回答这些问题。为了便于大家理解,以下是本文的主题大纲:[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JCXrdI0k-1636287756923)(https://gite_autosar 定义了 5 种多核支持类型
文章浏览阅读2.2k次,点赞6次,收藏14次。原因:自己写的头文件没有被加入到方案的包含目录中去,无法被检索到,也就无法打开。将自己写的头文件都放入header files。然后在VS界面上,右键方案名,点击属性。将自己头文件夹的目录添加进去。_vs2013打不开自己定义的头文件
文章浏览阅读3.3w次,点赞80次,收藏342次。此时,可以将系统中所有用户的 Session 数据全部保存到 Redis 中,用户在提交新的请求后,系统先从Redis 中查找相应的Session 数据,如果存在,则再进行相关操作,否则跳转到登录页面。此时,可以将系统中所有用户的 Session 数据全部保存到 Redis 中,用户在提交新的请求后,系统先从Redis 中查找相应的Session 数据,如果存在,则再进行相关操作,否则跳转到登录页面。当数据量很大时,count 的数量的指定可能会不起作用,Redis 会自动调整每次的遍历数目。_redis命令
文章浏览阅读449次,点赞3次,收藏3次。URP的设计目标是在保持高性能的同时,提供更多的渲染功能和自定义选项。与普通项目相比,会多出Presets文件夹,里面包含着一些设置,包括本色,声音,法线,贴图等设置。全局只有主光源和附加光源,主光源只支持平行光,附加光源数量有限制,主光源和附加光源在一次Pass中可以一起着色。URP:全局只有主光源和附加光源,主光源只支持平行光,附加光源数量有限制,一次Pass可以计算多个光源。可编程渲染管线:渲染策略是可以供程序员定制的,可以定制的有:光照计算和光源,深度测试,摄像机光照烘焙,后期处理策略等等。_urp渲染管线