相信有几年发开经验的同学都知道 AOP 相比 OOP 在一些如集中登录,防止快速点击,方法调用日志这些场景下有着天然的优势。
然而当前流行的 AspectJ 对安卓项目兼容性太差,各种问题排查起来贼麻烦……
为了提升公司项目的开发效率基于 Transform + ASM 开发了 BytecodeUtil 这个编译时修改字节码的开发平台。
Transform 和 ASM 不了解可点这里
项目源码点这里
性能BytecodeUtil 内部采用多线程并发处理 IO,并提供了过滤接口用于过滤你不想处理的 jar 内文件
目前在公司项目中需要处理的 Jar 包数为 238 个,总占用 74.3MB。如图:
在不过滤全量处理时需要 16s
合理配置项目所需的处理范围后缩短到了 3s
由于发布到 mavenCentral 有点麻烦,目前暂时先点这下载 。
-
下载所需的代码包后在你的 Gradle 项目根目录下解压
-
配置根目录下的 build.gradle 来使用解压中的 jar 包
// Top-level build file buildscript { // 解压的仓库地址 ext.repos = uri('./repos') repositories { // 使用仓库中的 jar maven { url repos } ... ... } dependencies { ... ... classpath "io.github.ysj001:bytecodeutil-plugin:1.0.4" } } allprojects { repositories { // 使用仓库中的 jar maven { url repos } ... ... } } -
application 模块的 build.gradle 配置依赖
apply plugin: 'com.android.application' // 使用 bytecodeutil 插件 apply plugin: 'bytecodeutil-plugin' // 插件扩展 bytecodeUtil { // 设置插件日志级别 loggerLevel = 1 // 挂载你所需的修改器 modifiers = [ // 如:挂载 AOP 修改器 Class.forName("com.ysj.lib.bytecodeutil.plugin.core.modifier.aspect.AspectModifier") ] // 不需要处理的 jar 包内文件过滤器。合理配置可大幅提升编译速度 notNeedJar = { entryName -> // 这里演示个比较通用的 (entryName.startsWith("kotlin") || entryName.startsWith("java") || entryName.startsWith("org/intellij/") || entryName.startsWith("org/jetbrains/") || entryName.startsWith("org/junit/") || entryName.startsWith("org/hamcrest/") || entryName.startsWith("com/squareup/") || entryName.startsWith("android") || entryName.startsWith("com/google/android/")) } } ... ... dependencies { ... ... implementation "io.github.ysj001:bytecodeutil-api:1.0.4" }
-keepclassmembers class * {
@com.ysj.lib.bytecodeutil.api.util.BCUKeep ;
}
AOP
需要挂载该修改器:com.ysj.lib.bytecodeutil.plugin.core.modifier.aspect.AspectModifier
介绍Aspect Oriented Programming。面向切面编程,它的应用是指在不修改源代码的情况下给程序动态添加功能,并将这个功能统一横切到一处统一处理的一种编程方式,能充分体现了高内聚低耦合的编程思想。
案例(DEMO)-
演示在 MainActivity 的 onCreate 函数体开头插入如下打印 log 的方法
// 使用该注解标识该类是切面类 @Aspect object AopTest { // 使用该注解标识该方法是一个切入点方法,该方法会被织入到指定 @Pointcut( target = "class:.*MainActivity", funName = "onCreate", funDesc = "\(Landroid/os/Bundle;\)V", position = POSITION_START, // 设置织入到目标方法的开头 ) fun log(jp: JoinPoint ) { Log.i(TAG, "捕获到: ${jp.target} args:${jp.args}") } } -
演示代理任意使用了 IntervalTrigger 注解的方法实现间隔触发
// 自定义一个间隔触发的注解 @Target(AnnotationTarget.FUNCTION) @Retention(AnnotationRetention.RUNTIME) annotation class IntervalTrigger( val intervalMS: Long = 1000 ) @Aspect object AopTest { var oldTriggerTime = 0L @Pointcut( target = "annotation:L.*IntervalTrigger;", position = POSITION_CALL, // 设置在调用位置代理源方法 ) fun log(callingPoint: CallingPoint) { val trigger = callingPoint.annotation(IntervalTrigger::class.java) ?: return val currentTimeMillis = System.currentTimeMillis() if (currentTimeMillis - oldTriggerTime < trigger.intervalMS) { Log.i(TAG, "log5: 禁止触发") return } oldTriggerTime = currentTimeMillis Log.i(TAG, "log5: 成功触发") callingPoint.call() } } // 如下: class MainActivity : AppCompatActivity() { @LogPositionReturn override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) findViewById(R.id.test).setOnClickListener { test3() } } @IntervalTrigger(500) private fun test3() { // todo something } }



