(4.6.23.2)Android之面向切面编程:Aspect集成示例之DebugTrace 与 SecurityCheckAnnotation

一、集成方法一

  • 需要在项目根目录的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;
    }

}

参考文献

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值