在了解AOP直线需要先了解下注释,可以先看下我的另一篇文章
android注解的使用
AOP(Aspect Oriented Programming)是面向切面编程,AOP和我们平时接触的OOP编程是不同的编程思想,OOP是面向对象编程,提倡的是将功能模块化,对象化。而AOP的思想则是提倡针对同一类问题统一处理,当然,我们在实际编程过程中,不可能单纯的AOP或者OOP的思想来编程,很多时候,可能会混合多种编程思想。
AOP的定义
把某一方面的一些功能提取出来与一批对象进行隔离,提取之后我们就可以对某个单方面的功能进行编程。
AOP的套路
把众多方法中的所有共有代码全部抽取出来,放置到某个地方集中管理,然后在具体运行时,再由容器动态织入这些共有代码的话,最起码可以解决两个问题:
1.1 Android程序员在编写具体的业务逻辑处理方法时,只需关心核心的业务逻辑处理,既提高了工作效率,又使代码变更简洁优雅。
1.2 在日后的维护中由于业务逻辑代码与共有代码分开存放,而且共有代码是集中存放的,因此使维护工作变得简单轻松。
1.3 面向切面编程AOP技术就是为解决这个问题而诞生的,切面就是横切面,代表的是一个普遍存在的共有功能,例如,日志切面、权限切面及事务切面等。
AOP的作用
一般来说,主要用于不想侵入原有代码的场景中,例如SDK需要无侵入的在宿主中插入一些代码,做日志埋点、性能监控、动态权限控制、甚至是代码调试等等。
AOP的使用目前在android方面通常都是用eclipse的AspectJ
另外一个比较成功的使用AOP的库是Jake大神的Hugo:
Jake大神的Hugo
[aspectj下载地址](http://www.eclipse.org/downloads/download.php?file=/tools/aspectj/aspectj-1.8.10.jar)
在网上还发现一个github的库,据说竭诚了aspectj
https://github.com/HujiangTechnology/gradle_plugin_android_aspectjx
AspectJ 之 Join Points介绍
Join Points在AspectJ中是关键的概念。Join Points可以看做是程序运行时的一个执行点,比如:一个函数的调用可以看做是个Join Points,相当于代码切入点。但在AspectJ中,只有下面几种执行点是认为是Join Points:
Join Points | 说明 | 实例 |
---|---|---|
method call | 函数调用 | 比如调用Log.e(),这是一个个Join Point |
method execution | 函数执行 | 比如Log.e()的执行内部,是一处Join Points。注意这里是函数内部 |
constructor call | 构造函数调用 | 和method call 类似 |
constructor execution | 构造函数执行 | 和method execution 类似 |
field get | 获取某个变量 | 比如读取DemoActivity.debug成员 |
field set | 设置某个变量 | 比如设置DemoActivity.debug成员 |
pre-initialization | Object在构造函数中做的一些工作。 | - |
initialization | Object在构造函数中做的工作。 | - |
static initialization | 类初始化 | 比如类的static{} |
handler | 异常处理 | 比如try catch 中,对应catch内的执行 |
advice execution | 这个是AspectJ 的内容 | - |
Pointcuts 介绍
一个程序会有多个Join Points,即使同一个函数,也还分为call 和 execution 类型的Join Points,但并不是所有的Join Points 都是我们关心的,Pointcuts 就是提供一种使得开发者能够值选择所需的JoinPoints的方法。
Advice介绍
Advice就是我们插入的代码可以以何种方式插入,有Before 还有 After、Around。
下面看个例子:
/**
* 找到处理的切点
* * *(..) 可以处理所有的方法
*/
@Pointcut("execution(@com.liuy.architect_day02.CheckNet * *(..))")
public void checkNetBehavior() {
}
这里会分成好几个部分,我们依次来看:
- @Before: Advice, 也就是具体的插入点
- execution:处理Join Point的类型,例如call、execution
- (* android.app.Activity.on(..)): 这个是最重要的表达式,第一个表示返回值,表示返回值为任意类型,后面这个就是典型的包名路径,其中可以包含 *来进行通配,几个 *没有区别。同时这里可以通过&&、||、!来进行条件组合。()代表这个方法的参数,你可以指定类型,例如android.os.Bundle,或者 (..) 这样来代表任意类型、任意个数的参数。
- public void checkNetBehavior: 实际切入的代码。
Before 和 After 其实还是很好理解的,也就是在Pointcuts之前和之后,插入代码,那么Android呢,从字面含义上来讲,也就是在方法前后各插入代码,他包含了 Before和 After 的全部功能,代码如下:
@(“execution(* com.example.andorid.MainActivity.testAOP()))”)
public void onActivityMethodAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{
String key = proceedingJoinPoint.getSignature().toString();
Log.d(TAG,”onActivityMethodAroundFirst:”+key);
proceedingJoinPoint.proceed();
Log.d(TAG,”onActivityMethodAroundSecond:”+key);
}
以上代码中,proceedingJoinPoint.proceed()代表执行原始的方法,在这之前、之后,都可以进行各种逻辑处理。
自定义Pointcuts
自定义Pointcuts可以让我们更加精准的切入一个或多个指定的切入点。
首先我们要定义一个注解类
@Target(ElementType.METHOD) // Target 放在哪个位置
@Retention(RetentionPolicy.RUNTIME)// RUNTIME 运行时 xUtils CLASS 代表编译时期 ButterKnife SOURCE 代表资源
public @interface CheckNet { // @interface 注解
}
在需要插入代码的地方加入这个注解,例如在MainActivity中加入:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@CheckNet
public void click(View view) {
Intent intent = new Intent(this, MainActivity.class);
startActivity(intent);
}
@CheckNet
public void postData() {
}
}
最后创建切入代码,我这里的例子是判断网络是否可用的
@Aspect
public class SectionAspect {
/**
* 找到处理的切点
* * *(..) 可以处理所有的方法
*/
@Pointcut("execution(@com.darren.architect_day02.CheckNet * *(..))")
public void checkNetBehavior() {
}
/**
* 处理切面
*/
@Around("checkNetBehavior()")
public Object checkNet(ProceedingJoinPoint joinPoint) throws Throwable {
Log.e("TAG", "checkNet");
// 做埋点 日志上传 权限检测(我写的,RxPermission , easyPermission) 网络检测
// 网络检测
// 1.获取 CheckNet 注解 NDK 图片压缩 C++ 调用Java 方法
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
CheckNet checkNet = signature.getMethod().getAnnotation(CheckNet.class);
if (checkNet != null) {
// 2.判断有没有网络 怎么样获取 context?
Object object = joinPoint.getThis();// View Activity Fragment ; getThis() 当前切点方法所在的类
Context context = getContext(object);
if (context != null) {
if (!isNetworkAvailable(context)) {
// 3.没有网络不要往下执行
Toast.makeText(context,"请检查您的网络",Toast.LENGTH_LONG).show();
return null;
}
}
}
return joinPoint.proceed();
}
/**
* 通过对象获取上下文
*
* @param object
* @return
*/
private Context getContext(Object object) {
if (object instanceof Activity) {
return (Activity) object;
} else if (object instanceof Fragment) {
Fragment fragment = (Fragment) object;
return fragment.getActivity();
} else if (object instanceof View) {
View view = (View) object;
return view.getContext();
}
return null;
}
/**
* 检查当前网络是否可用
*
* @return
*/
private static boolean isNetworkAvailable(Context context) {
// 获取手机所有连接管理对象(包括对wi-fi,net等连接的管理)
ConnectivityManager connectivityManager = (ConnectivityManager)
context.getSystemService(Context.CONNECTIVITY_SERVICE);
if (connectivityManager != null) {
// 获取NetworkInfo对象
NetworkInfo[] networkInfo = connectivityManager.getAllNetworkInfo();
if (networkInfo != null && networkInfo.length > 0) {
for (int i = 0; i < networkInfo.length; i++) {
// 判断当前网络状态是否为连接状态
if (networkInfo[i].getState() == NetworkInfo.State.CONNECTED) {
return true;
}
}
}
}
return false;
}
}
execution语法
语法结构:execution([修饰符] 返回值类型 方法名(参数) [异常模式]) | 实例 |
---|---|
execution(public .(..)) | 所有的public方法 |
execution(* hello(..)) | 所有的hello()方法 |
execution(String hello(..)) | 所有返回值为String的hello方法。 |
execution(* hello(String)) | 所有参数为String类型的hello() |
execution(* hello(String..)) | 至少有一个参数,且第一个参数类型为String的hello方法 |
execution(* com.aspect..*(..)) | 所有com.aspect包,以及子孙包下的所有方法 |
execution(* com...Dao.find*(..)) | com包下的所有一Dao结尾的类的一find开头的方法 |
call和execution的区别
call为调用,而execution为执行。
3 public class HelloWorld {
4 public static void main(int i){
5 System.out.println("in the main method i = " + i);
6 }
7
8 public static void main(String[] args) {
9 main(5);
10 }
我们拦截了参数为:int的main方法。 这里用到了一内置的对象:thisJoinPoint,他表示当前jionPoint. 跟我们在java中的this 其实是差不多的,如果你不明白,那么你多运行一下,好好体会一下。getSourceLocation()表示源代码的位置:
public aspect HelloAspect {
pointcut HelloWorldPointCut() : call(* main(int));
before() : HelloWorldPointCut(){
System.out.println("Entering : " + thisJoinPoint.getSourceLocation());
}
}
我们运行一下HelloWorld.java。
打印结果为
Entering : HelloWorld.java:9
in the main method i = 5
9是行号
接下来我们把call 换成execution打印
public aspect HelloAspect {
pointcut HelloWorldPointCut() : execution(* main(int));
before() : HelloWorldPointCut(){
System.out.println("Entering : " + thisJoinPoint.getSourceLocation());
}
}
Entering : HelloWorld.java:4
in the main method i = 5
结果为4
从上面的结果可以看出,call是调用的地方,execution是执行的地方。
thisJoinPoint.getSourceLocation() 这段代码将会在我们以后的Demo中经常用到。这是一个跟踪调试的好办法。
within 和 withincode
within
还是上面的例子,如果别的类中也有main方法?应该怎么办?你首先想到的肯定是修改pointcut,指定到我们的HelloWorld类。 这当然是可以的,假设:现在还有5个类,也有main方法,也需要拦截。那你这解决办法肯定就不行了。(当然你也可以用 || 来组合他们)。这个时候就用到了我们的within了。代码如下
pointcut HelloWorldPointCut() : execution(* main(..)) && !within(HelloAspectDemo);
before() : HelloWorldPointCut(){
System.out.println("Entering : " + thisJoinPoint.getSourceLocation());
withincode
withincode与within相似,不过withcode()接受的signature是方法,而不是类。用法,意思都差不多,只不过是使用场合不同。
public class MainActivity extends AppCompatActivity {
final String TAG = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
register1();
register2();
register3();
}
public void registerTest() {
Log.e(TAG, "execute registerTest");
}
public void register1(){
registerTest();
}
public void register2(){
registerTest();
}
public void register3(){
registerTest();
}
}
以上三个register方法都调用了registerTest()方法,如果这个时候想调用register3的registerTest()的方法,需要如下操作
@Pointcut("(call(* *..registerTest()))&&withincode(* *..register2())")
public void invokeregisterTestInregister2() {
}
@Before("invokeregisterTestInregister2()")
public void beforeInvokeregisterTestInregister2(JoinPoint joinPoint) throws Throwable {
Log.e(TAG, "method:" + getMethodName(joinPoint).getName());
}
private MethodSignature getMethodName(JoinPoint joinPoint) {
if (joinPoint == null) return null;
return (MethodSignature) joinPoint.getSignature();
}
输出如下
execute registerTest
method:registerTest
execute registerTest
execute registerTest