在Android中使用AspectJ进行切面编程的简易步骤

最近有做用户行为统计的需求,为了尽可能使统计代码不侵入业务代码,就研究了下hook和Aop。
之前写的hook方面的文章里,有评论给出了些建议,于是研究了下AspectJ,虽然还是不能完美解决项目中的问题,不过确实是个好东西。
实践了一把,这里简单记录一下。


先来一堆参考链接
【翻译】Android中的AOP编程
Android之AOP
Android Studio 中自定义 Gradle 插件
看AspectJ在Android中的强势插入
jarryleo / MagicBuriedPoint


言归正传

1.新建一个Library的module

新建一个module,类型选Library。
比如这里的TrackPoint

20180930103904.png

切面代码的定义基本就写在这里。

2.添加依赖

我们这种凡夫俗子要做点事只能抱一下大神的大腿,我们这里用一下别人写好的SDK:https://github.com/HujiangTechnology/gradle_plugin_android_aspectjx

2.1.根目录的build.gradle里
buildscript {
    ...
    dependencies {
        ...
        classpath 'com.hujiang.aspectjx:gradle-android-plugin-aspectjx:2.0.0'
    }
}
2.2.app项目的build.gradle及新建的module的build.gradle里都应用插件
apply plugin: 'android-aspectjx'
2.3.在app的build.gradle里面添加刚才新建的那个库
dependencies {
    ...
    implementation project(':TrackPoint')
}

3.定义切入点

Library里定义文件主要分三个文件TrackPointCallBack,TrackPoint,TrackPointAspect

3.1.先定义接口以供调用

TrackPointCallBack和TrackPoint两个文件就是接口和调用方法定义,简单起见,这里仅以点击和页面的打开关闭为例:

package net.codepig.trackpoint;

public class TrackPoint {

    private static TrackPointCallBack trackPointCallBack;

    private TrackPoint() {
    }

    public static void init(TrackPointCallBack callBack) {
        trackPointCallBack = callBack;
    }

    static void onClick(String pageClassName, String viewIdName) {
        if (trackPointCallBack == null) {
            return;
        }
        trackPointCallBack.onClick(pageClassName, viewIdName);
    }

    static void onPageOpen(String pageClassName) {
        if (trackPointCallBack == null) {
            return;
        }
        trackPointCallBack.onPageOpen(pageClassName);
    }

    static void onPageClose(String pageClassName) {
        if (trackPointCallBack == null) {
            return;
        }
        trackPointCallBack.onPageClose(pageClassName);
    }
}
package net.codepig.trackpoint;

public interface TrackPointCallBack {

    void onClick(String pageClassName, String viewIdName);

    void onPageOpen(String pageClassName);

    void onPageClose(String pageClassName);
}
3.2.Aspect的定义

这里是重点,就靠这个在不涉及业务代码的情况下,在需要的事件前后插入新的行为。
先看代码:

package net.codepig.trackpoint;

import android.view.View;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;

@Aspect
public class TrackPointAspect {
    @Pointcut("execution(* onClick(..))")
    public void methodPointcut() {
    }

    @Pointcut("execution(* android.app.Activity+.onCreate(..))")
    public void activityOnCreatePointcut() {
    }

    @Pointcut("execution(* android.app.Activity+.onDestroy(..))")
    public void activityOnDestroyPointcut() {
    }

    @Pointcut("execution(* android.app.Fragment+.onCreate(..))")
    public void fragmentOnCreatePointcut() {
    }

    @Pointcut("execution(* android.support.v4.app.Fragment+.onCreate(..))")
    public void fragmentV4OnCreatePointcut() {
    }

    @Pointcut("execution(* android.app.Fragment+.onDestroy(..))")
    public void fragmentOnDestroyPointcut() {
    }

    @Pointcut("execution(* android.support.v4.app.Fragment+.onDestroy(..))")
    public void fragmentV4OnDestroyPointcut() {
    }

    @Around("onClickPointcut()")
    public void aroundJoinClickPoint(final ProceedingJoinPoint joinPoint) throws Throwable {
        Object target = joinPoint.getTarget();
        String className = "";
        if (target != null) {
            className = target.getClass().getName();
        }
        //获取点击事件view对象及名称,可以对不同按钮的点击事件进行统计
        Object[] args = joinPoint.getArgs();
        if (args.length >= 1 && args[0] instanceof View) {
            View view = (View) args[0];
            int id = view.getId();
            String entryName = view.getResources().getResourceEntryName(id);
            TrackPoint.onClick(className, entryName);
        }
        joinPoint.proceed();//执行原来的代码
    }

    @Around("activityOnCreatePointcut() || fragmentOnCreatePointcut() || fragmentV4OnCreatePointcut()")
    public void aroundJoinPageOpenPoint(final ProceedingJoinPoint joinPoint) throws Throwable {
        Object target = joinPoint.getTarget();
        String className = target.getClass().getName();
        TrackPoint.onPageOpen(className);
        joinPoint.proceed();
    }

    @Around("activityOnDestroyPointcut() || fragmentOnDestroyPointcut() || fragmentV4OnDestroyPointcut()")
    public void aroundJoinPageClosePoint(final ProceedingJoinPoint joinPoint) throws Throwable {
        Object target = joinPoint.getTarget();
        String className = target.getClass().getName();
        TrackPoint.onPageClose(className);
        joinPoint.proceed();
    }
}

具体的语法可以参考前面列出的参考教程,这里只是简单的做一下说明:

3.2.1@Pointcut语法

设置需要切入的方法。其中参数中第一个*表示返回值使用任意类型。方法中的(..)也表示使用任意类型。
需要注意的是这里的Fragment相关需要加上android.support类的支持。

3.2.2@Around@Before@After语法

定义具体插入的代码,比如在相关的事件上插入需要的统计代码。
这里可以使用『&&、||、!』来组合不同的Pointcut定义。比如@Around("activityOnDestroyPointcut() || fragmentOnDestroyPointcut() || fragmentV4OnDestroyPointcut()")就是组合了三种关闭事件。

4.初始化

初始化可以在Application中进行。(没有的话就建一个。)
然后在onCreate方法中进行初始化

public class MainApp extends Application {
    private static final String TAG = "LOGCAT";

    @Override
    public void onCreate() {
        super.onCreate();
        TrackPoint.init(new TrackPointCallBack() {
            @Override
            public void onClick(String pageClassName, String viewIdName) {
                Log.d(TAG, "onClick: " + pageClassName + "-" + viewIdName);
                //添加你的操作
            }

            @Override
            public void onPageOpen(String pageClassName) {
                Log.d(TAG, "onPageOpen: " + pageClassName);
                //添加你的操作
            }

            @Override
            public void onPageClose(String pageClassName) {
                Log.d(TAG, "onPageClose: " + pageClassName);
                //添加你的操作
            }
        });
    }
}

收工!
当然值得操作的事件肯定不止上面这三种,以后慢慢添加吧。


相关github项目地址:https://github.com/codeqian/aspectJDemo

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,448评论 25 707
  • 用两张图告诉你,为什么你的 App 会卡顿? - Android - 掘金 Cover 有什么料? 从这篇文章中你...
    hw1212阅读 12,691评论 2 59
  • afinalAfinal是一个android的ioc,orm框架 https://github.com/yangf...
    passiontim阅读 15,396评论 2 45
  • 结束美好的北漂 离开这里好一阵子,发生了好多事。 七八月变革很多,值得铭记,比如离开一个可爱的城市——北京。 一个...
    4plus阅读 175评论 0 0
  • 不知为何伴随年龄增长,内心愈发的沧桑的一如泛起了褶皱的旧皮古书。 小时候我就是大人口中的惹祸精,可我更愿意称自己为...
    MagicCare阅读 347评论 0 3