AOP埋點從入門到放棄

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

 

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


特別聲明:

感謝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 配置相應的類

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

 

@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就可以了,

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


 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章