本篇文章已授權(quán)微信公眾號(hào) 玉剛說(shuō) (任玉剛)獨(dú)家發(fā)布
需求背景:
相信大部分朋友都經(jīng)歷過(guò),運(yùn)營(yíng)突然來(lái)要求澄者,要給某部分接口帶上某個(gè)參數(shù)(這個(gè)參數(shù)可能是from,表示當(dāng)前在哪個(gè)頁(yè)面请琳;或者 duration粱挡,表示當(dāng)前界面停留了多久)。這個(gè)時(shí)候俄精,最直接的做法就是询筏,直接加唄~ 有些接口還被多個(gè)界面調(diào)用,要改代碼的界面可能是十多個(gè)竖慧,也可能是大幾十個(gè)嫌套。
//舉例子,帖子點(diǎn)贊圾旨,原本的請(qǐng)求調(diào)用:
ApiService.getInstance().likePost(likeType, postId);
//直接在調(diào)用方法時(shí)加 from 參數(shù):
ApiService.getInstance().likePost(likeType, postId, from);
而我收到的需求則是要帶上當(dāng)前頁(yè)面和上一級(jí)頁(yè)面踱讨。。碳胳。這個(gè)需求勇蝙,按常規(guī)做法,在各個(gè)Activity間的intent都要傳入上一級(jí)Activity的信息。這個(gè)代碼量就更大了味混,而且代碼會(huì)很累贅产雹。
這時(shí)候,我的上級(jí)給了我提示翁锡,可以試下多重動(dòng)態(tài)代理蔓挖。之前我也考慮過(guò)這個(gè)需求適合用動(dòng)態(tài)代理做,但是我知道Retrofit本身已經(jīng)用了馆衔,我沒(méi)想到還可以多重動(dòng)態(tài)代理瘟判。接著就試了一下,還真的OK角溃,果然還是大佬牛啊~拷获!
給Retrofit嵌套一層動(dòng)態(tài)代理后,我們項(xiàng)目中調(diào)用請(qǐng)求接口的地方不需要修改代碼了减细,不用每處請(qǐng)求都手動(dòng)添加上 from 參數(shù)匆瓜,因?yàn)樵谶@個(gè)自定義的動(dòng)態(tài)代理工作時(shí),已經(jīng)幫我們統(tǒng)一加上了這個(gè) from 參數(shù)未蝌。
相關(guān)知識(shí)
動(dòng)態(tài)代理:方便的對(duì)被代理類的方法進(jìn)行統(tǒng)一處理驮吱。
反射:一種能夠在程序運(yùn)行時(shí)動(dòng)態(tài)訪問(wèn)、修改某個(gè)類中任意屬性(狀態(tài))和方法(行為)的機(jī)制(包括private實(shí)例和方法)萧吠。
閱讀本文需要你對(duì)動(dòng)態(tài)代理和反射有一定的理解左冬,不然建議先熟悉一下相關(guān)知識(shí)點(diǎn)。
Retrofit嵌套動(dòng)態(tài)代理步驟:
1.給 Retrofit.create (final Class<T> service)方法的返回值纸型,再加上自定義的動(dòng)態(tài)代理
/**
* 獲取對(duì)應(yīng)的 Service
*/
<T> T create(Class<T> service) {
// Retrofit 的代理
T retrofitProxy = retrofit.create(service);
//再添加一層自定義的代理拇砰。
T customProxy = addCustomProxy(retrofitProxy);
//返回這個(gè)嵌套的代理
return customProxy;
}
/**
* 嵌套添加動(dòng)態(tài)代理
* @param target 被代理的對(duì)象
* @return 返回嵌套動(dòng)態(tài)代理之后的對(duì)象
*/
public <T> T addCustomProxy(T target) {
CustomProxy customProxy = new CustomProxy();
return (T) customProxy.newProxy(target);
}
2.在原來(lái)的請(qǐng)求接口的基礎(chǔ)上,加上帶運(yùn)營(yíng)打點(diǎn)所需要的參數(shù)(在本例就是 from 參數(shù))
如果請(qǐng)求參數(shù)是每個(gè)值分開(kāi)傳的才需要這一步( 例如這里的 likePost 接口)狰腌;
對(duì)于請(qǐng)求參數(shù)是一個(gè)bean類或者M(jìn)ap毕匀,不需要這一步( 例如這里的 savePost 接口)。
/**
* 廣場(chǎng)發(fā)帖
*/
@FormUrlEncoded
@POST("square/post/save")
Call<RootBean<Object>> savePost(@Body EditPostRequest editPostRequest);
/**
* 帖子點(diǎn)贊
*/
@FormUrlEncoded
@POST("square/post/like")
Call<RootBean<Object>> likePost(@Field("likeType") int likeType, @Field("postId") long postId);
/**
* 帖子點(diǎn)贊
* 帶"from"參數(shù)的版本
* 不要?jiǎng)h除癌别,動(dòng)態(tài)代理會(huì)調(diào)用{@link CustomProxy}
*/
@FormUrlEncoded
@POST("square/post/like")
Call<RootBean<Object>> likePost(@Field("likeType") int likeType, @Field("postId") long postId, @Field("from") String from);
PS:注釋說(shuō)明“不要?jiǎng)h除,動(dòng)態(tài)代理會(huì)調(diào)用”建議一定不能省~~因?yàn)閯?dòng)態(tài)代理的方法在IDE中是索引不到的蹋笼,同事甚至自己很容易刪掉展姐,編譯是不會(huì)報(bào)錯(cuò)的。
3.在自定義的代理類里剖毯,真正執(zhí)行統(tǒng)一加參數(shù)的操作
(這里還是以加 from 做例子)
/**
* 嵌套添加動(dòng)態(tài)代理
* 簡(jiǎn)例:https://blog.csdn.net/zhenghuangyu/article/details/102808338
*/
public static class CustomProxy implements InvocationHandler {
//被代理對(duì)象圾笨,在這里就是 Retrofit.create(service) 的返回值
private Object mTarget;
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String from = "testFrom";
final String methodName = method.getName();
switch (methodName) {
case "savePost": {
//形參是一個(gè)bean類,用這種方式
//獲取第一個(gè)請(qǐng)求參數(shù)args[0]逊谋,這是我們定義該接口形參時(shí)的bean類
EditPostRequest editPostRequest = (EditPostRequest) args[0];
//以變量形式設(shè)置
editPostRequest.setFrom(from);
break;
}
case "likePost": {
//形參是一個(gè)個(gè)值的形式擂达,用這種方式
//將參數(shù)長(zhǎng)度+1,作為新的參數(shù)數(shù)組
args = Arrays.copyOf(args, (args.length + 1));
//在新的參數(shù)數(shù)組末端加上 from
args[args.length - 1] = from;
//為了調(diào)用帶 from 版本的方法胶滋,構(gòu)造新的形參
Class[] newParams = Arrays.copyOf(method.getParameterTypes(), (method.getParameterTypes().length + 1));
//新的形參里板鬓,最后一個(gè)參數(shù) from 是String類型的悲敷,這個(gè)必須聲明,才能準(zhǔn)確調(diào)用反射
newParams[newParams.length - 1] = String.class;
//找出新method對(duì)象俭令,就是帶 from 版本的那個(gè)方法
method = mTarget.getClass().getDeclaredMethod(method.getName(), newParams);
break;
}
}
//正式執(zhí)行方法
return method.invoke(mTarget, args);
}
//在這里嵌套外層的動(dòng)態(tài)代理
public Object newProxy(Object target) {
this.mTarget = target;
return Proxy.newProxyInstance(this.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
}
}
嗯后德,這樣就完成了為多個(gè)接口添加參數(shù)的需求。本來(lái)少說(shuō)也要修改幾十個(gè)地方抄腔,現(xiàn)在簡(jiǎn)單優(yōu)雅的解決了瓢湃。更重要的是,不需要機(jī)械地添加累贅的代碼赫蛇,用工程化的方案解決問(wèn)題绵患。
文章重點(diǎn)是多重動(dòng)態(tài)代理。至于我的需求里悟耘,怎么優(yōu)雅地處理當(dāng)前和上一級(jí)Activity的路徑落蝙,我想到的方法有兩種:1.用AMS獲取Activity棧 2.用ActivityLifecycle 。
我用的是第二種作煌,并通過(guò)一個(gè)Stack對(duì)象掘殴,自行記錄Activity的入棧出棧。不過(guò)這個(gè)不是文章重點(diǎn)粟誓,不詳細(xì)展開(kāi)了奏寨。放上簡(jiǎn)單代碼:
/**
* 要記錄最新的兩個(gè)頁(yè)面,用棧操作
*/
private Stack<String> tagsRecords = new Stack<>();
/**
* 標(biāo)簽入棧
*/
public void pushTagRecord(String tag) {
tagsRecords.push(tag);
}
/**
* 標(biāo)簽出棧
*/
public void popTagRecord() {
tagsRecords.pop();
}
//注冊(cè)LifeCycle監(jiān)聽(tīng)鹰服,在這里完成界面對(duì)應(yīng)tag的出棧入棧
Application.ActivityLifecycleCallbacks lifecycleCallbacks = new Application.ActivityLifecycleCallbacks() {
@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
//新建界面病瞳,入棧
pushTagRecord(activity.getLocalClassName());
}
@Override
public void onActivityDestroyed(Activity activity) {
//界面銷毀,出棧
popTagRecord();
}
}
//然后在項(xiàng)目Application類的初始化方法中注冊(cè)lifecycle
registerActivityLifecycleCallbacks(lifecycleCallbacks);
嗯悲酷,通過(guò)這種用lifecycle配合棧結(jié)構(gòu)的方式套菜,記錄頁(yè)面訪問(wèn)路徑,就避免了在每處 startActivity()的intent里傳遞參數(shù)设易。而且這種方法比AMS獲取Activity棧的方式更靈活逗柴。例如我的實(shí)際需求就是,特定的幾個(gè)Activity才算有效路徑顿肺。在Activity入棧出棧時(shí)戏溺,我可以做一層判斷過(guò)濾,而AMS我是控制不了的屠尊。