AOP埋点从入门到放弃(一)(2018年08月13日修改)

6,431 阅读3分钟

今天老大跑过来说项目埋点了解一下!丢下了这句话之后,就没有之后了!剩下我一个人在风中凌乱!!!

其实这个需求老大在很久之前就说要开发了,后来就搁置了!但是今天看老大的态度,应该排到日程了!所以没办法只有硬着头皮磕了!免得过一阵子加班到很晚,所以趁着时间宽松,先能把踩的坑踩踩!!!分享给大家,也让大家能避免一些不必要的时间浪费。更好的过个周末,陪陪女盆友!!!


特别声明:

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

简单说明一下原理,通过上面这个类,主要是在方法中的最开始添加一个相应的方法,也就是把你写的这段代码以一个方法的形式添加到某个位置!这样就实现了通过切面进行相应的处理方案了!!!

想看源码吗?想看链接吗?点这里