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

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


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


言归正传

1.新建一个Library的module

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

在Android中使用AspectJ进行切面编程的简易步骤_第1张图片

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

你可能感兴趣的:(在Android中使用AspectJ进行切面编程的简易步骤)