背景
項目中構(gòu)建接口請求,步驟比較繁瑣,希望像retrofit一樣。
這樣能提高開發(fā)效率,減少維護(hù)成本。
解決方案
在研究了retrofit的技術(shù)方案后,采用相同的動態(tài)代理+注解技術(shù)方案
- 注解:幫助我們簡化、規(guī)范接口請求的協(xié)議
- 動態(tài)代理:接口方法能直接解析使用,而不用手動創(chuàng)建對象。
結(jié)果

重構(gòu)后,兩步就能實現(xiàn)接口調(diào)用
1.聲明
public interface Api {
Api ready = XLApiManager.ready().getApi(Api.class);
@POST("system/changeRole")
XLCall<RE_Login> changeRole(
@Param("changeUserId") String changeUserId,
@Param("uniqueDeviceId") String uniqueDeviceId);
2.調(diào)用
Api.ready.changeRole(changeUserId, DeviceUtil.getDeviceId()).requestV2(...success...fail);
可以看到接口調(diào)用已經(jīng)非常簡單,和retrofit一致,符合我們重構(gòu)的預(yù)期。
核心講解
下面我們從使用者的角度出發(fā),講講本項目是如何實現(xiàn)的。
1、接口里的方法為什么能方法直接用了?
我們可以看到鏈?zhǔn)秸{(diào)用,每一步他的調(diào)用主體都不一樣,如下圖

ready時,我們通過動態(tài)代理,創(chuàng)建了Api接口的動態(tài)對象,后續(xù)的changeRole方法被轉(zhuǎn)發(fā)給了動態(tài)代理去執(zhí)行。
那么動態(tài)代理是怎么被創(chuàng)建、又是怎么執(zhí)行方法呢?
- 創(chuàng)建。用了享元模式,復(fù)用來避免動態(tài)代理重復(fù)創(chuàng)建浪費資源性能。同時引入了雙重檢查鎖,避免多線程情況下重復(fù)創(chuàng)建對象。
Api ready = XLApiManager.ready().getApi(Api.class);
//getApi實現(xiàn)
public <T> T getApi(Class<T> api) {
T result = (T) apiClassCache.get(api);
if (result != null) {
return result;
}
synchronized (apiClassCache) {
result = (T) apiClassCache.get(api);
if (result == null) {
result = create(api);
apiClassCache.put(api, result);
}
}
return result;
}
- 執(zhí)行
下面是動態(tài)代理的調(diào)用方法,我們Api.ready.xxx動態(tài)代理的方法,都交由invoke去解析執(zhí)行
Proxy.newProxyInstance是java系統(tǒng)方法,核心是看invoke
private <T> T create(final Class<T> api) {
XLHttpUtils.validateApiInterface(api);
return (T) Proxy.newProxyInstance(api.getClassLoader(), new Class<?>[]{api}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object... args) throws Throwable {
//1.解析函數(shù)、注解等信息
ApiMethod apiMethod = loadApiMethod(method);
//2.設(shè)置函數(shù)的入?yún)? apiMethod.setArgs(args);
// 3. 以此構(gòu)建一個接口請求
return new ApiCallV2(XLApiManager.this, apiMethod);
}
});
}
-
loadApiMethod負(fù)責(zé)解析出函數(shù)的協(xié)議,包括路徑、參數(shù)名、返回類型等 -
setArgs負(fù)責(zé)解析函數(shù)的入?yún)⒅?/li> - 一個請求所有的基本要素已經(jīng)準(zhǔn)備好,最后一步負(fù)責(zé)創(chuàng)建一個請求對象。
以上就是Api.ready.changeRole(changeUserId, DeviceUtil.getDeviceId())主體變化的全過程了。
此時創(chuàng)建好了請求對象,我們就可以發(fā)起請求,并提供解析回調(diào)了.requestV2(...success...fail)
2、入?yún)⒗镒⒔獾膮?shù),最終怎么傳遞給服務(wù)端?
舉個請求例子:
/**
* 教師 – 作業(yè) – 學(xué)生 – 提醒交作業(yè)
*/
@POST("teacherWork/v3/remindWork")
XLCall<RE_Result> remindWork(
@Param("workId") String workId,
@Param("classId") String classId);
在第一個問題,我們提到了,Api函數(shù)的調(diào)用都被轉(zhuǎn)發(fā)到了代理的invoke方法里
@Override
public Object invoke(Object proxy, Method method, Object... args) throws Throwable {
//1創(chuàng)建方法對象
ApiMethod apiMethod = loadApiMethod(method);
//2刷新入?yún)? apiMethod.setArgs(args);
return new ApiCallV2(XLApiManager.this, apiMethod);
}
}
在1、loadApiMethod里,通過parseParameterAnnotation,把注解的參數(shù)名記錄下來
在 2、setArgs刷新入?yún)?,?code>invoke拿到的入?yún)?strong>參數(shù)值,記錄下來
通過1、2步,我們拿到了一個包含入?yún)⒚?、值的?shù)組。
最后為了性能考慮,入?yún)⒓用懿襟E,我們延遲在實際使用的時候,通過
ParamSignUtils.signParams生成了加密信息數(shù)據(jù)
3、請求返回值為啥能自動解析成 DTO
1、返回類型怎么保存
通過FAQ第一個問題,我們知道了:接口對象所有的方法調(diào)用,都轉(zhuǎn)發(fā)到了InvocationHandler的invoke方法。
在invoke方法里,通過resolveResponseType拿到了方法的返回值XLCall<RE_Result>;再通過getParameterUpperBound()拿到了泛型里的類RE_Result
至此,請求函數(shù)已經(jīng)知道了okHttp返回的數(shù)據(jù)流要解析成什么對象
2、怎么解析成返回類型
拿到okHttp返回的Body。先解析成String再轉(zhuǎn)換成對應(yīng)的類型
String bodyString = new String(bodyBytes, getCharset(rawResponse.body()));
T bean = new ResponseConverter<T>(apiMethod.responseType).convert(bodyString);
@Override
public T convert(String result) {
Class<?> clazz = (Class) responseType;
if (clazz == String.class.getClass()) {
//noinspection unchecked
return (T) result;
}
return (T) JsonUtil.jsonToObject(result, clazz);
}
4、異步請求生命周期自動管理
我們使用JetPack的LifecycleOwner來自動監(jiān)聽載體生命周期
在ApiCallV2里,監(jiān)聽載體銷毀的事件
@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
public void destroy() {
mIsDestroy = true;
unRegisterLifeCircleCall();
if (!isCanceled()) {
cancel();
}
}
對于調(diào)用方來說是重載了requestV2,多了LifecycleOwner參數(shù)
Api.ready.changeRole(changeUserId, DeviceUtil.getDeviceId()).requestV2(...success...fail);
改成
Api.ready.changeRole(changeUserId, DeviceUtil.getDeviceId()).requestV2(...success...fail,LifecycleOwner);