一、集成方法一
- 需要在项目根目录的build.gradle中增加依赖:
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.3.0-beta2'
classpath 'com.hujiang.aspectjx:gradle-android-plugin-aspectjx:1.0.10'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
- 再主项目或者库的build.gradle中增加AspectJ的依赖
compile 'org.aspectj:aspectjrt:1.8.9'
同时在build.gradle中加入AspectJX模块
apply plugin: 'android-aspectjx'
这样就把整个Android Studio中的AspectJ的环境配置完毕了,如果在编译的时候,遇到一些『can’t determine superclass of missing type xxxxx』这样的错误,请参考项目README中关于excludeJarFilter的使用。
aspectjx {
//includes the libs that you want to weave
includeJarFilter 'universal-image-loader', 'AspectJX-Demo/library'
//excludes the libs that you don't want to weave
excludeJarFilter 'universal-image-loader'
}
二、集成方法二
- AspectJ比较强大,除了支持对source文件(即aj文件、或@AspectJ注解的Java文件,或普通java文件)直接进行编译外,
- 还能对Java字节码(即对class文件)进行处理。有感兴趣的同学可以对aspectj-test小例子的class文件进行反编译,你会发现AspectJ无非是在被选中的JPoint的地方加一些hook函数。当然Before就是在调用JPoint之前加,After就是在JPoint返回之前加。
- 更高级的做法是当class文件被加载到虚拟机后,由虚拟机根据AOP的规则进行hook。
在Android里边,我们用得是第二种方法,即对class文件进行处理:
//AndroidAopDemo.build.gradle
//此处是编译一个App,所以使用的applicationVariants变量,否则使用libraryVariants变量
//这是由Android插件引入的。所以,需要import com.android.build.gradle.AppPlugin;
android.applicationVariants.all { variant ->
/*
这段代码之意是:
当app编译个每个variant之后,在javaCompile任务的最后添加一个action。此action
调用ajc函数,对上一步生成的class文件进行aspectj处理。
*/
AppPluginplugin = project.plugins.getPlugin(AppPlugin)
JavaCompile javaCompile = variant.javaCompile
javaCompile.doLast{
String bootclasspath =plugin.project.android.bootClasspath.join(File.pathSeparator)
//ajc是一个函数,位于utils.gradle中
ajc(bootclasspath,javaCompile)
}
}
ajc函数其实和我们手动试玩aspectj-test目标一样,只是我们没有直接调用ajc命令,而是利用AspectJ提供的API做了和ajc命令一样的事情。
import org.aspectj.bridge.IMessage
import org.aspectj.bridge.MessageHandler
import org.aspectj.tools.ajc.Main
def ajc(String androidbootClassFiles,JavaCompile javaCompile){
String[] args = ["-showWeaveInfo",
"-1.8", //1.8是为了兼容java 8。请根据自己java的版本合理设置它
"-inpath",javaCompile.destinationDir.toString(),
"-aspectpath",javaCompile.classpath.asPath,
"-d",javaCompile.destinationDir.toString(),
"-classpath",javaCompile.classpath.asPath,
"-bootclasspath", androidbootClassFiles]
MessageHandlerhandler = new MessageHandler(true);
new Main().run(args,handler)
deflog = project.logger
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
throw message.thrown
break;
case IMessage.WARNING:
case IMessage.INFO:
log.info message.message, message.thrown
break;
case IMessage.DEBUG:
log.debug message.message, message.thrown
break;
}
}
}
主要利用了https://eclipse.org/aspectj/doc/released/devguide/ajc-ref.html中TheAspectJ compiler API一节的内容。由于代码已经在csdn git上,大家下载过来直接用即可。
三、DebugTrace
有该注解的函数,打印执行时间
3.1 定义注解DebugTrace
/**
* Indicates that the annotated method is being traced (debug mode only) and
* will use {@link android.util.Log} to print debug data:
* - Method name
* - Total execution time
* - Value (optional string parameter)
*/
@Retention(RetentionPolicy.CLASS)
@Target({ ElementType.CONSTRUCTOR, ElementType.METHOD })
public @interface DebugTrace {}
4.1 计时工具类
public class TimeWatcher {
private long startTime;
private long endTime;
private long elapsedTime;
public TimeWatcher() {
//empty
}
private void reset() {
startTime = 0;
endTime = 0;
elapsedTime = 0;
}
public void start() {
reset();
startTime = System.nanoTime();
}
public void stop() {
if (startTime != 0) {
endTime = System.nanoTime();
elapsedTime = endTime - startTime;
} else {
reset();
}
}
public long getTotalTimeMillis() {
return (elapsedTime != 0) ? TimeUnit.NANOSECONDS.toMillis(endTime - startTime) : 0;
}
public long getStartTime() {
return startTime;
}
public long getEndTime() {
return endTime;
}
}
3.3 切面编程
/**
* 类描述:
* Aspect representing the cross cutting-concern: Method and Constructor Tracing.
* Created by yhf on 2017/3/28.
*/
@Aspect
public class DebugTraceAspect {
public static final String TAG = "MainActivity-DTAspect";
//有这个注解的 返回值任意的 所有类的所有函数且参数不限
private static final String POINTCUT_METHOD =
"execution(@com.sangfor.aop.annotation.DebugTrace * *(..))";
//有这个注解的 返回值任意的 所有类的构造函数且参数不限
private static final String POINTCUT_CONSTRUCTOR =
"execution(@com.sangfor.aop.annotation.DebugTrace *.new(..))";
@Pointcut(POINTCUT_METHOD)
public void methodAnnotatedWithDebugTrace() {}
@Pointcut(POINTCUT_CONSTRUCTOR)
public void constructorAnnotatedDebugTrace() {}
@Around("methodAnnotatedWithDebugTrace() || constructorAnnotatedDebugTrace()")
public Object debugTraceJoinPoint(ProceedingJoinPoint joinPoint) throws Throwable {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
String className = methodSignature.getDeclaringType().getSimpleName();
String methodName = methodSignature.getName();
final TimeWatcher stopWatch = new TimeWatcher();
stopWatch.start();
Object result = joinPoint.proceed();
stopWatch.stop();
LogCore.aspectI(TAG,
new TimeAspectData("DebugTraceAspect","debugTraceJoinPoint",joinPoint,stopWatch).toString());
return result; //修改这里,相当于hook了返回值
}
}
几个在此提到的重点:
- 我们声明了两个作为 pointcuts 的 public 方法,筛选出所有通过 “org.android10.gintonic.annotation.DebugTrace” 注解的方法和构造函数。
- 我们使用 “@Around” 注解定义了 “weaveJointPoint(ProceedingJoinPoint joinPoint)” 方法,使我们的代码注入在使用 “@DebugTrace” 注解的地方生效。
- “Object result = joinPoint.proceed();” 这行代码是被注解的方法执行的地方。因此,在此之前,我们启动我们的计时类计时,在这之后,停止计时。
- 最后,我们构造日志信息,用 Android Log 输出。
@DebugTrace
private int mapGUI() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
testAOP();
return 1;
}
四、SecurityCheckAnnotation
检查权限
4.1 定义注解
//第一个@Target表示这个注解只能给函数使用
//第二个@Retention表示注解内容需要包含的Class字节码里,属于运行时需要的。
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SecurityCheckAnnotation {//@interface用于定义一个注解。
public String declaredPermission(); //declarePermssion是一个函数,其实代表了注解里的参数
}
4.2 定义切面
@Aspect
public class SecurityCheckAnnotationAspect {
public static final String TAG = "MainActivity-SCAAspect";
/*
来看这个Pointcut,首先,它在选择Jpoint的时候,把@SecurityCheckAnnotation使用上了,这表明所有那些public的,并且携带有这个注解的API都是目标JPoint
接着,由于我们希望在函数中获取注解的信息,所有这里的poincut函数有一个参数,参数类型是SecurityCheckAnnotation,参数名为ann
这个参数我们需要在后面的advice里用上,所以pointcut还使用了@annotation(ann)这种方法来告诉AspectJ,这个ann是一个注解
*/
@Pointcut("execution(@com.sangfor.aop.annotation.SecurityCheckAnnotation * *(..)) && @annotation(securityCheckAnnotation)")
public void checkPermssion(SecurityCheckAnnotation securityCheckAnnotation){};
/*
接下来是advice,advice的真正功能由check函数来实现,这个check函数第二个参数就是我们想要
的注解。在实际运行过程中,AspectJ会把这个信息从JPoint中提出出来并传递给check函数。
*/
@Before("checkPermssion(securityCheckAnnotation)")
public void checkPermssionByAspect(JoinPoint joinPoint, SecurityCheckAnnotation securityCheckAnnotation){
//从注解信息中获取声明的权限。
String neededPermission = securityCheckAnnotation.declaredPermission();
Log.d(TAG, "checkPermssionByAspect--" + joinPoint.toShortString());
Log.d(TAG, "checkPermssionByAspect -- \tneeded permission is " + neededPermission);
return;
}
}