登录 立即注册
安币:

安卓巴士 - 安卓开发 - Android开发 - 安卓 - 移动互联网门户

查看: 103|回复: 0

Android 中使用AOP

[复制链接]

2

主题

2

帖子

18

安币

初级码农

Rank: 1

发表于 2019-1-27 22:40:01 | 显示全部楼层 |阅读模式
如果对本篇文章感兴趣,请前往,原文地址:http://www.apkbus.com/blog-972611-79451.html

![Picture](//upload-images.jianshu.io/upload_images/1797490-3e19e45e3f2f5f45.png)## 目录![](//upload-images.jianshu.io/upload_images/1797490-9d0b57b28a6d1512.png)## 有什么用- App中很多跳转的地方都需要登入校验,无非就是```if-else```,但是如果这样的判断有很多,我们就得重复很多次,或者有一天需求变动,有可能就会更改多个地方。类似的还有网络判断,权限管理,Log日志的统一管理这样的问题,如果更优雅的实现这些功能呢?- App 调试时,如果一眼无法看出错误在哪里,有时会把一些关键信息打印出来,如何快速将方法的入参和出参都打印出来?- 如何安全地执行方法,不用考虑异常情况?- .......## 什么是AOP?在软件业,AOP为Aspect Oriented Programming的缩写,意为:[面向切面编程](https://baike.baidu.com/item/面向切面编程/6016335),通过[预编译](https://baike.baidu.com/item/预编译/3191547)方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是[OOP](https://baike.baidu.com/item/OOP)的延续,是软件开发中的一个热点,也是[Spring](https://baike.baidu.com/item/Spring)框架中的一个重要内容,是[函数式编程](https://baike.baidu.com/item/函数式编程/4035031)的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的[耦合度](https://baike.baidu.com/item/耦合度/2603938)降低,提高程序的可重用性,同时提高了开发的效率。AOP 解决 OOP 中遇到的一些问题.是 对OOP 的延续和扩展## AOP中的术语- **Joinpoint(连接点):**所谓连接点是指那些被拦截到的点。- **Pointcut(切入点):**所谓切入点是指我们要对哪些 Joinpoint 进行拦截的定义。- **Advice(通知/增强):**所谓通知是指拦截到 Joinpoint 之后所要做的事情就是通知。- **Introduction(引介):**引介是一种特殊的通知在不修改类代码的前提下, Introduction 可以在运行期为类 动态地添加一些方法或 Field。- **Target(目标对象):**代理的目标对象。- **Weaving(织入):**是指把增强应用到目标对象来创建新的代理对象的过程.   AspectJ 采用编译期织入和类装在期织入 。- **Proxy(代理):**一个类被 AOP 织入增强后,就产生一个结果代理类 。- **Aspect(切面):**是切入点和通知(引介)的结合 。### Advice分类:类型描述Before前置通知, 在目标执行之前执行通知After后置通知, 目标执行后执行通知Around环绕通知, 在目标执行中执行通知, 控制目标执行时机AfterReturning后置返回通知, 目标返回时执行通知AfterThrowing异常通知, 目标抛出异常时执行通知## Android中如何使用AOP?### AspectJ 介绍AspectJ是一个面向切面编程的框架。AspectJ是对java的扩展,而且是完全兼容java的,AspectJ定义了AOP语法,它有一个专门的编译器用来生成遵守Java字节编码规范的Class文件。AspectJ还支持原生的Java,只需要加上AspectJ提供的注解即可。在Android开发中,一般就用它提供的注解和一些简单的语法就可以实现绝大部分功能上的需求了。下面通过一个例子来说明一下AOP中各个术语的含义:```public class UserDao {     public void save(){}    public void find(){}    public void update(){}    public void delete(){}}```假设UserDao 中的四个方法均已实现,现在需要对delete()方法加入权限校验。那么我们就需要对UserDao 这个类进行增强,那么**UserDao**这个类就是**Target(目标对象)**,而该类中有四个方法,我们现在只对```delete()```方法进行改造,所以```delete()```就是**Pointcut(切入点)**;其他方法都是**Joinpoint(连接点)**;新增的权限校验方法就是**Advice(通知)**;**Introduction(引介)**是对类方面的增强;将通知应用到目标的过程就是 **Weaving(织入)**### 切入点表达式语法语法如下:**切入点指示符([访问修饰符] 方法的返回值类型 包名.类名.方法名(参数))**AspectJ类型匹配的通配符:```*:匹配任何数量字符;..:匹配任何数量字符的重复,如在类型模式中匹配任何数量子包;而在方法参数模式中匹配任何数量参数。 :匹配指定类型的子类型;仅能作为后缀放在类型模式后边。```### 切入点指示符切入点指示符用来指示切入点表达式目的,AspectJ切入点指示符如下:切入点指示符含义execution用于匹配方法执行的连接点within用于匹配指定类型内的方法执行this用于匹配当前AOP代理对象类型的执行方法;注意是AOP代理对象的类型匹配,这样就可能包括引入接口也类型匹配target用于匹配当前目标对象类型的执行方法;注意是目标对象的类型匹配,这样就不包括引入接口也类型匹配args用于匹配当前执行的方法传入的参数为指定类型的执行方法@within用于匹配所以持有指定注解类型内的方法@target用于匹配当前目标对象类型的执行方法,其中目标对象持有指定的注解@args用于匹配当前执行的方法传入的参数持有指定注解的执行@annotation用于匹配当前执行方法持有指定注解的方法AspectJ切入点支持的切入点指示符还有: call、get、set、preinitialization、staticinitialization、initialization、handler、adviceexecution、withincode、cflow、cflowbelow、if、@this、@withincode。[切入点语法详细说明](https://blog.csdn.net/fwt336/article/details/52210325)### Android 中使用Gradle集成 AspectJ在Android中集成AspectJ,主要思想就是hook Apk打包过程,使用AspectJ提供的工具来编译.class文件。自己手动接入AspectJ的话,比较繁琐。目前有一些在Android中集成AspectJ的比较火的框架,如JakeWharton的 [gradle_plugin_android_aspectjx](https://github.com/HujiangTechnology/gradle_plugin_android_aspectjx)。该框架支持kotlin。这里就使用该框架做演示,不再自己手动接入。**在项目根目录build.gradle下引入aspectjtools插件:**```buildscript {    ext.kotlin_version = '1.2.30'    repositories {        google()        jcenter()    }    dependencies {        classpath 'com.android.tools.build:gradle:3.0.1'        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"        //aspectjtools插件        classpath 'com.hujiang.aspectjx:gradle-android-plugin-aspectjx:1.1.1'        classpath 'org.aspectj:aspectjtools:1.8.9'    }}```**在module目录下的build.gradle中引入插件和依赖:(注释部分)**```apply plugin: 'com.android.application'apply plugin: 'kotlin-android'apply plugin: 'kotlin-android-extensions'//引入aspectj插件apply plugin: 'com.hujiang.android-aspectjx'android {    compileSdkVersion 28    defaultConfig {        applicationId "com.gfd.aop"        minSdkVersion 15        targetSdkVersion 28        versionCode 1        versionName "1.0"        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"    }    buildTypes {        release {            minifyEnabled false            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'        }    }}dependencies {    implementation fileTree(dir: 'libs', include: ['*.jar'])    implementation "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"    implementation 'com.android.support:appcompat-v7:28.0.0-rc02'    implementation 'com.android.support.constraint:constraint-layout:1.1.3'    testImplementation 'junit:junit:4.12'    androidTestImplementation 'com.android.support.test:runner:1.0.2'    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'    //引入aspectj的依赖    implementation 'org.aspectj:aspectjrt:1.8.9'}```至此Aspectj的环境已经搭建好了,下面通过一个登入检查的例子来说明它如果使用。### 1.创建Target(目标类)```@Retention(AnnotationRetention.RUNTIME)//存储在编译后的 Class 文件,反射可见。@Target(AnnotationTarget.FUNCTION)//方法(不包括构造函数)annotation class CheckLogin```这里创建的是一个注解,所有被该注解标识的方法都是接入点关于Target和Retention参数的说明:```AnnotationTarget参数说明:/** * AnnotationTarget.CLASS:类,接口或对象,注解类也包括在内。 * AnnotationTarget.ANNOTATION_CLASS:只有注解类。 * AnnotationTarget.TYPE_PARAMETER:Generic type parameter (unsupported yet)通用类型参数(还不支持)。 * AnnotationTarget.PROPERTY:属性。 * AnnotationTarget.FIELD:字段,包括属性的支持字段。 * AnnotationTarget.LOCAL_VARIABLE:局部变量。 * AnnotationTarget.VALUE_PARAMETER:函数或构造函数的值参数。 * AnnotationTarget.CONSTRUCTOR:仅构造函数(主函数或者第二函数)。 * AnnotationTarget.FUNCTION:方法(不包括构造函数)。 * AnnotationTarget.PROPERTY_GETTER:只有属性的 getter。 * AnnotationTarget.PROPERTY_SETTER:只有属性的 setter。 * AnnotationTarget.TYPE:类型使用。 * AnnotationTarget.EXPRESSION:任何表达式。 * AnnotationTarget.FILE:文件。 * AnnotationTarget.TYPEALIAS:@SinceKotlin("1.1") 类型别名,Kotlin1.1已可用。 */AnnotationRetention参数说明:/** * AnnotationRetention.SOURCE:不存储在编译后的 Class 文件。 * AnnotationRetention.BINARY:存储在编译后的 Class 文件,但是反射不可见。 * AnnotationRetention.RUNTIME:存储在编译后的 Class 文件,反射可见。 */```### 2.创建切面AspectJ```@Aspect//标识切面class CheckLoginAspect {    private var isLogin = false         //切入点    @Pointcut("execution(@com.gfd.aop.CheckLogin * *(..))")    fun checkLogin(){    }    @Around("checkLogin()")//环绕通知,先执行通知    @Throws(Throwable::class)//可能抛出的异常    fun aroundJoinPoint(joinPoint: ProceedingJoinPoint){        val methodSignature = joinPoint.signature as MethodSignature        val checkLogin : CheckLogin? = methodSignature.method.getAnnotation(CheckLogin::class.java)        if(checkLogin != null){            val context = joinPoint.`this` as Context            if(isLogin){//如果已经登入再去执行对应的内容                joinPoint.proceed()//执行标注的方法中的内容            }else{                Toast.makeText(context,"请先登入",Toast.LENGTH_SHORT).show()            }        }    }}```这是方便演示判断登入的方法省略直接用一个变量isLogin 来代替了,只要明白意思即可。### 3.创建测试类```class MainActivity : AppCompatActivity() {    override fun onCreate(savedInstanceState: Bundle?) {        super.onCreate(savedInstanceState)        setContentView(R.layout.activity_main)        mBtnJust.setOnClickListener {            test()        }    }    @CheckLogin    private fun test() {        Toast.makeText(this@MainActivity,"跳转成功",Toast.LENGTH_SHORT).show()    }}```test方法被```@CheckLogin```修饰,会先判断是否登入,如果登入了就会执行text方法中的代码,上面我们模拟的是没有登入```isLogin = false```,所以程序的运行结果就是提示:请先登入,而不会执行test方法中的代码。这样在需要检测登入的操作方法上添加上```@CheckLogin```即可实现登入校验的操作,当登入校验的逻辑发生改变的时候,我们也不需要改动调用的地方。当然我们可以使用通配符“*”对项目中所有的的某个方法进行增强操作。## 总结AOP是对OOP的扩展,OOP强调的是纵向的,而AOP是横向的,假如项目中有很多个删除的方法,现在都需要对删除方法加上校验的操作,一种是:定义一个基类,在基类中实现权限校验的功能,然后去集成它,这样所有用到的地方都得修改,继承就是纵向的;第二种就是利用AOP,使用代理对象,这样所有用到删除的方法是横向的,组成一个面。![](//upload-images.jianshu.io/upload_images/1797490-4087cbb146a2c8d9.png)  继续阅读全文



想在安卓巴士找到更多优质博文,可移步博客区

如果对本篇文章感兴趣,请前往,
原文地址:
http://www.apkbus.com/blog-972611-79451.html
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

站长推荐

通过邮件订阅最新安卓weekly信息
上一条 /4 下一条

下载安卓巴士客户端

全国最大的安卓开发者社区

广告投放| 广东互联网违法和不良信息举报中心|中国互联网举报中心|下载客户端|申请友链|手机版|站点统计|安卓巴士 ( 粤ICP备15117877号 )

快速回复 返回顶部 返回列表