Android开发利器-AOP


前言

AOP(Aspect Oriented Programming)面向切面编程,我们知道有OOP面向对象编程,提倡功能模块化,为啥要学习AOP呢,本文将从是什么、为什么、怎么用来介绍AOP,看完本文你一定会对AOP有一个完整的认识。


一、AOP是什么?

面向切面编程,顾名思义就是以切入的方式实现某些功能,我们在实现某些功能,诸如日志埋点、性能统计、防抖、权限控制、为了减少对原有代码的入侵,采用这种编程方法。

我们先看看不使用AOP的方式实现一个统计方法耗时,代码如下:

       val beginTime = SystemClock.currentThreadTimeMillis()
        // 处理业务逻辑
        dosomething();
        val endTime = SystemClock.currentThreadTimeMillis()
        val dx = endTime - beginTime
        Log.e("ddup", methodSignature.name + "方法执行耗时:" + dx + "ms")

如果我们有多个方法需要统计,我们需要每个方法加上这样的代码,这样看起来是不是有很多重复代码,而且对我们原来的业务代码入侵,造成破坏。我们有没有一种打点的方式,类似注解的方式,将需要统计方法添加一个注解,最后再统一处理,类似下面的代码:

  @AopMethodTime
    fun loadData() {
        for (index in 1..1000) {
            Log.e("ddup", "load data..." + index)
        }
    }

需要统计的方法,只需要添加@AopMethodTime,是不是简单多了,这就是AOP魅力所在。

了解AOP,我们需要了解一下几个基本概念:

  • Advice:通知、增强处理,想要的功能,比如上面的方法耗时统计。
  • Join Point:连接点 允许通知(Advice)代码执行前、中、后。
  • Pointcut:切入点 需要切入的地方。
  • Aspect:对一类行为的抽象,是切点的集合。
  • Weaving:把切面应用到目标对象来创建新的代理对象的过程。

这些概念有些晦涩难懂,说实话我第一次接触AOP看一堆术语,也觉得麻烦,不过不用担心,有个印象就行,自己动手实现一个例子就没有问题了。

二、常用AOP技术

介绍完AOP是什么,我们来说说常用的AOP技术,从织入的时机来看,分为静态织入和动态织入。
主要的技术框架如下:

织入时机技术框架
静态织入APT,AspectJ、ASM、Javassit
动态织入Java动态代理,cglib、Javassit

下面简单的介绍上面的技术框架:

APT

APT(Annotation Processing Tool)即注解处理器,它对源代码文件进行检测找出其中的Annotation,使用Annotation进行额外的处理,简单的来说,它就是帮助我们扫描注解并生成java代码工具的技术,我们熟悉的Butterknife也是基于此技术实现的,感兴趣可以之前我之前写的一篇文章手把手教你实现一个Butterknife

AspectJ

AspectJ是一种严格意义上的AOP技术,因为它提供了完整的面向切面编程的注解,这样让使用者可以在不关心字节码原理的情况下完成代码的织入,因为编写的切面代码就是要织入的实际代码,我介绍的技术也是基于此,Hugo统计方法耗时的框架也是基于此。

ASM

ASM是非常底层的面向字节码编程的AOP框架,理论上可以实现任何关于字节码的修改,非常硬核。许多字节码生成API底层都是用ASM实现,常见比如Groovy、cglib,因此在Android平台下使用ASM无需添加额外的依赖。完整的学习ASM必须了解字节码和JVM相关知识。

Javassit

javassit是一个开源的字节码创建、编辑类库,现属于Jboss web容器的一个子模块,特点是简单、快速,与AspectJ一样,使用它不需要了解字节码和虚拟机指令。

Java动态代理

JDK本身的一种技术啊,是代理模式的一种实现,运行时动态增强原始类的行为,实现方式是运行时直接生成class字节码并将其加载进虚拟机,我们在使用Android hook技术会经常用到它,具体使用,这里我就不介绍了。

cglib

CGLIB是一个强大的、高性能的代码生成库。其被广泛应用于AOP框架(Spring、dynaop)中,用以提供方法拦截操作。代码库地址:https://github.com/cglib/cglib

上述技术部分介绍引自谈谈Android AOP技术方案

二、使用步骤

介绍完相关技术后,下面通过几个完整例子来实际体验一下AOP技术。由于最近一直在学习kotlin,以下基于kotlin实现,java代码实现,下面会介绍一个开源库啊。

1.引入库

Project build.gradle引入库:

classpath 'com.hujiang.aspectjx:gradle-android-plugin-aspectjx:2.0.10'

app build.gradle引入库:

    implementation "org.aspectj:aspectjrt:1.9.5"

2.实际例子

定义一个快速点击注解:

@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.FUNCTION)
annotation class AopOnClick(
    val value: Long = 2000
)

定义一个快速点击的切面aspect:

/**
     * 定义切点,标记切点为所有被@AopOnclick注解的方法
     */
    @Pointcut("execution(@com.example.android_aop.annotation.AopOnClick * *(..))")
    fun methodClick() {
    }


    /**
     * 定义一个切面方法,包裹切点方法
     */
    @Around("methodClick()")
    @Throws(Throwable::class)
    fun aroundClickJoinPoint(joinPoint: ProceedingJoinPoint) {
        // 取出方法的注解
        val methodSignature = joinPoint.signature as MethodSignature
        val method = methodSignature.method
        if (!method.isAnnotationPresent(AopOnClick::class.java)) {
            return
        }
        val aopOnclick = method.getAnnotation(AopOnClick::class.java)
        var view: View? = null;
        for (arg in joinPoint.args) {
            if (arg is View) {
                view = arg
                break
            }
        }
        // 判断是否快速点击
        if (view != null && !AopClickUtils.isFastDoubleClick(view, aopOnclick.value)) {
            // 不是快速点击,执行原方法
            joinPoint.proceed()
        }
    }

这里主要说一下execution(@com.example.android_aop.annotation.AopOnClick * *(…)),这个是我们要切入的注解,这个地址要写对,要不能会报错,aopOnclick.value是我们使用地方传入的值,**joinPoint.proceed()**是原来方法的执行,我们可以控制是否需要拦截,joinPoint参数可以获取注解方法里面的参数。

防抖的处理AopClickUtils:

    /**
     * 最近一次点击时间
     */
    private var mLastClickTime: Long = 0

    /**
     * 最近一次点击的控件ID
     */
    private var mLastClickViewId: Int = 0


    /**
     * 是否快速点击
     */
    fun isFastDoubleClick(v: View, intervalMillis: Long): Boolean {

        var time = System.currentTimeMillis();
        var timeInterval = time - mLastClickTime
        val viewId = v.id
        if (timeInterval > 0 && timeInterval < intervalMillis && viewId == mLastClickViewId) {
            return true
        } else {
            mLastClickTime = time
            mLastClickViewId = viewId;
            return false
        }


    }

调用如下:

    /**
     * 快速点击
     */
    @AopOnClick(1000)
    fun noDoubleClick(v: View?) {
        Log.e("ddup", "noDoubleClick...")

    }

我们在需要防止快速点击的方法,添加如下@AopOnClick(1000)注解,1000代表一秒执行一次,打印一次noDoubleClick…日志

3.更多参考

申请权限:

  /**
     * 申请权限
     */
    @AopPermission(
        [PermissionConsts.CALENDAR, PermissionConsts.CAMERA, PermissionConsts.LOCATION]
    )
    private fun requestPermissions() {
        Log.e("ddup", "requestPermissions success...")
    }

添加上面代码可以就可以申请日历、相机、定位的权限
切换线程:

    /**
     * 改变线程
     */
    @AopOnClick()
    @AopIOThread(ThreadType.Single)
    private fun changeThread(v: View?) {
        Log.e("ddup", "current thread = " + Thread.currentThread().name.toString())
    }

添加上面代码可以就可以切换到单线程实现的子线程里面

4.项目地址

参考例子地址:github地址,觉得不错的给个🌟。
java实现:大神写的一个aop框架


总结

至此已全部介绍完了AOP相关知识,从上面的例子可以看出,AOP对我们Android在实现某些功能,对原有代码几乎无侵入性,使用简单,首先我们定义一个注解,需要调用的地方加上注解,而且我们可以对同一个方法添加多个注解,切入多次,然后在定义一个切面,实现我们要切入的内容,并且可以选择是否拦截原有方法,最后再需要切入的地方添加注解即可。

当然AOP也不是十分完美的,出现问题相比一般的错误,定位相对要更加麻烦,基于AspectJ的实现方式会存在重复织入、不织入的问题。

最后,我想说的是AOP不乏是一种很好的技术,我们可以在实现某些功能,尝试使用它,一定会让我们事半功倍。

Thanks

谈谈Android AOP技术方案

Android面向切面编程(AOP)

XAOP

创作不易,觉得不错的话,请点赞、评论鼓励,谢谢。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值