嗨丸相,我是哈利迪~《看完不忘系列》將以從樹干到細(xì)枝
的思路分析一些技術(shù)框架拌蜘,本文將對開源項目Retrofit
進(jìn)行介紹。
本文約2800字陪竿,閱讀大約8分鐘禽翼。
Retrofit源碼基于最新版本2.9.0
預(yù)備
Retrofit使得網(wǎng)絡(luò)調(diào)用可以像RESTful設(shè)計風(fēng)格一樣簡潔,如:
interface WanApi {
//用注解標(biāo)記網(wǎng)絡(luò)請求方式get族跛、post捐康,參數(shù)path、query等
@GET("article/list/{page}/json")
Call<WanArticleBean> articleList(@Path("page") int page);
}
又如庸蔼,后端的Spring Boot框架通過約定大于配置
思想省去了很多配置,其在網(wǎng)絡(luò)接口RestController上也運(yùn)用了這種風(fēng)格贮匕,
@RestController
public class ActivityController {
@Autowired
private ActivityService activityService;
//用注解標(biāo)記網(wǎng)絡(luò)請求方式和入?yún)? @GetMapping("/goods")
public ResultEntity queryGoods(@RequestParam("page") int page) {
return activityService.queryGoods(page);
}
}
Retrofit的底層網(wǎng)絡(luò)實現(xiàn)基于okhttp姐仅,自身的類不是很多,最核心的點(diǎn)就是動態(tài)代理
了。代理模式
簡單來說掏膏,就是為對象提供一個增強(qiáng)
或控制其訪問
的代理劳翰。下面我們先來了解下靜態(tài)代理
和動態(tài)代理
~
靜態(tài)代理
編譯期就完成代理
- 源碼級:手動編寫代理類、APT生成代理類
- 字節(jié)碼級:編譯期生成字節(jié)碼馒疹、
舉個栗子佳簸,
interface 賺錢 {
void makeMoney(int income);
}
class 小鮮肉 implements 賺錢 { //委托類
@Override
public void makeMoney(int income) {
System.out.println("開拍,賺個" + income);
}
}
class 經(jīng)紀(jì)人 implements 賺錢 { //代理類
賺錢 xxr;
public 經(jīng)紀(jì)人(賺錢 xxr) {
this.xxr = xxr;
}
@Override
public void makeMoney(int income) {
if (income < 1000_0000) { //控制訪問
System.out.println("才" + income + "颖变,先回去等通知吧");
} else {
xxr.makeMoney(income);
}
}
}
public static void main(String[] args) {
賺錢 xxr = new 小鮮肉();
賺錢 jjr = new 經(jīng)紀(jì)人(xxr);
jjr.makeMoney(100_0000); //輸出:才1000000生均,先回去等通知吧
jjr.makeMoney(1000_0000); //輸出:開拍,賺個10000000
}
為什么代理類
和委托類
要實現(xiàn)相同接口腥刹?是為了盡可能保證代理類
的內(nèi)部結(jié)構(gòu)和委托類
一致马胧,這樣對代理類
的操作都可以轉(zhuǎn)移到委托類
上,代理類
只關(guān)注增強(qiáng)
和控制
衔峰。
動態(tài)代理
運(yùn)行期生成字節(jié)碼佩脊,如Proxy.newProxyInstance、CGLIB
Proxy.newProxyInstance是java自帶垫卤,只能對接口代理(因為生成的類已經(jīng)繼承了Proxy威彰,java沒法多繼承)
CGLIB則更強(qiáng)大,還能對普通類代理穴肘,底層基于ASM(ASM使用類似SAX解析器逐行掃描來提高性能)
舉個栗子歇盼,
class 合作標(biāo)準(zhǔn) implements InvocationHandler {
賺錢 xxr;
public 合作標(biāo)準(zhǔn)(賺錢 xxr) {
this.xxr = xxr;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
int income = (int) args[0];
if (income < 1000_0000) { //控制訪問
System.out.println("才" + income + ",先回去等通知吧");
return null;
} else {
return method.invoke(xxr, args);
}
}
}
public static void main(String[] args) {
賺錢 xxr = new 小鮮肉();
合作標(biāo)準(zhǔn) standard = new 合作標(biāo)準(zhǔn)(xxr);
//生成類(字節(jié)碼):class $Proxy0 extends Proxy implements 賺錢
//然后反射創(chuàng)建其實例bd梢褐,即來一場臨時的商務(wù)拓展
賺錢 bd = (賺錢) Proxy.newProxyInstance(賺錢.class.getClassLoader(),
new Class[]{賺錢.class},
standard);
//調(diào)用makeMoney旺遮,內(nèi)部轉(zhuǎn)發(fā)給了合作標(biāo)準(zhǔn)的invoke
bd.makeMoney(100_0000);
bd.makeMoney(1000_0000);
}
通過栗子可以看出,動態(tài)代理
不需要提前創(chuàng)建具體的代理類(如經(jīng)紀(jì)人
或經(jīng)紀(jì)公司
)去實現(xiàn)賺錢
接口盈咳,而是先擬一份合作標(biāo)準(zhǔn)
(InvocationHandler)耿眉,等到運(yùn)行期才創(chuàng)建代理類$Proxy0
(字節(jié)碼),然后反射創(chuàng)建其實例商務(wù)拓展
鱼响,這樣顯得更為靈活鸣剪。
了解完動態(tài)代理
,就可以開始Retrofit之旅了~
樹干
簡單使用
引入依賴丈积,
implementation 'com.squareup.okhttp3:okhttp:3.14.9'
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
implementation 'com.google.code.gson:gson:2.8.6'
定義接口WanApi
筐骇,
interface WanApi {
//用注解標(biāo)記網(wǎng)絡(luò)請求類型get,參數(shù)path
@GET("article/list/{page}/json")
Call<WanArticleBean> articleList(@Path("page") int page);
}
發(fā)起請求江滨,
class RetrofitActivity extends AppCompatActivity {
final String SERVER = "https://www.xxx.com/";
@Override
protected void onCreate(Bundle savedInstanceState) {
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(SERVER) //指定服務(wù)器地址
.addConverterFactory(GsonConverterFactory.create()) //用gson將數(shù)據(jù)反序列化成實體
.build();
//運(yùn)行期生成一個實現(xiàn)WanApi接口的類(字節(jié)碼)铛纬,并反射創(chuàng)建其實例
WanApi wanApi = retrofit.create(WanApi.class);
//得到Retrofit的call,他封裝了okhttp的call
Call<WanArticleBean> call = wanApi.articleList(0);
//請求入隊
call.enqueue(new Callback<WanArticleBean>() {
@Override
public void onResponse(Call<WanArticleBean> call, Response<WanArticleBean> response) {
//得到數(shù)據(jù)實體
WanArticleBean bean = response.body();
//不同于okhttp唬滑,Retrofit已經(jīng)用Handler幫我們切回主線程了
mBinding.tvResult.setText("" + bean.getData().getDatas().size());
}
@Override
public void onFailure(Call<WanArticleBean> call, Throwable t) {}
});
}
}
實現(xiàn)原理
由于Retrofit底層基于okhttp告唆,哈迪在《看完不忘系列》之okhttp已經(jīng)對網(wǎng)絡(luò)流程做了分析棺弊,所以本文忽略網(wǎng)絡(luò)實現(xiàn)只關(guān)注Retrofit自身的一些處理,Retrofit對象的構(gòu)建就是簡單的builder模式擒悬,我們直接看create模她,
//Retrofit.java
public <T> T create(final Class<T> service) {
//驗證
validateServiceInterface(service);
return (T)
//動態(tài)代理
Proxy.newProxyInstance(
service.getClassLoader(), //類加載器
new Class<?>[] {service}, //一組接口
new InvocationHandler() {
//判斷android和jvm平臺及其版本
private final Platform platform = Platform.get();
@Override
public Object invoke(Object proxy, Method method, Object[] args){
//如果該方法是Object的方法,直接執(zhí)行不用管
if (method.getDeclaringClass() == Object.class) {
return method.invoke(this, args);
}
//isDefaultMethod:檢查是否是java8開始支持的接口默認(rèn)方法
return platform.isDefaultMethod(method)
? platform.invokeDefaultMethod(method, service, proxy, args)
: loadServiceMethod(method).invoke(args); //我們關(guān)注這里
}
});
}
Proxy.newProxyInstance動態(tài)代理懂牧,運(yùn)行期會生成一個類(字節(jié)碼)如$ProxyN
侈净,實現(xiàn)傳入的接口即WanApi
,重寫接口方法然后轉(zhuǎn)發(fā)給InvocationHandler的invoke僧凤,如下(偽代碼)畜侦,
class $ProxyN extends Proxy implements WanApi{
Call<WanArticleBean> articleList(@Path("page") int page){
//轉(zhuǎn)發(fā)給invocationHandler
invocationHandler.invoke(this,method,args);
}
}
我們先看validateServiceInterface驗證邏輯,
//Retrofit.java
private void validateServiceInterface(Class<?> service) {
//檢查:WanApi不是接口就拋異常...
//檢查:WanApi不能有泛型參數(shù)拼弃,不能實現(xiàn)其他接口...
if (validateEagerly) { //是否進(jìn)行嚴(yán)格檢查夏伊,默認(rèn)關(guān)閉
Platform platform = Platform.get();
for (Method method : service.getDeclaredMethods()) { //遍歷WanApi方法
//不是默認(rèn)方法,并且不是靜態(tài)方法
if (!platform.isDefaultMethod(method) && !Modifier.isStatic(method.getModifiers())) {
//把方法提前加載進(jìn)來(檢查下有沒有問題)
loadServiceMethod(method);
}
}
}
}
如果開了validateEagerly吻氧,會一次性把接口WanApi
的所有方法都檢查一遍并加載進(jìn)來溺忧,可以在debug模式下開啟,提前發(fā)現(xiàn)錯誤寫法盯孙,比如在@GET請求設(shè)置了@Body這種錯誤就會拋出異常:
java.lang.IllegalArgumentException: Non-body HTTP method cannot contain @Body.
loadServiceMethod
然后是loadServiceMethod(method).invoke(args)
鲁森,看名字可知是先找方法,然后執(zhí)行,
//Retrofit.java
//緩存,用了線程安全ConcurrentHashMap
final Map<Method, ServiceMethod<?>> serviceMethodCache = new ConcurrentHashMap<>();
ServiceMethod<?> loadServiceMethod(Method method) {
ServiceMethod<?> result = serviceMethodCache.get(method);
//WanApi的articleList方法已緩存忱详,直接返回
if (result != null) return result;
synchronized (serviceMethodCache) {
result = serviceMethodCache.get(method);
if (result == null) {
//解析articleList的注解,創(chuàng)建ServiceMethod并緩存起來
result = ServiceMethod.parseAnnotations(this, method);
serviceMethodCache.put(method, result);
}
}
return result;
}
跟進(jìn)ServiceMethod.parseAnnotations痛垛,
//ServiceMethod.java
static <T> ServiceMethod<T> parseAnnotations(Retrofit retrofit, Method method) {
//1.
RequestFactory requestFactory = RequestFactory.parseAnnotations(retrofit, method);
//檢查:articleList方法返回類型不能用通配符和void...
//2.
return HttpServiceMethod.parseAnnotations(retrofit, method, requestFactory);
}
先看1. RequestFactory.parseAnnotations,
//RequestFactory.java
static RequestFactory parseAnnotations(Retrofit retrofit, Method method) {
return new Builder(retrofit, method).build();
}
class Builder {
RequestFactory build() {
//解析方法注解如GET
for (Annotation annotation : methodAnnotations) {
parseMethodAnnotation(annotation);
}
//省略各種檢查...
//解析參數(shù)注解如Path
int parameterCount = parameterAnnotationsArray.length;
parameterHandlers = new ParameterHandler<?>[parameterCount];
for (int p = 0, lastParameter = parameterCount - 1; p < parameterCount; p++) {
parameterHandlers[p] =
parseParameter(p, parameterTypes[p], parameterAnnotationsArray[p], p == lastParameter);
}
//省略各種檢查...
return new RequestFactory(this);
}
}
得到RequestFactory后桶蛔,看2. HttpServiceMethod.parseAnnotations匙头,HttpServiceMethod負(fù)責(zé)適配和轉(zhuǎn)換處理,將接口方法的調(diào)用調(diào)整為HTTP調(diào)用仔雷,
//HttpServiceMethod.java
//ResponseT響應(yīng)類型如WanArticleBean蹂析,ReturnT返回類型如Call
static <ResponseT, ReturnT> HttpServiceMethod<ResponseT, ReturnT> parseAnnotations(
Retrofit retrofit, Method method, RequestFactory requestFactory) {
//省略kotlin協(xié)程邏輯...
Annotation[] annotations = method.getAnnotations();
//遍歷找到合適的適配器
CallAdapter<ResponseT, ReturnT> callAdapter =
createCallAdapter(retrofit, method, adapterType, annotations);
//得到響應(yīng)類型,如WanArticleBean
Type responseType = callAdapter.responseType();
//遍歷找到合適的轉(zhuǎn)換器
Converter<ResponseBody, ResponseT> responseConverter =
createResponseConverter(retrofit, method, responseType);
okhttp3.Call.Factory callFactory = retrofit.callFactory;
return new CallAdapted<>(requestFactory, callFactory, responseConverter, callAdapter);
}
可見最終返回了一個CallAdapted碟婆,看到CallAdapted电抚,
//CallAdapted extends HttpServiceMethod extends ServiceMethod
class CallAdapted<ResponseT, ReturnT> extends HttpServiceMethod<ResponseT, ReturnT> {
private final CallAdapter<ResponseT, ReturnT> callAdapter;
CallAdapted(
RequestFactory requestFactory,
okhttp3.Call.Factory callFactory,
Converter<ResponseBody, ResponseT> responseConverter,
CallAdapter<ResponseT, ReturnT> callAdapter) {
super(requestFactory, callFactory, responseConverter);
this.callAdapter = callAdapter;
}
@Override
protected ReturnT adapt(Call<ResponseT> call, Object[] args) {
//適配器
return callAdapter.adapt(call);
}
}
那這個CallAdapter實例到底是誰呢,我們先回到Retrofit.Builder竖共,
//Retrofit.Builder.java
public Retrofit build() {
Executor callbackExecutor = this.callbackExecutor;
//如果沒設(shè)置線程池蝙叛,則給android平臺設(shè)置一個默認(rèn)的MainThreadExecutor(用Handler將回調(diào)切回主線程)
if (callbackExecutor == null) {
callbackExecutor = platform.defaultCallbackExecutor();
}
List<CallAdapter.Factory> callAdapterFactories = new ArrayList<>(this.callAdapterFactories);
//添加默認(rèn)的DefaultCallAdapterFactory
callAdapterFactories.addAll(platform.defaultCallAdapterFactories(callbackExecutor));
}
DefaultCallAdapterFactory這個工廠創(chuàng)建具體的CallAdapter實例,
//DefaultCallAdapterFactory.java
public CallAdapter<?, ?> get(Type returnType, Annotation[] annotations, Retrofit retrofit) {
final Type responseType = Utils.getParameterUpperBound(0, (ParameterizedType) returnType);
//如果指定了SkipCallbackExecutor注解公给,就表示不需要切回主線程
final Executor executor =
Utils.isAnnotationPresent(annotations, SkipCallbackExecutor.class)
? null
: callbackExecutor;
return new CallAdapter<Object, Call<?>>() {
@Override
public Type responseType() {
return responseType;
}
@Override
public Call<Object> adapt(Call<Object> call) {
//默認(rèn)情況下甥温,返回用主線程池包裝的Call锻煌,他的enqueue會使用主線程池的execute
return executor == null ? call : new ExecutorCallbackCall<>(executor, call);
}
};
}
invoke
前邊loadServiceMethod
得到了CallAdapted,然后執(zhí)行invoke姻蚓,實現(xiàn)在父類HttpServiceMethod里,
//HttpServiceMethod.java
final ReturnT invoke(Object[] args) {
//終于見到okhttp了匣沼!
Call<ResponseT> call = new OkHttpCall<>(requestFactory, args, callFactory, responseConverter);
return adapt(call, args);
}
class CallAdapted<ResponseT, ReturnT> extends HttpServiceMethod<ResponseT, ReturnT> {
private final CallAdapter<ResponseT, ReturnT> callAdapter;
@Override
protected ReturnT adapt(Call<ResponseT> call, Object[] args) {
//用前邊得到的適配器狰挡,把OkHttpCall包成ExecutorCallbackCall
return callAdapter.adapt(call);
}
}
然后是請求入隊,ExecutorCallbackCall.enqueue -> OkHttpCall.enqueue释涛,
//ExecutorCallbackCall.java
void enqueue(final Callback<T> callback) {
delegate.enqueue(
new Callback<T>() {
@Override
public void onResponse(Call<T> call, final Response<T> response) {
//將回調(diào)切回主線程
callbackExecutor.execute(
() -> {
callback.onResponse(ExecutorCallbackCall.this, response);
});
//...
}
@Override
public void onFailure(Call<T> call, final Throwable t) {}
});
}
//OkHttpCall.java
void enqueue(final Callback<T> callback) {
//okhttp邏輯
okhttp3.Call call;
call.enqueue(new okhttp3.Callback() {
void onResponse(okhttp3.Call call, okhttp3.Response rawResponse) {
callback.onResponse(OkHttpCall.this, response);
}
})
}
總算把流程跑通了加叁,回到前邊再看一遍流程圖,就豁然開朗了~
細(xì)枝
CallAdapter
CallAdapter適配器用于適配返回類型唇撬,比如還可以支持Rxjava它匕、協(xié)程的使用,
interface WanApi {
//Call
@GET("article/list/{page}/json")
Call<WanArticleBean> articleList(@Path("page") int page);
//Rxjava窖认,需要 addCallAdapterFactory(RxJavaCallAdapterFactory.create())
@GET("article/list/{page}/json")
Observable<WanArticleBean> articleListRx(@Path("page") int page);
}
Converter
Converter轉(zhuǎn)換器用于轉(zhuǎn)換參數(shù)類型豫柬,比如把Long時間戳格式化成string再傳給后端,
interface WanApi {
//Long cur 當(dāng)前時間
@GET("article/list/{page}/json")
Call<WanArticleBean> articleList(@Path("page") int page, @Query("cur") Long cur);
}
class TimeConverter implements Converter<Long, String> {
private SimpleDateFormat mFormat = new SimpleDateFormat("yyyy-MM-dd-HHmmss");
@Override
public String convert(Long value) throws IOException {
if (value > 1_000_000_000_000L) {//毫秒扑浸,不是很嚴(yán)謹(jǐn) - -
return mFormat.format(new Date(value));
}
return String.valueOf(value);
}
}
class TimeConverterFactory extends Converter.Factory {
@Override
public Converter<?, String> stringConverter(Type type, Annotation[] annotations, Retrofit retrofit) {
if (type == Long.class) {
//使用自定義TimeConverter
return new TimeConverter();
}
return super.stringConverter(type, annotations, retrofit);
}
public static Converter.Factory create() {
return new TimeConverterFactory();
}
}
//再設(shè)置一下就行了烧给,addConverterFactory(TimeConverterFactory.create())
動態(tài)替換url
在構(gòu)建Retrofit時傳入HttpUrl對象,之后這個實例就一直存在不會更改喝噪,所以可以反射修改他的字段比如host础嫡,來實現(xiàn)動態(tài)替換服務(wù)端地址,
String SERVER = "https://www.xxx.com/";
HttpUrl httpUrl = HttpUrl.get(SERVER);
Retrofit retrofit = new Retrofit.Builder()
//.baseUrl(SERVER)
.baseUrl(httpUrl) //使用HttpUrl
.build();
尾聲
咱們下期見~??
系列文章:
參考資料
- GitHub & 文檔 & API
- imooc - 破解Retrofit
- 簡書 - 從架構(gòu)角度看Retrofit的作用酝惧、原理和啟示
- 簡書 - JAVA動態(tài)代理
- csdn - CGLIB(Code Generation Library)詳解
- 知乎 - Java 動態(tài)代理作用是什么榴鼎?