實現(xiàn)retro風(fēng)格的接口請求

背景

項目中構(gòu)建接口請求,步驟比較繁瑣,希望像retrofit一樣。
這樣能提高開發(fā)效率,減少維護(hù)成本。

解決方案

在研究了retrofit的技術(shù)方案后,采用相同的動態(tài)代理+注解技術(shù)方案

  1. 注解:幫助我們簡化、規(guī)范接口請求的協(xié)議
  2. 動態(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)用主體都不一樣,如下圖

鏈?zhǔn)秸{(diào)用不同時期,主體不一樣

ready時,我們通過動態(tài)代理,創(chuàng)建了Api接口的動態(tài)對象,后續(xù)的changeRole方法被轉(zhuǎn)發(fā)給了動態(tài)代理去執(zhí)行。

那么動態(tài)代理是怎么被創(chuàng)建、又是怎么執(zhí)行方法呢?

  1. 創(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;
    }
  1. 執(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);
            }
        });
    }
  1. loadApiMethod負(fù)責(zé)解析出函數(shù)的協(xié)議,包括路徑、參數(shù)名、返回類型等
  2. setArgs負(fù)責(zé)解析函數(shù)的入?yún)⒅?/li>
  3. 一個請求所有的基本要素已經(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ā)到了InvocationHandlerinvoke方法。
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、異步請求生命周期自動管理

我們使用JetPackLifecycleOwner來自動監(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);

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容