android 开发,我们通常使用的是面向对象编程,这个写起来比较方便。但一些特殊的功能,比如说埋点统计些信息,或者打印某些方法的消耗时间,如果我们在要统计的地方直接写代码,看着不优雅,并且也把功能耦合在一起了。AOP 叫做切面编程,它更像一把刀切入到某个功能里面,不用直接耦合代码。比如如打印耗时日志,使用切面编程则可以把要统计的一些方法的代码统一放在一个地方,通过注解来引用,这样就比较完美的做到了代码分离。
AspectJ 是 AOP 的一个实现类库,我们可以直接使用它。先说说gradle配置方式,我们可以在 Android Studio 中建个 module 库,再库的 gradle 配置文件中,引入 aspectj 的单独脚本配置,我把它抽了出来,如下
build.gradle
******************
apply plugin: 'com.android.library'
apply from: 'asplib.gradle'
android {
compileSdkVersion 28
buildToolsVersion '28.0.3'
}
******************
asplib.gradle
*********************
import org.aspectj.bridge.MessageHandler
import org.aspectj.tools.ajc.Main
buildscript {
repositories {
mavenCentral()
google()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.4.1'
classpath 'org.aspectj:aspectjtools:1.8.1'
}
}
dependencies {
implementation 'org.aspectj:aspectjrt:1.8.1'
}
project.android.libraryVariants.all { variant ->
JavaCompile javaCompile = variant.javaCompileProvider.get()
javaCompile.doLast {
String[] args = ["-showWeaveInfo",
"-1.5",
"-inpath", javaCompile.destinationDir.toString(),
"-aspectpath", javaCompile.classpath.asPath,
"-d", javaCompile.destinationDir.toString(),
"-classpath", javaCompile.classpath.asPath]
MessageHandler handler = new MessageHandler(true)
new Main().run(args, handler)
}
}
*******************************
在 module 中,定义一个类 TrAspect ,用 @Aspect 来修饰它,比如我们来打印下 Activity 的声明周期日志,
@Aspect
public class TrAspect {
private static final String POINTCUT_ONMETHOD = "execution(* android.app.Activity.on**(..))";
@Before(POINTCUT_ONMETHOD)
public void beforeOnMethod(JoinPoint joinPoint) {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
String className = methodSignature.getDeclaringType().getSimpleName();
String methodName = methodSignature.getName();
Log.e("TrAspect", "before " + className + " " + methodName );
}
@After(POINTCUT_ONMETHOD)
public void onMethLog(JoinPoint joinPoint){
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
String className = methodSignature.getDeclaringType().getSimpleName();
String methodName = methodSignature.getName();
Log.e("TrAspect", "after " + className + " " + methodName );
}
}
在主工程 app 中,创建 MainActivity,重写 onCreate(Bundle savedInstanceState) 方法
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
然后我们编译下,发现无效,什么原因呢?原来 app 主工程中 build.gradle 也需要配置(我也不清楚原因),抽取出来,build.gradle 中配置 apply from:'asp.gradle'
asp.gradle
********************************
import org.aspectj.bridge.IMessage
import org.aspectj.bridge.MessageHandler
import org.aspectj.tools.ajc.Main
buildscript {
repositories {
mavenCentral()
google()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.4.1'
classpath 'org.aspectj:aspectjtools:1.8.1'
}
}
dependencies {
implementation project(':gintonic')
implementation 'org.aspectj:aspectjrt:1.8.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.javaCompileProvider.get()
javaCompile.doLast {
String[] args = ["-showWeaveInfo",
"-1.5",
"-inpath", javaCompile.destinationDir.toString(),
"-aspectpath", javaCompile.classpath.asPath,
"-d", javaCompile.destinationDir.toString(),
"-classpath", javaCompile.classpath.asPath,
"-bootclasspath", project.android.bootClasspath.join(File.pathSeparator)]
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
}
}
}
}
***************************
编译后,打印日志
2020-04-29 19:08:28.406 11794-11794/ E/TrAspect: before MainActivity onCreate
2020-04-29 19:08:28.495 11794-11794/ E/TrAspect: after MainActivity onCreate
细看 TrAspect 中使用的注解,@Before 和 @After 这两个,意思是在要切入的方法之前和之后执行,"execution(* android.app.Activity.on**(..))" 是我们配置的条件,这个里面是说 Activity 子类中所有以 on 开头的方法,当然是我们重写的方法,这就是个条件匹配,一旦匹配到了,马上执行方法。如果想把上面二合一,怎么办?使用 @Around,它具有 @Before 和 @After 的功能,见代码
@Around(POINTCUT_ONMETHOD)
public Object weaveJoinPoint(ProceedingJoinPoint joinPoint) throws Throwable {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
String className = methodSignature.getDeclaringType().getSimpleName();
String methodName = methodSignature.getName();
Log.e("TrAspect", "before " + className + " " + methodName );
Object result = joinPoint.proceed();
Log.e("TrAspect", "after " + className + " " + methodName );
return result;
}
joinPoint.proceed(); 的意思就是执行 onCreate() 方法,打印值日,和上面的一样。
同理,埋点也可以这么做,我们可以通过判断方法名字,把日志写入缓存或文本中,然后在主工程中通过读取缓存输入,上报到服务端。