Android 中的 AOP ——实践篇

Android · brothergang · 于 发布 · 最后由 brothergang回复 · 742 次阅读
4255 1489714372

面向切面编程

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

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

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

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

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

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

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 相关的代码如下:

@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 的增加依赖:

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

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

compile 'org.aspectj:aspectjrt:1.8.9'

同时加入AspectJX插件:

apply plugin: 'com.jakewharton.hugo'

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

运行结果

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

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

个人主页:http://brothergang.me

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

共收到 1 条回复
4255 1489714372

.

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册