Android使用AOP埋點(diǎn)實(shí)戰(zhàn)

背景

最近收到了一些運(yùn)營(yíng)需求束莫,需要對(duì)一些數(shù)據(jù)進(jìn)行埋點(diǎn)懊烤,剛剛收到需求的時(shí)候矗愧,只有一個(gè)方案那就是去所有的頁(yè)面添加監(jiān)聽(tīng)灶芝,增加想要添加的業(yè)務(wù)數(shù)據(jù), 那龐大的工作量以及超強(qiáng)的耦合度唉韭,想想都頭大夜涕。不禁的心疼起自己的頭發(fā)。后面知道了原來(lái)可以面向切面編程属愤。接下來(lái)就是我使用aspectj的實(shí)戰(zhàn)女器。

實(shí)戰(zhàn)指項(xiàng)目配置

  • 第一步 項(xiàng)目的build.gradle的dependencies中加入
classpath 'org.aspectj:aspectjtools:1.8.9'
classpath 'org.aspectj:aspectjweaver:1.8.9'  
  • 第二步 在APP的build.gradle的dependencies中引入以下代碼
 implementation 'org.aspectj:aspectjrt:1.8.9' 
  • 第三步在app的build.gradle的android的目錄下加入
import org.aspectj.bridge.IMessage
import org.aspectj.bridge.MessageHandler
import org.aspectj.tools.ajc.Main

final def log = project.logger

android.applicationVariants.all { variant ->
//    if (!variant.buildType.isDebuggable()) {
//        log.debug("Skipping non-debuggable build type '${variant.buildType.name}'.")
//        return
//    }// 這段代碼會(huì)導(dǎo)致在release下失效 

    JavaCompile javaCompile = variant.javaCompiler
    javaCompile.doLast {
        String[] args = ["-showWeaveInfo",
                         "-1.8",
                         "-inpath", javaCompile.destinationDir.toString(),
                         "-aspectpath", javaCompile.classpath.asPath,
                         "-d", javaCompile.destinationDir.toString(),
                         "-classpath", javaCompile.classpath.asPath,
                         "-bootclasspath", project.android.bootClasspath.join(File.pathSeparator)]
        log.debug "ajc args: " + Arrays.toString(args)

        MessageHandler handler = new MessageHandler(true)
        new Main().run(args, handler)
        for (IMessage message : handler.getMessages(null, true)) {
            switch (message.getKind()) {
                case IMessage.ABORT:
                case IMessage.ERROR:
                case IMessage.FAIL:
                    log.error message.message, message.thrown
                    break
                case IMessage.WARNING:
                    log.warn message.message, message.thrown
                    break
                case IMessage.INFO:
                    log.info message.message, message.thrown
                    break
                case IMessage.DEBUG:
                    log.debug message.message, message.thrown
                    break
            }
        }
    }

上面那一段是在網(wǎng)上看別人的文章搬過(guò)來(lái)的,當(dāng)時(shí)有個(gè)bug就是 住诸,正式包切面代碼怎么都注入不成功驾胆,后面才發(fā)現(xiàn)了這段代碼涣澡。注釋掉下面這段代碼之后就成功了。

if (!variant.buildType.isDebuggable()) {
       log.debug("Skipping non-debuggable build type '${variant.buildType.name}'.")
       return
    }

到這里AOP的配置就完成了 丧诺。接下來(lái)我將貼出我根據(jù)需求所完成的相關(guān)業(yè)務(wù)代碼

1. 統(tǒng)計(jì)頁(yè)面停留時(shí)長(zhǎng)入桂。

這里的統(tǒng)計(jì)使用了極光推送的統(tǒng)計(jì) ,官方文檔就是需要每個(gè)頁(yè)面的開(kāi)始以及結(jié)束的生命周期里加上兩句話 驳阎,我們的項(xiàng)目反正有100多個(gè)頁(yè)面抗愁,如果每個(gè)頁(yè)面都去加的話,那工作量 額... 不好說(shuō)呵晚。我們還是看看切面真么實(shí)現(xiàn)頁(yè)面監(jiān)聽(tīng)吧
實(shí)現(xiàn)代碼

@Aspect
public class TraceAspect {

    private static final String TAG = "aop_page_trace";

    @After("execution(* android.app.Activity.onResume(..)) )")
    public void onActivityMethodBefore(JoinPoint joinPoint) throws Throwable {

        Signature signature = joinPoint.getSignature();
        System.out.println (TAG + " 切面的點(diǎn)執(zhí)行開(kāi)始  context==" + (Context) joinPoint.getThis()) ;
        System.out.println (TAG + "切面的點(diǎn)執(zhí)行開(kāi)始類(lèi)名" + signature.getDeclaringType().getCanonicalName());
        JAnalyticsInterface.onPageStart((Context) joinPoint.getThis(), signature.getDeclaringType().getCanonicalName());

    }

    @After("execution(* android.app.Activity.onPause(..)) ")
    public void onActivityMethodDestory(JoinPoint joinPoint) throws Throwable {
        Signature signature = joinPoint.getSignature();
        JAnalyticsInterface.onPageEnd((Context) joinPoint.getThis(), signature.getDeclaringType().getCanonicalName());
        System.out.println (TAG +" 切面的點(diǎn)執(zhí)行銷(xiāo)毀  context==" + (Context) joinPoint.getThis());
        System.out.println (TAG +"切面的點(diǎn)執(zhí)行銷(xiāo)毀類(lèi)名" + signature.getDeclaringType().getCanonicalName());
    } 

execution 一般指定方法的執(zhí)行,在往后因?yàn)闆](méi)有注解和訪問(wèn)權(quán)限的限制蜘腌,所以這里什么也沒(méi)寫(xiě),返回值用代替饵隙,說(shuō)明可以用任何返回值撮珠,android.app.Activity.on代表函數(shù)名稱的全路徑,后面的一個(gè)型號(hào)代表on后面可以接任何東西金矛,后面的(..)代表其參數(shù)可以為任何值芯急。
@After指在jPoint()之后執(zhí)行,我為了不影響之前代碼的執(zhí)行所以一般都用這個(gè)通配符绷柒。

2. 按鈕點(diǎn)擊 或者頁(yè)面跳轉(zhuǎn)的的相關(guān)埋點(diǎn)

我先說(shuō)一下思路志于。 我這里借助到了注解,第一步自定義注解废睦,第二步在需要埋點(diǎn)的地方引用當(dāng)前注解伺绽。第三步在切面檢查引用注解的地方找到切點(diǎn),第四步嗜湃,再切點(diǎn)處加入自己想要的邏輯代碼
自定義注解的代碼如下

/***
點(diǎn)擊的注解
 2019-10-30
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.FIELD})
public @interface ClickAnnotate {

    int type() default 0; //我根據(jù)需求定義了一個(gè)type參數(shù) 進(jìn)行標(biāo)記奈应,以便于找到切點(diǎn)后 進(jìn)行業(yè)務(wù)判斷

監(jiān)聽(tīng)點(diǎn)擊或跳轉(zhuǎn)的切面代碼如下

@Aspect
public class ClickAspect {

    private static final String TAG = "aop_click";
    private String intention = IntentionAty.class.getCanonicalName();

    @Pointcut("execution(@com.aoptest.annotation.ClickAnnotate * *(..))")
    public void clickP() {
    }

    @After("clickP()")
    public void insertCodeBlock(JoinPoint joinPoint) throws Throwable {

        MethodSignature methodSignature = ((MethodSignature) joinPoint.getSignature());
        Method method = methodSignature.getMethod();
        if (method.isAnnotationPresent(ClickAnnotate.class)) {
            ClickAnnotate  clickAnnotate = method.getAnnotation(ClickAnnotate.class);

            int type = clickAnnotate.type();
            switch (type) {
                case Constants.AOP_HOME_MENNU://這個(gè)常量是自定義的
                    String menCode = "";
                    if (joinPoint.getArgs() != null && joinPoint.getArgs().length > 0) {
                        if (joinPoint.getArgs()[0] instanceof String) {
                            menCode = (String) joinPoint.getArgs()[0];
                        }
                    }//joinPoint.getArgs() 就是你切入方法的地方的參數(shù) ,一定要注意數(shù)組越界問(wèn)題
                    if (TextUtils.isEmpty(menCode)) return;
                    switch (menCode) {
                        case "MENU_HOME_HYGL":
                            Log.i(TAG, "insertCodeBlock: 點(diǎn)擊了會(huì)員管理");
          
                            break;
                        case "MENU_HOME_YXDD":
                            Log.i(TAG, "insertCodeBlock: 點(diǎn)擊了意向訂單");
         
                            break;
                    }
                    break;

            }

        }
    }
}

到這里购披,切面需要做的事情就完成了杖挣,接下來(lái)就是只需要調(diào)用注解,表明需要切入代碼的地方就行了刚陡。

/**
     * 
     * @param menuCode 菜單對(duì)應(yīng)的編碼 
     */
  @ClickAnnotate(type = Constants.AOP_HOME_MENNU)//這里的常量和上面的切面對(duì)應(yīng)
    private void setMenuJump(String menuCode) {
        Class<?> className = null;
     if (menuCode.equals("MENU_HOME_YXDD")) {
            className = IntentionAty.class;
        }  

        if (className != null) {
            startActivity(new Intent(mContext, className));
        }
    }

根據(jù)以上步驟調(diào)用之后惩妇,切面就會(huì)檢查調(diào)用@ClickAnnotate注解的所有方法,找到切點(diǎn)筐乳,然后就注入相關(guān)代碼歌殃。我這里考慮到復(fù)用,所以在注解里面定義了一個(gè)type用于區(qū)分蝙云。

總結(jié)

在項(xiàng)目中氓皱。如果使用了混淆的話,一定要注意注解不要被混淆了,否則會(huì)導(dǎo)致切面不執(zhí)行的波材。經(jīng)過(guò)這個(gè)需求之后股淡,我發(fā)現(xiàn)AOP真的是好香香喔, AOP 大大降低了代碼的耦合度廷区,挺高了代碼的可維護(hù)性唯灵。關(guān)于AOP相關(guān)注解以及表達(dá)式的寫(xiě)法可以參考 AOP常用的注解以及概念,這里講到了相關(guān)概念隙轻,我就不搬過(guò)來(lái)了早敬。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市大脉,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌水孩,老刑警劉巖镰矿,帶你破解...
    沈念sama閱讀 206,378評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異俘种,居然都是意外死亡秤标,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)宙刘,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)苍姜,“玉大人,你說(shuō)我怎么就攤上這事悬包⊙弥恚” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,702評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵布近,是天一觀的道長(zhǎng)垫释。 經(jīng)常有香客問(wèn)我,道長(zhǎng)撑瞧,這世上最難降的妖魔是什么棵譬? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,259評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮预伺,結(jié)果婚禮上订咸,老公的妹妹穿的比我還像新娘。我一直安慰自己酬诀,他們只是感情好脏嚷,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,263評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著料滥,像睡著了一般然眼。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上葵腹,一...
    開(kāi)封第一講書(shū)人閱讀 49,036評(píng)論 1 285
  • 那天高每,我揣著相機(jī)與錄音屿岂,去河邊找鬼。 笑死鲸匿,一個(gè)胖子當(dāng)著我的面吹牛爷怀,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播带欢,決...
    沈念sama閱讀 38,349評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼运授,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了乔煞?” 一聲冷哼從身側(cè)響起吁朦,我...
    開(kāi)封第一講書(shū)人閱讀 36,979評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎渡贾,沒(méi)想到半個(gè)月后逗宜,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,469評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡空骚,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,938評(píng)論 2 323
  • 正文 我和宋清朗相戀三年纺讲,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片囤屹。...
    茶點(diǎn)故事閱讀 38,059評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡熬甚,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出肋坚,到底是詐尸還是另有隱情乡括,我是刑警寧澤,帶...
    沈念sama閱讀 33,703評(píng)論 4 323
  • 正文 年R本政府宣布冲簿,位于F島的核電站粟判,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏峦剔。R本人自食惡果不足惜档礁,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,257評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望吝沫。 院中可真熱鬧呻澜,春花似錦、人聲如沸惨险。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,262評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)辫愉。三九已至栅受,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背屏镊。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工依疼, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人而芥。 一個(gè)月前我還...
    沈念sama閱讀 45,501評(píng)論 2 354
  • 正文 我出身青樓律罢,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親棍丐。 傳聞我的和親對(duì)象是個(gè)殘疾皇子误辑,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,792評(píng)論 2 345