使用
1、接入
compile 'com.squareup.retrofit2:retrofit:2.3.0'
compile 'com.squareup.retrofit2:retrofit-converters:2.3.0'
compile 'com.squareup.retrofit2:retrofit-adapters:2.3.0'
compile 'com.squareup.retrofit2:converter-gson:2.3.0'
compile 'com.squareup.retrofit2:adapter-rxjava2:2.3.0'
2、由此分析的架构
image.png3、使用
// Retrofit的构建,一般全局仅构建一次
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("http://www.zhihu.com")
.addConverterFactory(GsonConverterFactory.create())
.build();
// 接口定义
public interface INetApiService {
@GET("/demobiz/api.php")
Call<BizEntity> getBizInfo(@Query("id") String id);
}
// 发起请求
retrofit.create(INetApiService.class)
.getBizInfo("fff")
.enqueue(new Callback<BizEntity>() {
@Override
public void onResponse(Call<BizEntity> call, Response<BizEntity> response) {}
@Override
public void onFailure(Call<BizEntity> call, Throwable t) {}
});
对比传统封装
// 接口定义
public class GetBizInfoRequest extends BaseRequest<BizEntity> {
public String id;
public GetBizInfoRequest(String id) {
super("/demobiz/api.php");
this.id = id;
}
}
// 发起请求
AppNetWork.execute(new GetBizInfoRequest("fff"), new Callback<BizEntity>() {
@Override
public void onResponse(Call<BizEntity> call, Response<BizEntity> response) {}
@Override
public void onFailure(Call<BizEntity> call, Throwable t) {}
});
- 可以看到发起请求这一块都很简洁,区别在于接口的定义
- Retrofit通过编译时注解,把接口定义从类降低到了一个方法,代码更简洁,开销更小
- 面对大量接口的定义,比如Login相关的API,传统请求需要建立一个文件夹,里面存放若干Request类,而Retrofit请求,只需要一个LoginAPI的类,内部存放不同方法即可
- 合理使用编译时注解,可以大大简化项目,这个技巧值得我们学习与实践
架构
1、数据角度
image.png对于开发者来说,发起网络请求是非常简单的
框架会给你制定一套模板,你按照模板,把数据填充进去,即可发起请求
而Retrofit提供给你的模板显然更简单,甚至于到了完美的地步
2、功能角度
image.png- 通过编译时注解,搜集请求数据
- converter数据解析:比如默认的converter-gson,把json转换成了JavaBean。如果服务器返回的是xml,可以替换为对应的converter
- adapter响应类型转换:Retrofit自己定制了Call对象,区别于OkHttp的Call对象,内部会进行retrofit.call => okhttp.call的转换,高度解耦。而通过adapter,可以定义这种转换,并把这种能力开放出来,很典型的就是adapter-rxjava,定义了Observer<retrofit.Call<T>>到retrofit.Call<T>的转换
- OkHttp发起请求
一言以蔽之,Retrofit只是数据的收集者,处理者,并不是网络请求的执行者
原理
对于原理的分析,只分析请求数据的搜集这一块
而对于converter、adapter等其他实现,由于过于简单,所以不再赘述
1、回顾Retrofit的使用
retrofit.create(INetApiService.class)
.getBizInfo("fff")
.enqueue(new Callback<BizEntity>() {
@Override
public void onResponse(Call<BizEntity> call, Response<BizEntity> response) {}
@Override
public void onFailure(Call<BizEntity> call, Throwable t) {}
});
看到getBizInfo方法的调用,可以明白INetApiService这个接口必然被实例化了,否则其方法无法被调用
可是我们并没有定义其实现类,所以这个类是框架为我们创建的,所以其实现手段就是动态代理
2、理解动态代理
看下create方法
public <T> T create(final Class<T> service) {
return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, @Nullable Object[] args) throws Throwable {
ServiceMethod<Object, Object> serviceMethod = (ServiceMethod<Object, Object>) loadServiceMethod(method);
OkHttpCall<Object> okHttpCall = new OkHttpCall<>(serviceMethod, args);
return serviceMethod.adapt(okHttpCall);
}
});
}
Proxy.newProxyInstance就是Java动态代理的实现,可以根据一个类的类型,生产其实例化对象,并在某方法执行前后,织入一段逻辑
这也是Java常用的Hook手段,先通过动态代理生成“狸猫”,再通过反射替换“太子”,以达到不可告人的目的
这一切是通过JVM修改字节码来实现的
3、具体的实现
明白了动态代理的原理,自然知道,我们通过动态代理实现的接口,自然是空逻辑,所有的具体实现逻辑,就是我们传入的
public <T> T create(final Class<T> service) {
return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, @Nullable Object[] args) throws Throwable {
ServiceMethod<Object, Object> serviceMethod = (ServiceMethod<Object, Object>) loadServiceMethod(method);
OkHttpCall<Object> okHttpCall = new OkHttpCall<>(serviceMethod, args);
return serviceMethod.adapt(okHttpCall);
}
});
}
核心在于loadServiceMethod方法,不难猜到,这一步会对注解中的信息进行读取和保存,用于后续的网络请求
而且这里使用的是编译时注解,比如@Get中指定的url,还有@Query中指定的param key name,都会在编译时被保存
而param value,就是我们运行时动态传入的args
可优化的点
- 目前仅支持OkHttp通道,不支持灵活切换网络库,可以对此进行改造。因为很多公司设计的网络库比OkHttp更好或者更适合公司业务,比如美团更好地支持长连接,携程抛弃HTTP协议直接面向TCP协议做请求等。
- 不仅要支持网络库切换,还应该让网络库把线程控制权交出来,实现外部管理网络请求线程池,可以对网络请求性能做优化
结合RxJava使用
非常推荐Retrofit结合RxJava使用。在寻常请求中,是否使用RxJava没有任何影响。但是后续具有的复杂异步场景情况下,RxJava就能发挥很大的威力。比如一个请求执行完后,拿着返回数据,再次做请求,可以用flatMap解决回调的多重嵌套。比如后续要做磁盘缓存等等。
收获
- 编译时注解:可以用它来开发一些自己的库,大大简化自己的项目
- 动态代理:这里的动态代理的使用方式比较奇葩,目的不是为了在方法前后织入逻辑,而是给了接口统一的默认实现
- 解耦OkHttp,不直接使用okhttp.callback,而是使用adapter实现了retrofit.callback到okhttp.callback的转换,我们以后在封装另一个库的时候,完全可以如法炮制
- converter、adapter的配置。我们在开发一个库的时候,如果希望某个功能是可配置的,完全可以如法炮制
后记
有什么写得错误、让人费解或遗漏的地方,希望可以不吝赐教,我会马上更改
网友评论