安装AspectJ
Pangu-Immortal (Pangu-Immortal) · GitHub
Android上的ApsectJ开发由几部分组成,AspectJ gradle插件,ApsectJ依赖,还有 AspectJ编译器。
首先安装AspectJ编译器很简单,就跟安装JAVA环境一样,
下载链接:http://www.eclipse.org/downloads/download.php?file=/tools/aspectj/aspectj-1.9.0.jar
目前最新的已经更新到1.9.1了。如果你电脑已经有JAVA环境的话直接运行这个jar包就行,
在安装完毕后需要配置环境变量到 aspectj的bin目录下,这里不赘述
export PATH="$PATH:~/Library/Android/sdk/platform-tools"
export PATH="$PATH:/usr/local/opt/gradle/gradle-4.1/bin"
export PATH="$PATH:~/Library/Android/sdk/ndk-bundle"
export PATH="$PATH:~/Library/flutter/bin"
export PATH="$PATH:~/Library/kotlinc/bin"
export PATH="$PATH:~/Library/AspectJ/bin" <- AspectJ的PATH
配置完后运行 ajc -v 应该可以看到对应输出
AspectJ Compiler 1.9.1 (1.9.1 - Built: 2018 at 17:52:10 GMT)
赠送源码:https://github.com/Pangu-Immortal。
《最完整的Android逆向知识体系》
配置Android Gradle增加AspectJ依赖
首先需要把 AspectJ 依赖加到 gradle根目录中,
buildscript {
repositories {
maven { url 'https://maven.google.com' }
maven { url 'https://jitpack.io' }
jcenter()
google()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.2.1'
classpath 'org.aspectj:aspectjtools:1.9.1'
classpath 'org.aspectj:aspectjweaver:1.9.1'
}
}
然后在项目app目录的build.gradle需要添加以下内容,
import org.aspectj.bridge.IMessage
import org.aspectj.bridge.MessageHandler
import org.aspectj.tools.ajc.Main
apply plugin: 'com.android.application'
android {
...
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation 'org.aspectj:aspectjrt:1.9.1'
}
final def log = project.logger
final def variants = project.android.applicationVariants
variants.all { variant ->
if (!variant.buildType.isDebuggable()) {
log.debug("Skipping non-debuggable build type '${variant.buildType.name}'.")
return
}
JavaCompile javaCompile = variant.javaCompile
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
}
}
}
}
创建AspectJ代码
下面这部分代码看起来会一脸懵逼,不过目前先不用管具体的语法含义,
先跑起来环境,然后再结合理论慢慢在修改代码中感受就能快速的上手AOP了。
以一个HelloWorld为例子,我们的MainActivity中啥事情不干,只有基本的生命周期方法。
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@Override
protected void onStart() {
super.onStart();
}
}
现在我们要写一个AspectJ类。
这个类要做的事情是告诉ACJ编译器,要在MainActivity中的每个方法前面打印一行log,输出当前执行的是哪个方法,
@Aspect
public class AspectTest {
@Pointcut("execution(* phoenix.com.helloaspectj.MainActivity.**(..))")
public void executeAspectJ() {
} // 注意*号后面必须有空格
@Before("executeAspectJ()")
public void beforeAspectJ(JoinPoint joinPoint) throws Throwable {
Log.d(”tag“, "injected -> " + joinPoint.toShortString());
}
}
第一次接触AspectJ的看到这段代码有点摸不着头脑,解释一下几个注解的意思,
@Aspect: 告诉ACJ编译器这是个AspectJ类
@PointCut: PointCut是AspectJ中的一个概念,跟它一起的另一个概念是 JoinPoint,这两个概念一起描述要注入的切面
@Before: 表示要注入的位置,常用的有 Before/After/Around,分别表示在执行前,执行后,和取代原方法
这里@PointCut注解后的参数表示的意思是对 MainActivity中的所有方法进行注入,参数用的是正则匹配语法。
下面看看这段代码执行的结果
16:04:56.611 22823-22823/? D/tag: injected -> execution(MainActivity.onCreate(..))
16:04:56.661 22823-22823/? D/tag: injected -> execution(MainActivity.onStart())
看到虽然我们没有在MainActivity中写入log打印语句,但是通过AspectJ实现了,在MainActivity两个生命周期执行前插入了我们自己的log。
严格的说使用方式有两种,随便网上搜一下都能找到,例子就不多举了,随便体验一个简单,有了直接的感受就好。
本篇主要目的还是让新手快速上手并亲身体会,关键 让你知道我们开发的时候用AspectJ来干点啥。。
(知识点很简单,主要还是消除大家心里的疑惑,带着疑惑很难接受新知识)---这也是我的一个学习方法。
Pointcuts 示例
以下示例表示在aspectjx插件下,相同包是指同一个aar/jar包,AspectJ常规配置下不同包不能执行“execution”织入
execution
execution(* com.howtodoinjava.EmployeeManager.*( .. ))
匹配EmployeeManger接口中所有的方法
execution(* EmployeeManager.*( .. ))
当切面方法和EmployeeManager接口在相同的包下时,匹配EmployeeManger接口中所有的方法
execution(public * EmployeeManager.*(..))
当切面方法和EmployeeManager接口在相同的包下时,匹配EmployeeManager接口的所有public方法
execution(public EmployeeDTO EmployeeManager.*(..))
匹配EmployeeManager接口中权限为public并返回类型为EmployeeDTO的所有方法。
execution(public EmployeeDTO EmployeeManager.*(EmployeeDTO, ..))
匹配EmployeeManager接口中权限为public并返回类型为EmployeeDTO,第一个参数为EmployeeDTO类型的所有方法。
execution(public EmployeeDTO EmployeeManager.*(EmployeeDTO, Integer))
匹配EmployeeManager接口中权限为public、返回类型为EmployeeDTO,参数定义为EmployeeDTO,Integer的所有方法。
"execution(@com.xyz.service.BehaviorTrace * *(..))"
匹配注解为"@com.xyz.service.BehaviorTrace",返回值为任意类型,任意包名下的任意方法。
within
任意连接点:包括类/对象初始化块,field,方法,构造器
within(com.xyz.service.*)
com.xyz.service包下任意连接点
within(com.xyz.service..*)
com.xyz.service包或子包下任意连接点
within(TestAspect)
TestAspect类下的任意连接点
within(@com.xyz.service.BehavioClass *)
持有com.xyz.service.BehavioClass注解的任意连接点
主要应用场景:(重点)
- 数据统计
- 日志记录
- 用户行为统计
- 应用性能统计
- 数据校验
- 行为拦截
- 无侵入的在宿主中插入一些代码,
- 做日志埋点
- 性能监控
- 动态权限控制
- 代码调试
AspectJ 只是 AOP 的一种手段,类似的还有用 asm 去修改字节码。
AOP之所以会有越来越多的人去了解。
- 第一,非常好的去耦合。
- 第二,可以用AOP来实现无痕埋点,数据收集,甚至修改SDK中动不了的代码。
暂不支持 Java 9 以上平台
其他问题请看插件的文档和 Issues。
赠送源码:https://github.com/Pangu-Immortal。
《最完整的Android逆向知识体系》