給Retrofit嵌套動(dòng)態(tài)代理,高效處理運(yùn)營(yíng)打點(diǎn)

本篇文章已授權(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我是控制不了的屠尊。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末旷祸,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子讼昆,更是在濱河造成了極大的恐慌托享,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,406評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異闰围,居然都是意外死亡赃绊,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門辫诅,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)凭戴,“玉大人,你說(shuō)我怎么就攤上這事炕矮∶捶颍” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 163,711評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵肤视,是天一觀的道長(zhǎng)档痪。 經(jīng)常有香客問(wèn)我,道長(zhǎng)邢滑,這世上最難降的妖魔是什么腐螟? 我笑而不...
    開(kāi)封第一講書人閱讀 58,380評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮困后,結(jié)果婚禮上乐纸,老公的妹妹穿的比我還像新娘。我一直安慰自己摇予,他們只是感情好汽绢,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,432評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著侧戴,像睡著了一般宁昭。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上酗宋,一...
    開(kāi)封第一講書人閱讀 51,301評(píng)論 1 301
  • 那天积仗,我揣著相機(jī)與錄音,去河邊找鬼蜕猫。 笑死寂曹,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的回右。 我是一名探鬼主播稀颁,決...
    沈念sama閱讀 40,145評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼楣黍!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起棱烂,我...
    開(kāi)封第一講書人閱讀 39,008評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤租漂,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體哩治,經(jīng)...
    沈念sama閱讀 45,443評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡秃踩,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,649評(píng)論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了业筏。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片憔杨。...
    茶點(diǎn)故事閱讀 39,795評(píng)論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖蒜胖,靈堂內(nèi)的尸體忽然破棺而出消别,到底是詐尸還是另有隱情,我是刑警寧澤台谢,帶...
    沈念sama閱讀 35,501評(píng)論 5 345
  • 正文 年R本政府宣布寻狂,位于F島的核電站,受9級(jí)特大地震影響朋沮,放射性物質(zhì)發(fā)生泄漏蛇券。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,119評(píng)論 3 328
  • 文/蒙蒙 一樊拓、第九天 我趴在偏房一處隱蔽的房頂上張望纠亚。 院中可真熱鬧,春花似錦筋夏、人聲如沸蒂胞。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,731評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)啤誊。三九已至,卻和暖如春拥娄,著一層夾襖步出監(jiān)牢的瞬間蚊锹,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 32,865評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工稚瘾, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留牡昆,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,899評(píng)論 2 370
  • 正文 我出身青樓摊欠,卻偏偏與公主長(zhǎng)得像丢烘,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子些椒,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,724評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容