AOP 系列一

面向切面编程

阅读之前先来看几个关于埋点的问题,看看这篇文章对你是不是有用?

  • 每个页面的进入和退出需要埋点统计;
  • 很多的按钮点击事件需要统计上报;
  • 一些函数的性能数据需要统计;

好了,如果你遇到了这些问题,那么告诉你,AOP 可以解决你的问题。

最早接触 AOP,是和滴滴的郑老师聊的过程中,郑老师提到的。
之前遇到了一些埋点方面的问题,有一些不是很完美的方案,所以请教了一下滴滴的郑老师,提供了一些解决思路。

有很多人,都是奔着解决方案去的。给我「鱼」就行,至于怎么「渔」,以后再说。从方法论上来说,这样当然是不对的,但是从实际角度出发,这又是最简单高效的一种方式。所以这次我也是直接上「鱼」,后面再来谈如何「渔」。

举个稍微特殊点的例子,先看看一般的打点方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
public class TestActivity extends AppCompatActivity {
private static final String TAG = TestActivity.class.getSimpleName();

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
}

@Override
protected void onStart() {
super.onStart();
debugLog(" === onStart ===");
}

@Override
protected void onRestart() {
super.onRestart();
debugLog(" === onRestart ===");
}

@Override
protected void onResume() {
super.onResume();
debugLog(" === onResume ===");
}

@Override
protected void onPause() {
super.onPause();
debugLog(" === onPause ===");
}

@Override
protected void onStop() {
super.onStop();
debugLog(" === onStop ===");
}

@Override
protected void onDestroy() {
super.onDestroy();
debugLog(" === onDestroy ===");
}

private void debugLog(String loginfo){
LogUtils.i(this.getClass(), loginfo);
}
}

在这里,如果我们需要统计 Activity 的生命周期(作为 APP 开发,或多或少的都打过类似的日志吧),一般都会在 Activity 关键的几个生命周期函数里打上日志,然后来看看整个 Activity 的生命周期是怎么样的。如果是只是统计一个 Activity 还好,如果是统计多个呢?难道每个 Activity 都写上一遍吗?

当然不是,程序员的其中一个使命就是能机器做的决不自己动手,所以我们让 AOP 来帮我们解决。

具体的 AOP 相关的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
@Aspect   //必须使用@AspectJ标注,这样class DemoAspect就等同于 aspect DemoAspect了
public class DemoAspect {
static final String TAG = "DemoAspect";

/*
@Pointcut:pointcut也变成了一个注解,这个注解是针对一个函数的,比如此处的logForActivity()
其实它代表了这个pointcut的名字。如果是带参数的pointcut,则把参数类型和名字放到
代表pointcut名字的logForActivity中,然后在@Pointcut注解中使用参数名。
基本和以前一样,只是写起来比较奇特一点。后面我们会介绍带参数的例子
*/
@Pointcut("execution(* com.brothergang.demo.aop.TestActivity.onCreate(..)) ||"
+ "execution(* com.brothergang.demo.aop.TestActivity.onStart(..)) ||"
+ "execution(* com.brothergang.demo.aop.TestActivity.onResume(..)) ||"
+ "execution(* com.brothergang.demo.aop.TestActivity.onDestroy(..)) ||"
+ "execution(* com.brothergang.demo.aop.TestActivity.onPause(..))"
)
public void logForActivity() {
}

; //注意,这个函数必须要有实现,否则Java编译器会报错

/*
@Before:这就是Before的advice,对于after,after -returning,和after-throwing。对于的注解格式为
@After,@AfterReturning,@AfterThrowing。Before后面跟的是pointcut名字,然后其代码块由一个函数来实现。
比如此处的log。
*/
@Before("logForActivity()")
public void log(JoinPoint joinPoint) {
//对于使用Annotation的AspectJ而言,JoinPoint就不能直接在代码里得到多了,而需要通过
//参数传递进来。
Log.e(TAG, "AOP 埋点:" + joinPoint.toShortString());
}

@Pointcut("execution(* android.view.View.OnClickListener.onClick(..))"
)
public void clickEvent() {
}

@Before("clickEvent()")
public void logClickEvent(JoinPoint joinPoint) {
//对于使用Annotation的AspectJ而言,JoinPoint就不能直接在代码里得到多了,而需要通过
//参数传递进来。
Log.e(TAG, "AOP 埋点:" + joinPoint.toShortString());
}
}

当然,光这样是运行不起来的,首先,需要在项目根目录的build.gradle中 dependencies 的增加依赖:

1
classpath 'com.jakewharton.hugo:hugo-plugin:1.2.1'

然后再主项目或者库的build.gradle中增加AspectJ的依赖:

1
compile 'org.aspectj:aspectjrt:1.8.9'

同时加入AspectJX插件:

1
apply plugin: 'com.jakewharton.hugo'

然后 Sync Now,好了,可以跑起来了。自己看效果吧。可以看到白色部分是代码中的埋点,红色部分是通过 AOP 的方式埋的点。

运行结果图

好了,实践完了,后面就要深入概念理解了。

源码在这里,觉得好的话顺手给个 Star 吧

关注微信公众号「扯淡笔记」,看我扯淡!
扯淡笔记