AOP面向切面编程在后端应用非常广泛,有效的进行了解耦,将分散的代码集中起来,对原有业务逻辑侵入较低。本文基于AspectJ实现Android应用场景下的AOP。
什么是AOP
什么是AOP?回答这个问题之前,回顾一下OOP(面向对象),我们在面向对象的编程过程中,功能模块化,每个模块处理自己的业务。但是有一个问题,就是当业务逻辑变得越来越复杂之后,各个模块相互调用,某些模块被多处反复调用,代码分散不易维护。对原有业务逻辑入侵比较大,牵一发而动全身。AOP(面向切面编程)就是为了解决这个问题,把某些功能统一管理起来,作为一个切面,需要使用的地方作为一个切入点,将功能代码在编译时织入或者运行时进行动态代理。
AOP的应用场景
AOP在Android的实际应用其实比较少,因为Android应用功能入口比较统一,后端各个业务模块之间比较分散,一些需要多处调用的模块(如:登录、日志收集等)适合AOP进行统一管理。回归正题,Android的AOP场景有:检测耗时、权限处理、埋点、登录、触摸限制等。
AspectJ简单使用
AOP是方法论,具体的实现需要借助一下框架。常用的Android AOP框架有: APT, AspectJ, Javassist, ASM。AspectJ是按照AOP的思想为java设计的具备强大AOP功能的框架,学习AspectJ有助于我们对AOP的理解。
APT: 注解处理器
Javassist: 字节码处理框架
ASM: 字节码处理框架
AspectJ: AOP框架
结合注解我们可以更加灵活的使用AspectJ,以下是AspectJ提供的部分注解,简化我们使用AspectJ的成本。
@Aspect: 用于注解类,定义一个切面类
@Pointcut: 用于注解方法,定义一个切入点
@Before: 用于注解方法,织入代码的执行点,表示在JPoint执行之前
@After: 用于注解方法,织入代码的执行点,表示JPoint执行完之后
@Around: 用于注解方法,织入代码的执行点,表示替代JPonit执行(执行原方法需调用proceed)
在这里我们主要使用AspectJ注解来实现简单的例子。
大材小用之耗时检查
//定义注解
@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
annotation class ExecTime(val value: String)
//定义切面
@Aspect
class ExecTimeAspect {
@Pointcut("execution(@com.cvte.tv.aopdemo.annotation.ExecTime * *(..))")
fun execTimePointcut() {
}
@Around("execTimePointcut()")
@Throws(Throwable::class)
fun execTimePointcut(joinPoint: ProceedingJoinPoint) {
val methodSignature = joinPoint.signature as MethodSignature
val className = methodSignature.declaringType.simpleName
val methodName = methodSignature.name
val behaviorTrace = methodSignature.method.getAnnotation(ExecTime::class.java)!!
val value = behaviorTrace.value
val start = System.currentTimeMillis()
joinPoint.proceed()
val duration = System.currentTimeMillis() - start
Log.e(value, "${className}类中${methodName}方法耗时:${duration}ms")
}
}
使用的时候就只需要在相关方法加上@ExecTime("TAG")
权限处理
同理,我们可以定义权限检测和权限请求的注解和切面,在切面类execTimePointcut处进行权限处理。要注意一个点,切面类不应该持有Context(虽然我们可以通过joinPoint去获取当前的context),与Context相关的操作我们应该委托给一个公共的管理处理。在权限处理这个例子中,切面类不应该有太多权限请求的相关逻辑,它仅仅是一个切入点,具体的实现应该交给统一的权限管理器处理。
埋点项目实践
未完待续......