Android中AOP的实际运用
Android中AOP的实际运用
一、AOP简介
AOP即面向切面编程,区别于OOP(面向对象编程)的功能模块化,AOP主要侧重于解决某一类的问题。曾经我也不知道面向切面到底是切的谁,直到我看到下面这个图才理解。
从上图可以看出,面向切面编程是在不影响原业务功能的情况下,将我们所需要的功能插入进原业务代码中。
通俗的讲,比如在我们的android商城应用中,点击按钮跳转到功能界面时,很多地方都判断用户是否已登录,若用户已登录则跳转到功能界面,一般,我们的代码会这么写:
public void goToFun() { if(LoginUtil.isLogin()) { startActivity(new Intent(MainActivity.this, FunctonActivity.class)); } else { startActivity(new Intent(MainActivity.this, LoginActivity.class)); }}
而在AOP思想中,代码会写成这样:
@CheckLoginpublic void goToFun() { startActivity(new Intent(MainActivity.this, FunctonActivity.class));}
乍一看,你可能会觉得代码并没有精简多少,但是若判断场景更加复杂、判断场景更多的时候,这样的写法很显然会非常冗余,并影响阅读体验。AOP的精髓就在于,不影响原代码业务逻辑的情况下,通过编译时注入代码的方式,通用的插入新的代码逻辑,效率高,侵入性低。
二、应用场景
AOP思想是用来解决一系列相同问题的方案,它可以运用的场景很多,比如:日志打印,性能监测,埋点方案等。
三、AOP实现方法
AOP是一种编程思想,实现它的框架有很多,其中最有名的是AspectJ,本文也是用AspectJ框架实现的。
1.AspectJ简介
拥有自己的编译器和语法,可以在编译期间将需要植入的代码编译成Java代码插入到你的源代码文件当中,它是一种几乎和Java完全一样的语言,而且完全兼容Java(AspectJ应该就是一种扩展Java,但它不是像Groovy那样的拓展)。
基础概念
在学习AspectJ之前,要先简单的了解一下几个概念,这个稍微了解一下就好了,不懂也没关系,看后面的示例就好了。
-
Advice(通知): 注入到class文件中的代码。典型的 Advice 类型有 before、after 和 around,分别表示在目标方法执行之前、执行后和完全替代目标方法执行的代码。 除了在方法中注入代码,也可能会对代码做其他修改,比如在一个class中增加字段或者接口。
-
Joint point(连接点): 程序中可能作为代码注入目标的特定的点,例如一个方法调用或者方法入口。
-
Pointcut(切入点): 告诉代码注入工具,在何处注入一段特定代码的表达式。例如,在哪些 joint points 应用一个特定的 Advice。切入点可以选择唯一一个,比如执行某一个方法,也可以有多个选择,比如,标记了一个定义成@DebguTrace 的自定义注解的所有方法。
-
Aspect(切面):Pointcut 和 Advice 的组合看做切面。例如,我们在应用中通过定义一个 pointcut 和给定恰当的advice,添加一个日志切面。
-
Weaving(织入): 注入代码(advices)到目标位置(joint points)的过程。
切入点表达式规则
切入点表达式帮助我们定位需要在哪些类和方法上面进行切面,可以指定某一个类,或者某个包下的某一些类,只要满足表达式规则的类,均会被切到。
表达式一般有两种写法,第一种表达式:
execution (public * com.sample.service.impl..*. *(..))
其中:
execution()
:表达式主体,必须要写,表达式的条件就从这个主体中判断;public
:第一个参数表示作用的方法可见级别,可以省略;- 第一个
*
:表示作用方法的返回值类型,*
表示所有返回值类型; com.sample.service.impl
:表示作用的包的路径;..*
:表示包下的所有子类及子孙类;*(..)
:其中*
表示方法名,(..)
表示任意参数。
因此,第一种表达式表达的意思是:切入点为com.sample.service.impl
包下的所有类及子类中的所有public
方法。
第二种表达式:
execution(@com.zw.kotlindemo.aop.CheckCostTime * *(..))
相比第一种写法,多了一个@com.zw.kotlindemo.aop.CheckCostTime
,后面的* *(..))
和第一种是一样的,多出的部分表示的是切入点的方法必须有此注解标示才能匹配。
2.示例
下面,我们就通过AspectJ来实现一个AOP案例,主要实现功能为,监测Activity的每一个生命周期的调用时间。
2.1 导入依赖
在模块(app)的build.gradle
文件中,加入如下代码:
import org.aspectj.bridge.IMessageimport org.aspectj.bridge.MessageHandlerimport org.aspectj.tools.ajc.Mainbuildscript { repositories { mavenCentral() } dependencies { classpath 'org.aspectj:aspectjtools:1.8.9' classpath 'org.aspectj:aspectjweaver:1.8.9' }}repositories { mavenCentral()}final def log = project.loggerfinal def variants = project.android.applicationVariantsvariants.all { variant -> if (!variant.buildType.isDebuggable()) { log.debug("Skipping non-debuggable build type '${variant.buildType.name}'.") return; } JavaCompile javaCompile = variant.javaCompile javaCompile.doLast { String[] args = ["-showWeaveInfo", "-1.8", "-inpath", javaCompile.destinationDir.toString(), "-aspectpath", javaCompile.classpath.asPath, "-d", javaCompile.destinationDir.toString(), "-classpath", javaCompile.classpath.asPath, "-bootclasspath", project.android.bootClasspath.join(File.pathSeparator)] log.debug "ajc args: " + Arrays.toString(args) MessageHandler handler = new MessageHandler(true); new Main().run(args, handler); 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 break; case IMessage.WARNING: log.warn message.message, message.thrown break; case IMessage.INFO: log.info message.message, message.thrown break; case IMessage.DEBUG: log.debug message.message, message.thrown break; } } }}
并且,在模块的build.gradle
文件中,添加依赖库:
implementation 'org.aspectj:aspectjrt:1.8.9'
2.2 新建注解,表示和我们这次功能相关
@Target(ElementType.METHOD) // 表示作用在方法上@Retention(RetentionPolicy.RUNTIME) // 表示在代码运行时也生效public @interface CheckCostTime { String value() default "unknown";}
2.3 新建一个Aspect类。
@Aspectpublic class CheckCostTimeAspect { }
2.4 在Aspect类中,声明切入点
@Pointcut("execution(@com.zw.kotlindemo.aop.CheckCostTime * *(..))")public void executionCostTime() { }
表示从CheckCostTime
这个接口切入,其中,* *
表示可以为任意包名,(..)
表示为任意参数。
2.5 在Aspect类中,声明通知
由于我们要监测方法的执行时间,所以必须在方法开始和方法结束都需要记录时间,因此,我们选用@Around
注解。
@Around("executionCostTime()")public Object checkCostTime(ProceedingJoinPoint joinPoint) throws Throwable { // 通过反射获取是否有CheckCostTime注解 MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature(); CheckCostTime checkCostTime = methodSignature.getMethod().getAnnotation(CheckCostTime.class); if(checkCostTime != null) { // 被插入的方法执行前 String funName = checkCostTime.value(); long startTime = System.currentTimeMillis(); // 被插入的方法执行时 Object obj = joinPoint.proceed(); // 被插入的方法执行后 long endTime = System.currentTimeMillis(); Log.e("ceshi","function " + funName + " cost: " + (endTime - startTime) + "ms"); return obj; } return joinPoint.proceed();}
Aspect类完整代码
package com.zw.kotlindemo.aop;import android.util.Log;import org.aspectj.lang.ProceedingJoinPoint;import org.aspectj.lang.annotation.Around;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Pointcut;import org.aspectj.lang.reflect.MethodSignature;@Aspectpublic class CheckCostTimeAspect { @Pointcut("execution(@com.zw.kotlindemo.aop.CheckCostTime * *(..))") public void executionCostTime() { } @Around("executionCostTime()") public Object checkCostTime(ProceedingJoinPoint joinPoint) throws Throwable { // 通过反射获取是否有CheckCostTime注解 MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature(); CheckCostTime checkCostTime = methodSignature.getMethod().getAnnotation(CheckCostTime.class); if(checkCostTime != null) { // 被插入的方法执行前 String funName = checkCostTime.value(); long startTime = System.currentTimeMillis(); // 被插入的方法执行时 Object obj = joinPoint.proceed(); // 被插入的方法执行后 long endTime = System.currentTimeMillis(); Log.e("ceshi","function " + funName + " cost: " + (endTime - startTime) + "ms"); return obj; } return joinPoint.proceed(); }}
2.6 在Activity中运用
public class FourActivity extends AppCompatActivity { @CheckCostTime("onCreate") @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_four); } @CheckCostTime("onStart") @Override protected void onStart() { super.onStart(); } @CheckCostTime("onResume") @Override protected void onResume() { super.onResume(); } @CheckCostTime("onStop") @Override protected void onStop() { super.onStop(); } @CheckCostTime("onPause") @Override protected void onPause() { super.onPause(); } @CheckCostTime("onDestroy") @Override protected void onDestroy() { super.onDestroy(); }}
2.7 打印结果
2019-07-05 15:55:40.271 16583-16583/com.zw.kotlindemo E/ceshi: function onCreate cost: 31ms2019-07-05 15:55:40.275 16583-16583/com.zw.kotlindemo E/ceshi: function onStart cost: 0ms2019-07-05 15:55:40.277 16583-16583/com.zw.kotlindemo E/ceshi: function onResume cost: 0ms2019-07-05 15:55:44.743 16583-16583/com.zw.kotlindemo E/ceshi: function onPause cost: 0ms2019-07-05 15:55:45.063 16583-16583/com.zw.kotlindemo E/ceshi: function onStop cost: 0ms2019-07-05 15:55:45.065 16583-16583/com.zw.kotlindemo E/ceshi: function onDestroy cost: 1ms
总结
AOP的优点:
-
侵入性低:在程序编译时注入代码,不影响原始业务代码;
-
通用性强:专注解决一系列相同问题,减少代码冗余度;
-
耦合度低:修改自身业务逻辑不影响原代码的执行。
AOP的缺点:
- 性能问题:由于AOP的实现原理还是通过反射,对代码的执行效率可能会产生影响。
更多相关文章
- android下创建文件夹和修改其权限的方法
- Eclipse与Android源码中ProGuard工具的使用(代码混淆)
- Anroid-vlc开源播放器代码编译及简单调用手把手
- Android X86强制竖屏怎么办?安卓(Android)x86屏幕旋转成横屏解决
- 浅谈android代码保护技术_ 加固
- android客户端和网站数据交互的实现(基于Http协议获取数据方法)
- 《第一行代码Android》学习总结第七章 运行时权限
- Andoid自动判断输入是电话,网址或者Email的方法----Linkify的应
- 用Go语言写Android应用 (2) - 从Android的Java调用Go代码