架构之路

Android中的AOP编程

2018-01-29  本文已影响52人  若无初见

随着开发经验的累积,我们的代码质量也应该随之更加优雅、简洁。应该减少系统的重复代码,降低模块间的耦合度,并有利于未来的可操作性和可维护性。我们知道java中已经广泛使用aop编程思想,比如:java的Spring框架。

spring提供了两个核心功能,一个是IoC(控制反转),另外一个便是Aop(面向切面编程),IoC有助于应用对象之间的解耦,AOP则可以实现横切关注点(如日志、安全、缓存和事务管理)与他们所影响的对象之间的解耦。 

在搞懂怎么使用一个东西前,我觉得有必要了解下这个东西到底是什么?他有什么作用?他的内在是什么?

说到AOP也许不太了解,那么OOP一定是知道的。Object-Oriented Progreming.面向对象编程。学java基础的时候肯定都都会学面向对象思想和三大特性。这里就不详细介绍了。AOP是Aspect-Oriented Progreming的缩写,在OOP设计中有个单一职责原则,在很多时候都不会有问题,但是当很多模块都需要同一个功能的时候,这个时候还用OOP就会很麻烦。那么AOP在Android中的应用就应运而生。

如果说,OOP如果是把问题划分到单个模块的话,那么AOP就是把涉及到众多模块的某一类问题进行统一管理。

下面引用一些概念

AOP(Aspect-OrientedProgramming,面向方面编程),可以说是OOP(Object-Oriented Programing,面向对象编程)的补充和完善。OOP引入封装、继承和多态性等概念来建立一种对象层次结构,用以模拟公共行为的一个集合。当我们需要为分散的对象引入公共行为的时候,OOP则显得无能为力。也就是说,OOP允许你定义从上到下的关系,但并不适合定义从左到右的关系。例如日志功能。日志代码往往水平地散布在所有对象层次中,而与它所散布到的对象的核心功能毫无关系。对于其他类型的代码,如安全性、异常处理和透明的持续性也是如此。这种散布在各处的无关的代码被称为横切(cross-cutting)代码,在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用。

而AOP技术则恰恰相反,它利用一种称为“横切”的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其名为“Aspect”,即方面。所谓“方面”,简单地说,就是将那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可操作性和可维护性。AOP代表的是一个横向的关系,如果说“对象”是一个空心的圆柱体,其中封装的是对象的属性和行为;那么面向方面编程的方法,就仿佛一把利刃,将这些空心圆柱体剖开,以获得其内部的消息。而剖开的切面,也就是所谓的“方面”了。然后它又以巧夺天功的妙手将这些剖开的切面复原,不留痕迹。

使用“横切”技术,AOP把软件系统分为两个部分:核心关注点和横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处都基本相似。比如权限认证、日志、事务处理。Aop 的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来。正如Avanade公司的高级方案构架师Adam Magee所说,AOP的核心思想就是“将应用程序中的商业逻辑同对其提供支持的通用服务进行分离。”

实现AOP的技术,主要分为两大类:一是采用动态代理技术,利用截取消息的方式,对该消息进行装饰,以取代原有对象行为的执行;二是采用静态织入的方式,引入特定的语法创建“方面”,从而使得编译器可以在编译期间织入有关“方面”的代码。

那么今天我就来说一下AOP编程思想在Android中的使用。

原文链接 : Aspect Oriented Programming in Android

译文:【翻译】Android中的AOP编程

场景:

日志

持久化

性能监控

数据校验

缓存

当然还有一些业务逻辑也可以使用比如:日志输出、方法耗时、异常拦截、动态权限、登录验证等。

开始使用:

我们举个 性能监控中的方法耗时的例子做个简单的aop编程demo.

说到一个方法的耗时操作,最简单的方式是什么?

这里我们使用系统休眠的方法模拟耗时

好的,这是一个很简单的方式。那么问题来了。这只是一个方法,那如果我现在一个应用中的所有方法都要执行这个方法的耗时统计操作呢?

也许你会说:那就都加上呗。不就两行代码?  

好的,这是个简单的例子。那比如现在有个需求:统计用户各个模块的使用情况,前提是不能使用诸如:友盟统计、诸葛统计等第三方工具。此时我们在统计的同时还要加上上传数据到服务器的工作。

那么 此时问题就来了:重复的代码会越来越多,大大增加偶尔性,不便于维护等一系列其他不可预知的问题存在。

好的,为了解决类似以上的问题,我们引入了java中的 aop编程思想

配置

在Android Studio 配置AspectJ环境(android aop编程使用的运行环境),与简单使用。

真的不得不说下这个配置的大坑。

经过测试,我们介绍两个不同的配置方式

1:原始的配置方式

问题:AS虽然不能找到依赖包,但是实际上可以直接粘贴依赖代码之后gradle更新一下是可以的。如果一开始不知道这个坑,去网上下了一个jar包,比如:aspectj-1.8.10.jar。那么问题来了,不管你之后怎么配置都无任何作用。注入的代码没有任何作用。所以千万不要在网上随便下一个jar包,看起来是没什么错,实际上是不行的。

解决方式:就像上面提供的连接配置一样,在AspectJ的官方网站可以下载http://www.eclipse.org/aspectj/downloads.php

下载下来后是一个jar包,双击就可以安装。 

一路next

然后去对应安装的文件夹拷贝出 

添加到libs包下。具体的配置方式就按照给出的链接

2:引入别人封装好的插件配置

这里说明一个问题:AS3.0配置的时候会出现这个错误

那么久忽略这个配置

配置完成,我们继续往下讲

使用aop最重要的是将一系列想要执行的动作放在一个切面上,比如我们的例子,每个方法都应该放在这个切面上。

那么我们是如何完成这个操作的呢?    

我们通过注解的操作,实际上后台会通过注解反射的操作将功能点放在切面上。

说到注解,我们使用过设置布局的自定义注解。如果你不懂,你一定看过java方法中很多都有@override的注解。我们点击进去

模仿它,我们自定义一个注解:

@Target表示用到什么地方。可选值:

@Retention表示在什么时候有效。可选值:

注解写完,我们要在想要使用他的地方做注解。比如我们的mShake方法:

以上就完成了把mShake的方法放在了切面上。那么我们现在就开始完成切面的动作,也就是凡是在切面上的点我们将要执行的方法。

创建Aspectj方法

讲解:

第一个POINT_CUT表示切面长什么样。我们用一张图表示:

第二个方法

头部有三个注解表示的意义已经在注释中体现出来了,针对今天的耗时例子,我们自然而然的选择了Around的方式。注意点是如果使用了该注解,一定需要返回一个对象

我们多添加几个运行一下

经过测试,只要注解正确就可以在同一个方法或者类等等添加多个注解。比如在摇一摇的方法上添加一个登录检测的注解

以上就是对Android中的AOP编程做的一个简答例子。

关于其他用到的地方我们会针对PoinCut中的表达式做进一步研究


关于Pointcut的切入点的表达式我们一般使用的是excution(方法执行时)

切入点指示符用来指示切入点表达式目的,,在Spring AOP中目前只有执行方法这一个连接点,Spring AOP支持的AspectJ切入点指示符如下:

execution:用于匹配方法执行的连接点;

within:用于匹配指定类型内的方法执行;

this:用于匹配当前AOP代理对象类型的执行方法;注意是AOP代理对象的类型匹配,这样就可能包括引入接口也类型匹配;

target:用于匹配当前目标对象类型的执行方法;注意是目标对象的类型匹配,这样就不包括引入接口也类型匹配;

args:用于匹配当前执行的方法传入的参数为指定类型的执行方法;

@within:用于匹配所以持有指定注解类型内的方法;

@target:用于匹配当前目标对象类型的执行方法,其中目标对象持有指定的注解;

@args:用于匹配当前执行的方法传入的参数持有指定注解的执行;

@annotation:用于匹配当前执行方法持有指定注解的方法;

具体的可以参考 Spring切入点表达式常用写法

紧接着我们要说的是怎么传入参数!

有上面的介绍我们知道,比如一个应用中多处地方需要检测用户是否登录,那么这个时候用AOP编程的方式显然是再好不过了。我们知道检验登录的应该使用@before作为注解

那么问题来了:很明显上面的context是空值,需要外部传入。

那么到底要怎么传呢?

我们注意到joinPoint这个时候实际上有个很大的作用

查阅api可以得到 当前的注解类以及当前执行了什么样的功能等

为什么这两个注解都可以传入String值呢? 原因如下

注解中默认有一个value()的方法。

那么你可能会想,既然可以传值那么我是不是可以传入一个context呢?

很遗憾的告诉你:不行!查阅了java自定义注解发现

自定义注解:

  使用@interface自定义注解时,自动继承了java.lang.annotation.Annotation接口,由编译程序自动完成其他细节。在定义注解时,不能继承其他的注解或接口。@interface用来声明一个注解,其中的每一个方法实际上是声明了一个配置参数。方法的名称就是参数的名称,返回值类型就是参数的类型(返回值类型只能是基本类型、Class、String、enum)。可以通过default来声明参数的默认值。

定义注解格式:

public @interface 注解名 {定义体}

  注解参数的可支持数据类型:

1.所有基本数据类型(int,float,boolean,byte,double,char,long,short)

2.String类型

3.Class类型

4.enum类型

5.Annotation类型

6.以上所有类型的数组

Annotation类型里面的参数该怎么设定: 

第一,只能用public或默认(default)这两个访问权修饰.例如,String value();这里把方法设为defaul默认类型;

第二,参数成员只能用基本类型byte,short,char,int,long,float,double,boolean八种基本数据类型和 String,Enum,Class,annotations等数据类型,以及这一些类型的数组.例如,String value();这里的参数成员就为String;

第三,如果只有一个参数成员,最好把参数名称设为"value",后加小括号

所以我们放弃了从外部传入context的方式,但是通过研究我们终于明白了怎么向注解中提供参数的方法。

其实,从一个类中传入context的方法相信很多人都做过,我们的一些自定义的第三方工具中经常会传入一个Application的context。

或者我们也许会有一个ActivitManager类专门管理没个activity,然后获取当前的activity。

以后者通过application的registerActivityLifecycleCallbacks接口管理应用中的每个activity,总之方法还是很多的。

最后附上项目地址:https://github.com/xmrkwzw/AndroidAop-master

上一篇下一篇

猜你喜欢

热点阅读