【安卓学习之第三方库】 Aspect学习:AOP+注解

█ 【安卓学习之第三方库】 Aspect学习:AOP+注解

█ 相关文章:

█ 读前说明:

  • 本文通过学习别人写demo,学习一些课件,参考一些博客,’学习相关知识,如果涉及侵权请告知
  • 本文只简单罗列相关的代码实现过程
  • 涉及到的逻辑以及说明也只是简单介绍,主要当做笔记,了解过程而已
  • 下载demo
  • 本demo直接参考:https://github.com/getActivity,进行简单的集成测试,ok

█ 简介:

  1. @interface:(自)定义注解,核心用途是在反射上, 比如:写了个注解,注解接口就会接收你的注解内容,接着把他给注解处理类,一般配合APT或AOP一起使用的。(可以参考【JAVA编程思想第4版】-第20章 注解)。

    ● @interface 不是interface,是注解类。定义注解(Annotation)。
    ● 定义一个可用的注解,包括用于什么地方:类,方法,property,方法入参等等
    
  2. 元注解(meta annotation):Java标准库自带的,用来修饰其他注解。

  3. @Retention:元注解,定义了生命周期,默认为CLASS,自定义的Annotation都是RUNTIME。

    ● @Retention(RetentionPolicy.SOURCE) // 仅编译期,由编译器使用,一般只使用,不编写。
    ● @Retention(RetentionPolicy.CLASS)// 仅class文件,由底层工具库使用,涉及到class的加载,很少用到。
    ● @Retention(RetentionPolicy.RUNTIME) // 运行期,不但要使用,还经常需要编写。
    
  4. @Target:元注解,定义能够被应用于源码的哪些位置。

    ● @Target(ElementType.TYPE)   // 接口、类、枚举、注解
    ● @Target(ElementType.FIELD) // 字段、枚举的常量
    ● @Target(ElementType.METHOD) // 方法
    ● @Target(ElementType.PARAMETER) // 方法参数
    ● @Target(ElementType.CONSTRUCTOR)  // 构造方法
    ● @Target(ElementType.LOCAL_VARIABLE)// 局部变量
    ● @Target(ElementType.ANNOTATION_TYPE)// 注解
    ● @Target(ElementType.PACKAGE) // 包   	
    
  5. @Around注解可以用来在调用一个具体方法前和调用后来完成一些具体的任务。

    ● 比如我们想在执行XXX方法前打印出请求参数,并在方法执行结束后来打印出响应值
    
  6. 因为注解定义后也是一种class,所有的注解都继承自java.lang.annotation.Annotation,因此,读取注解,需要使用反射API。

  7. APT可以理解为运行时class加载工具,定义编译期的注解,再通过继承Proccesor实现代码生成逻辑,如ButterKnife, EventBus。

    ● APT:(Annotation Processing Tool)即注解处理器,是一种注解处理的工具
    ● 用来在编译器扫描以及处理注解。以注解作为桥梁,通过预先设定好的代码规则来生成对应的Java代码
    ● 只能创建新类,无法在当前类添加删除代码,无法修改sdk代码。
    
  8. AOP是一个比APT更强大的代码生成框架。通过预编译方式和运行期动态代理技术,把众多模块的某一类问题(打印日志、权限申请、检测网络、防重复点击等)进行统一管理,提高程序的可重用性。

    ● AOP:AOP为Aspect Oriented Programming的缩写,意为:面向切面编程。
    ● AOP其实是一种思想,实现这个思想技术的主要有三个派系:ASPECTJ,ASM,JAVASIST。
    ● 能创建新类,当前类添加删除代码,修改sdk代码。
    
  9. 切点函数execution:切入点语法定义

    ● 格式: execution([修饰符模式]  <返回类型模式> <方法名模式>(<参数模式>) [异常模式]) 
             execution (返回类型 包名..类名. *(..)) 
    ● demo: execution (* *..*. *(..))
    ● 第1个*号:表示返回类型, *号表示所有的类型;
    ● 第2个*号:表示包名,需要拦截的包名,后面的两个句点表示当前包和当前包的所有子包;
    ● 第3个*号:表示类名,*号表示所有的类;
    ● 第4个*号:表示方法名,*号表示所有的方法,后面括弧里面表示方法的参数,两个句点表示任何参数;
    ● execution (* com.bx.bxaspect..MainActivity. onCreate(..)):
       表示拦截com.bx.bxaspect包下的MainActivity文件中的onCreate方法。
    
  10. Pointcut定义时,还可以使用&&、||、! 这三个运算

这个例子中,logMessage()将匹配任何MessageSender和MessageReceiver中的任何方法。

@Pointcut("execution(* com.savage.aop.MessageSender.*(..))")
private void logSender(){}

@Pointcut("execution(* com.savage.aop.MessageReceiver.*(..))")
private void logReceiver(){}

@Pointcut("logSender() || logReceiver()")
private void logMessage(){}

在Android项目中使用AspectJ集成

  1. 根目录下的build.gradle文件中添加依赖:
buildscript {
    repositories {
        google()
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.5.1'
        // AOP 配置插件
        classpath 'com.hujiang.aspectjx:gradle-android-plugin-aspectjx:2.0.4'
    }
}
  1. APP目录下的build.gradle中添加AspectJ的依赖
apply plugin: 'com.android.application'
apply plugin: 'android-aspectjx'
android {
    compileSdkVersion 30
    buildToolsVersion "30.0.0"
    defaultConfig {  ...  }

    // AOP 配置
    aspectjx {
        // 排除一些第三方库的包名(Gson、 LeakCanary 和 AOP 有冲突)
        // 否则就会起冲突:ClassNotFoundException: Didn't find class on path: DexPathList
        exclude 'androidx', 'com.google', 'com.squareup', 'com.alipay', 'com.taobao', 'org.apache'
        // 织入遍历符合条件的库
        // includeJarFilter 'universal-image-loader', 'AspectJX-Demo/library'
        // 排除包含‘universal-image-loader’的库
        // excludeJarFilter 'universal-image-loader'
    }

    // 支持 Java JDK 8
    compileOptions {
        targetCompatibility JavaVersion.VERSION_1_8
        sourceCompatibility JavaVersion.VERSION_1_8
    }
}

dependencies {
    ...  
    implementation 'org.aspectj:aspectjrt:1.9.5'
}
  1. 自定义注解类,也可以不写

  2. 创建一个XXXAspect的类,如果有自定义注解类,可以直接和定义注解类绑定

█ 3个XXXAspect的类

- 
 ● MainAspect:

会在MainActivity.java 中的 protected void onXXX() {}前执行method3()

@Aspect
public class MainAspect {
    final String TAG = MainAspect.class.getSimpleName();
    // 方法切入点(一定要写,否则编译错误)
    @Before("execution(* *..MainActivity+.on**(..))")
    public void method3(JoinPoint joinPoint) throws Throwable {
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        String className = joinPoint.getThis().getClass().getSimpleName();
        Log.e("####--test", "a1." + TAG + ",class:" + className + ",method:" + methodSignature.getName());
    }
}
public class MainActivity extends AppCompatActivity implements View.OnClickListener{

    @Override
    protected void onCreate(Bundle savedInstanceState) {// 切入
    }
    
    @SingleClick
    @Override
    public void onClick(View v) {// 切入
    }
}
####--test: a1.MainAspect,class:MainActivity,method:onCreate
####--test: a1.MainAspect,class:MainActivity,method:onClick

● SingleClickAspect:

会在有注解@SingleClick的方法中,执行aroundJoinPoint()

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface SingleClick {
    long value() default 1000;// 快速点击的间隔
}
@Aspect
public class SingleClickAspect {

    private long mLastTime;// 最近一次点击的时间
    private int mLastId;// 最近一次点击的控件ID

    // 在连接点进行方法替换
    @Around("... && @annotation(singleClick)")
    public void aroundJoinPoint(ProceedingJoinPoint joinPoint, SingleClick singleClick) throws Throwable {
        Log.e("####--test", "b1.在连接点进行方法替换:aroundJoinPoint()");
        View view = null;
        for (Object arg : joinPoint.getArgs()) {
            if (arg instanceof View) {
                view = (View) arg;
            }
        }
        if (view != null) {
            long currentTime = Calendar.getInstance().getTimeInMillis();
            if (currentTime - mLastTime < singleClick.value() && view.getId()
                    == mLastId) {
                Log.e("SingleClick", "发生快速点击");
                return;
            }
            mLastTime = currentTime;
            mLastId = view.getId();
            //执行原方法
            joinPoint.proceed();
        }
    }
}
public class MainActivity extends AppCompatActivity implements View.OnClickListener{
    @SingleClick
    @Override
    public void onClick(View v) {// 切入
    }
}
E/####--test: b1.在连接点进行方法替换:aroundJoinPoint()
E/####--test: b2.响应点击事件:onClick()
SingleClick: 发生快速点击

● DebugLogAspect:

会在有注解@DebugLog的方法中,

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.CONSTRUCTOR})
public @interface DebugLog {
    String value() default "DebugLog";
}
@Aspect
public class DebugLogAspect {
    // 在连接点进行方法替换
    @Around("... && @annotation(debugLog)")
    public Object aroundJoinPoint(ProceedingJoinPoint joinPoint, DebugLog debugLog) throws Throwable {
        Log.e("####--test", "c1.在连接点进行方法替换:aroundJoinPoint()");
        enterMethod(joinPoint, debugLog);

        long startNanos = System.nanoTime();
        Object result = joinPoint.proceed();// 执行原始的方法
        long stopNanos = System.nanoTime();

        exitMethod(joinPoint, debugLog, result, TimeUnit.NANOSECONDS.toMillis(stopNanos - startNanos));

        return result;
    }
}
public class MainActivity extends AppCompatActivity implements View.OnClickListener{
    @DebugLog
    public void start(Context context, String phone, String code) {
        Log.e("####--test", "c4.执行跳转方法: MainActivity.start");
    }
}
E/####--test: b1.在连接点进行方法替换:aroundJoinPoint()
E/####--test: b2.响应点击事件:onClick()

● 全部日志打印

// 刚启动app
####--test: a1.MainAspect,class:MainActivity,method:onCreate
// 点击测试按钮1
####--test: a1.MainAspect,class:MainActivity,method:onClick
####--test: b1.在连接点进行方法替换:aroundJoinPoint()
####--test: b2.响应点击事件:onClick()
// 点击测试按钮3,会执行stat()方法
####--test: a1.MainAspect,class:MainActivity,method:onClick
####--test: b1.在连接点进行方法替换:aroundJoinPoint()
####--test: b2.响应点击事件:onClick()
####--test: c1.在连接点进行方法替换:aroundJoinPoint()
####--test: c2.方法执行前切入:enterMethod()
####--test: c3.获取方法的日志信息:getMethodLogInfo()
####DebugLog: ⇢ com.bx.bxaspect.MainActivity.start(context=com.bx.bxaspect.MainActivity@1eea895, phone=123456, code=123)
####--test: c4.执行跳转方法: MainActivity.start
####--test: c5.方法执行完毕,切出:exitMethod()
####DebugLog: ⇠ com.bx.bxaspect.MainActivity.start [0ms]

█ 注意点:

  • DebugLog 中定义了:ElementType.METHOD, ElementType.CONSTRUCTOR
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.CONSTRUCTOR})
public @interface DebugLog {

    String value() default "DebugLog";
}
  • 所以DebugLogAspect中要写*.new(…)和* *(…))
@Aspect
public class DebugLogAspect {
    @Pointcut("execution(@com.bx.bxaspect.aop.DebugLog *.new(..))")
    public void constructor() {
        Log.e("####--test", "constructor()");
    }
    @Pointcut("execution(@com.bx.bxaspect.aop.DebugLog * *(..))")
    public void method() {
        Log.e("####--test", "method()");
    }
}
  • 最后切点处理:@Around("(method() || constructor()) && @annotation(debugLog)")
    @Around("(method() || constructor()) && @annotation(debugLog)")
    public Object aroundJoinPoint(ProceedingJoinPoint joinPoint, DebugLog debugLog) throws Throwable {

    }
  • 如果不算这样的条件:逻辑上面可能会出错

█ 相关资料:

- 
 ● 1.Android中使用AspectJ
 ● 2.APT和AOP在android那些事情_a498415077的博客-CSDN博客
 ● 3.java自定义注解+注解功能实现的一个例子_还有点梦想的博客-CSDN博客_java自定义注解怎么实现注解
 ● 4.实现ButterKnife中绑定View的功能:Android APT案例_qq_34341338的博客-CSDN博客_android apt
 ● 5.Spring AOP 切面@Around注解的具体使用_lichuangcsdn的博客-CSDN博客
 ● 6.详解JDK 5 Annotation 注解之@Target的用法介绍_java_脚本之家
 ● 7.定义注解 - Java语言使用@interface语法来定义注解(Annotation)廖雪峰的官方网站
 ● 8.安卓AOP三剑客:APT,AspectJ,Javassist - 简书
 ● 9.AspectJ的切入点表达式—execution表达式详解_大叔比较胖的博客-CSDN博客_execution
 ● 10.Spring AOP 中@Pointcut的用法 - 山高我为峰 - 博客园
 ● 11.在Android项目中使用AspectJ的方法_Android_脚本之家
 ● 12.Android | 使用 AspectJ 限制按钮快速点击 - 简书连接2
 ● 13.Android | 一文带你全面了解 AspectJ 框架 - 简书连接2
 ● 13.项目简介 · xuexiangjys/XAOP Wiki · GitHub

转载请注明出处:
https://blog.csdn.net/ljb568838953/article/details/110525653

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值