Android AOP实战-防重点击(支持lambda表达式)

1,561 阅读2分钟

本篇文章是基于AspectJ实现AOP的。

AspectJ实际上是对AOP编程思想的一个实践,AOP虽然是一种思想,但就好像OOP中的Java一样,一些先行者也开发了一套语言来支持AOP。目前用得比较火的就是AspectJ了,它是一种几乎和Java完全一样的语言,而且完全兼容Java。

相关注解

@Aspect:声明切面,标记类
@Pointcut(切点表达式):定义切点,标记方法
@Before(切点表达式):前置通知,切点之前执行
@Around(切点表达式):环绕通知,切点前后执行
@After(切点表达式):后置通知,切点之后执行
@AfterReturning(切点表达式):返回通知,切点方法返回结果之后执行
@AfterThrowing(切点表达式):异常通知,切点抛出异常时执行

切点表达式

execution(<@注解类型模式>? <修饰符模式>? <返回类型模式> <方法名模式>(<参数模式>) <异常模式>?)

下面是一个防止重复点击的示例,刚刚应用到项目中,大家做个参考,支持lambda点击事件

根目录build.gradle中添加依赖

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

在使用的module中的build.gradle中添

apply plugin: 'android-aspectjx'
package com.fusheng.aspectjdemo

import android.util.Log
import android.view.View
import org.aspectj.lang.JoinPoint
import org.aspectj.lang.ProceedingJoinPoint
import org.aspectj.lang.annotation.Around
import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.Before
import org.aspectj.lang.annotation.Pointcut

/**
 * @Author: lixiaowei
 * @CreateDate: 2020/12/1 2:12 PM
 * @Description:切面控制点击事件重复问题,如果想让某个点击事件可以重复,
 * 可以在方法上添加@EnableDoubleClick注解
 */
@Aspect
class SingleClickAop {
    val TAG = "SingleClickAop"
    private val MIN_CLICK_DELAY_TIME = 600
    private var isDoubleClick: Boolean? = false
    private var preInvokerStr: String? = null
    private var clickViewKey: Int = -1

    companion object {
        //切点为OnClickListener.onClick方法的匹配表达式
        private const val CLICK_POINTCUTS = "execution(* android.view.View.OnClickListener.onClick(..))"

        //切点为lambda表达式的匹配表达式
        private const val CLICK_IN_LAMBDA_POINTCUTS = "execution(void *..lambda$*(..))"
    }

    @Pointcut(CLICK_POINTCUTS)
    fun onClickPointcuts() {
    }

    @Pointcut(CLICK_IN_LAMBDA_POINTCUTS)
    fun onClickInLambdaPointcuts() {
    }

    @Before("execution(@com.fusheng.aspectjdemo.EnableDoubleClick * *(..))")
    fun beforeEnableDoubleClick(joinPoint: JoinPoint) {
        isDoubleClick = true
    }

    @Around("onClickPointcuts()||onClickInLambdaPointcuts()")
    fun onClickListener(joinPoint: ProceedingJoinPoint) {
        if (isDoubleClick == true) {
            //如果发现有EnableDoubleClick注解标注,那么直接直接执行方法,之后返回
            isDoubleClick = false
            joinPoint.proceed()
            return
        }
        val args = joinPoint.args
        val view: View? = getViewFromArgs(args)
        clickViewKey = view?.id ?: -1
        Log.d(TAG, "clickViewKey----${clickViewKey}----")
        if (view == null) {
            //点击了非控件类型的元素,执行
            joinPoint.proceed()
            return
        }
        val lastClickTime = view.getTag(clickViewKey) as Long?
        val enableClick = canClick(lastClickTime ?: 0)//是否超过限制时间
        Log.d(TAG, "enableClick----${enableClick}----${lastClickTime}----")
        if (enableClick) {
            joinPoint.proceed()
            view.setTag(clickViewKey, System.currentTimeMillis())
            preInvokerStr = joinPoint.`this`?.toString() ?: ""
        } else {
            val isDiffInvoker = preInvokerStr != joinPoint.`this`?.toString() ?: ""//是否是相同的目标,防止OnClickListener嵌套引起的第二层onClick方法不执行问题
            //Log.d(TAG, "isSameTarget----${isDiffInvoker}----${preInvokerStr}----${joinPoint.`this`?.toString() ?: ""}----")
            if (isDiffInvoker) {
                joinPoint.proceed()
            }
        }
    }

    /**
     * 获取参数中的view
     */
    private fun getViewFromArgs(args: Array<Any>?): View? {
        if (args.isNullOrEmpty()) {
            return null
        }
        for (e in args) {
            if (e is View) {
                return e
            }
        }
        return null
    }

    /**
     * 判断是否达到可以点击的时间间隔
     * @param lastClickTime
     */
    private fun canClick(lastClickTime: Long): Boolean {
        Log.d(TAG, "currentTimeMillis----${lastClickTime}----${System.currentTimeMillis() - lastClickTime}----")
        return System.currentTimeMillis() - lastClickTime >= MIN_CLICK_DELAY_TIME
    }
}

项目地址: github.com/xiaoDongBei…

AOP应用场景

按钮防抖
登陆判断
日志打印
性能监控
权限获取
网络判断
数据校验
修复第三方代码,比如第三方jar包
hook 某些代码为封装过的安全性更好的代码, 比如hook系统toast或者Gson等等