AOP埋點從入門到放棄(一)(2018年08月13日修改)

筆墨Android發表於2018-08-11

今天老大跑過來說專案埋點了解一下!丟下了這句話之後,就沒有之後了!剩下我一個人在風中凌亂!!!

AOP埋點從入門到放棄(一)(2018年08月13日修改)

其實這個需求老大在很久之前就說要開發了,後來就擱置了!但是今天看老大的態度,應該排到日程了!所以沒辦法只有硬著頭皮磕了!免得過一陣子加班到很晚,所以趁著時間寬鬆,先能把踩的坑踩踩!!!分享給大家,也讓大家能避免一些不必要的時間浪費。更好的過個週末,陪陪女盆友!!!


特別宣告:

感謝JavaNoober提出的問題!

問題是這樣的?如果release的話,AspectJ失效怎麼辦?

當時真的給我問懵逼了,這種查,這種百度,都解決不了!最後還是請教了大神才解決的!!!

首先自己真的不瞭解配置這段程式碼的含義,所以產生了相應的問題,特別感謝您的指出。

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

這段程式碼的含義是在Debug的時候才執行的,如果不是Debug會直接返回的,所以呢?在你打release的時候,當然失效了。都return了!!!只要把這段程式碼去掉就可以了。


本系列文章知識點:

  • 專案中埋點的需求分析
  • AOP思想的應用
  • AspectJ怎麼整合到專案中(難點1)
  • AspectJ中的一些知識點說明(難點2)
  • AOP在專案中的應用等...

出於可讀性考慮,我準備把這個系列分成幾部分去寫,因為這樣才能充分利用你的碎片時間,能讓你在碎片化中學習一個知識點。

第一篇文章主要講解關於AOP中埋點的概念和相應的整合; 第二篇文章主要講解關於AspectJ中用到的一些知識點; 第三篇文章主要講解關於AspectJ在專案的其他一些應用。

1.專案中埋點的需求分析

1.1 首先先給菜鳥們科普一下什麼叫做埋點

所謂 埋點 ,百度百科是這麼說的!其實說簡單點,就是我在APP中都做了什麼事情,讓你們運營的知道,其實想想挺可怕的,這我要是出去浪,媳婦就知道了!!!明白了吧,你的一切行為都在掌控之中,用來生成人物畫像什麼的。。。一堆亂七八糟的!那麼我們程式設計師要做什麼呢?像什麼統計時長了,點選了什麼按鈕了,常去什麼頁面了等...好吧!剩下的就看你們運營需要什麼了,就科普到這裡吧!

1.2 常見的埋點方案

我整理了相應內容,我發現其實埋點可以分為:

  • 伺服器層面的:主要是通過APP端的請求進行分析
  • APP層面的:通過埋點進行相應的分析

作為一個移動端的猿,理所應當的從APP層面去分析相應的實現,現在在APP端的實現基本上分為以下幾種

  • 程式碼埋點:在需要的地方新增相應的程式碼,可謂是那裡需要寫哪裡!!!但是缺點同時體現出來了,那就是程式碼量會成噸的輸出,如果有一天你們專案經理跑過來改了某一個需求,程式碼更是成噸的增長,那個時候你會像"平安的程式設計師一樣"奮起反抗的!!!
  • 自動化埋點:通過一些特殊手段(相應的切面程式設計AOP思想,這個也是本文要說的重點!!!),對相應的方法進行統計!
  • 第三方實現 現在很多第三方都有,百度、友盟等...只要按照說明文件就可以了!

其實從程式設計師角度分析的話,無非就是程式碼寫得多少的事情嗎?往往許多內容都這能用這個東西衡量的,所以沒有實現不了的,大不了我就多寫點程式碼唄!但是為了讓你成為一名有逼格的程式猿,總是要學點什麼的!!!

2. AOP思想的應用

百度百科是這麼形容AOP的!面向切面程式設計。也就是說在某個切面,你可以做一些相應的操作!這麼和你比喻吧,當你觸發一個點選事件的時候,點選的一瞬間算是一個切面,你可以在這個切面的前後加上一些相應的內容,也就是相應的切面程式設計了!

能解決什麼問題呢? 往往很多人都會這麼問?有這樣一個需求,一些APP只有在登陸的情況下才能做一些事情,往往有很多按鈕都需要判斷登陸的情況,如果你每一個按鈕都寫一個判斷方法,那程式碼就很多了,如果產品跑過來說在新增一個VIP的功能你怎麼辦?所有的地方都要改?我擦,毀滅性的啊!這個時候就可以使用AOP這種程式設計思想了!再點選之前做一些相應的處理,那麼即便是你在改的話,也只需要改一個地方!

上面說了那麼多都是廢話,只是瞭解一下就可以了!我看Android中使用AOP基本上都是使用註解和一個叫AspectJ這麼個東西,都說是非侵入式埋點,這個非侵入式是一個很好的東西,也就是不用更改之前程式碼的邏輯就可以實現相應的需求,所以我覺得埋點使用這個東西就非常好了!

3. AspectJ怎麼整合到專案中(難點1)

關於AspectJ這個東西的整合,要用到一些gradle中的知識,其實對這裡的知識我也不是很瞭解,也不再我們今天要講的內容中,所以這裡直接跳過了,感興趣的同學可以自行百度,這個插一句(學習要有目的性,如果你要學某一個東西的話,其它的東西真的可以先放一放!!!)我就講講怎麼整合就好了!!!

3.1 新增相應的依賴

首先說明一個事情,因為程式碼是非侵入性的,所以建議你把AspectJ整合在一個專門的Module中,這樣在不改變原有的內容就能實現相應的方案。why?因為我就是這麼做的。。。

3.1.1 首先在 專案 的build.gradle中新增相應的依賴

    classpath 'org.aspectj:aspectjtools:1.8.9'
    //雖然都說句要加,但是我沒加程式還是正常執行的!
    classpath 'org.aspectj:aspectjweaver:1.8.9'
複製程式碼

位置圖

整段程式碼是這樣滴!

// Top-level build file where you can add configuration options common to all sub-projects/modules.

buildscript {
    
    repositories {
        google()
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.1.4'
        classpath 'org.aspectj:aspectjtools:1.8.9'
        //我發現這個東西不加也是可以正常執行的
//        classpath 'org.aspectj:aspectjweaver:1.8.9'
    }
}

allprojects {
    repositories {
        google()
        jcenter()
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}
複製程式碼

3.1.2 其次在專案中新增依賴和一些必要的配置

在專案的build.gradle中新增相應的依賴implementation 'org.aspectj:aspectjrt:1.8.9'

然後在 根路徑 新增相應的配置

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
    }

    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
            }
        }
    }
}
複製程式碼

別問我為什麼?我真的不理解這段程式碼,反正我知道這段程式碼是必須的。

程式碼位置

整段程式碼是這樣滴!

apply plugin: 'com.android.application'

android {
    compileSdkVersion 28
    defaultConfig {
        applicationId "com.jinlong.aspectjdemo"
        minSdkVersion 14
        targetSdkVersion 28
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    implementation fileTree(include: ['*.jar'], dir: 'libs')
    implementation 'com.android.support:appcompat-v7:28.0.0-rc01'
    implementation 'com.android.support.constraint:constraint-layout:1.1.2'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
    implementation project(':aspectmodule')
    implementation 'org.aspectj:aspectjrt:1.8.9'
}

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
    }

    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
            }
        }
    }
}
複製程式碼

在這裡說明以下,如果你要是在Module中使用,那麼在app的build.gradle中也要進行相應的配置!切記!!! 重要的事情說 "三遍"!!!這裡直接貼一下相應Module中的配置!

apply plugin: 'com.android.library'

android {
    compileSdkVersion 28

    defaultConfig {
        minSdkVersion 14
        targetSdkVersion 28
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"

    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }

}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])

    implementation 'com.android.support:appcompat-v7:28.0.0-rc01'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'

    implementation 'org.aspectj:aspectjrt:1.8.9'
}

import org.aspectj.bridge.IMessage
import org.aspectj.bridge.MessageHandler
import org.aspectj.tools.ajc.Main
final def log = project.logger

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

    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
            }
        }
    }
}
複製程式碼

注意點

注意這裡主專案和類庫中是不一樣的!!!以上可以保證你編譯通過了,但是這才是開始的配置!!!

3.1.3 配置相應的類

這裡先說以下相應的配置,具體為什麼先不去說!!!下篇文章我會盡我所能給你講解清楚的!!!相信我

AOP埋點從入門到放棄(一)(2018年08月13日修改)

@Aspect
public class TraceAspect {

    private static final String TAG = "hjl";

    @Before("execution(* android.app.Activity.on*(..))")
    public void onActivityMethodBefore(JoinPoint joinPoint) throws Throwable {
        String key = joinPoint.getSignature().toString();
        Log.e(TAG, "onActivityMethodBefore: 切面的點執行了!" + key);
    }
}
複製程式碼

首先說一下這段注意事項:

  • 頂部的註解@Aspect是不能少的,如果沒有它一切都是扯淡!!!
  • @Before("execution(* android.app.Activity.on*(..))") 這段程式碼才是核心,先簡單說一下,這段程式碼主要表述的內容是,檢測所有activity中以on開頭的方法,比如onCreate()然後前面的@Before說明的是在這個方法執行前執行裡面的!這樣你就可以執行程式,直接看LOG就可以了,

簡單說明一下原理,通過上面這個類,主要是在方法中的最開始新增一個相應的方法,也就是把你寫的這段程式碼以一個方法的形式新增到某個位置!這樣就實現了通過切面進行相應的處理方案了!!!

想看原始碼嗎?想看連結嗎?點這裡

相關文章