Android中AOP的实际运用

一、AOP简介

AOP即面向切面编程,区别于OOP(面向对象编程)的功能模块化,AOP主要侧重于解决某一类的问题。曾经我也不知道面向切面到底是切的谁,直到我看到下面这个图才理解。
Android中AOP的实际运用_第1张图片

从上图可以看出,面向切面编程是在不影响原业务功能的情况下,将我们所需要的功能插入进原业务代码中。

通俗的讲,比如在我们的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之前,要先简单的了解一下几个概念,这个稍微了解一下就好了,不懂也没关系,看后面的示例就好了。

Android中AOP的实际运用_第2张图片

  • 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的实现原理还是通过反射,对代码的执行效率可能会产生影响。

更多相关文章

  1. android下创建文件夹和修改其权限的方法
  2. Eclipse与Android源码中ProGuard工具的使用(代码混淆)
  3. Anroid-vlc开源播放器代码编译及简单调用手把手
  4. Android X86强制竖屏怎么办?安卓(Android)x86屏幕旋转成横屏解决
  5. 浅谈android代码保护技术_ 加固
  6. android客户端和网站数据交互的实现(基于Http协议获取数据方法)
  7. 《第一行代码Android》学习总结第七章 运行时权限
  8. Andoid自动判断输入是电话,网址或者Email的方法----Linkify的应
  9. 用Go语言写Android应用 (2) - 从Android的Java调用Go代码

随机推荐

  1. android 开发 @override 编译错误 解决办
  2. android 基于百度地图api获取经纬度
  3. Android(安卓)xxx is not translated in
  4. android 锁屏时,不运行锁屏程序
  5. android创建文件夹
  6. 解决Android(安卓)Studio Fetching Andro
  7. Android(安卓)菜单(OptionMenu)大全
  8. 一个APK反编译利器Apktool(android汉化)
  9. 安卓报错:AS Error inflating class andr
  10. Android(安卓)LayoutEditor使Eclipse自动