最近有做用户行为统计的需求,为了尽可能使统计代码不侵入业务代码,就研究了下hook和Aop。
之前写的hook方面的文章里,有评论给出了些建议,于是研究了下AspectJ,虽然还是不能完美解决项目中的问题,不过确实是个好东西。
实践了一把,这里简单记录一下。
先来一堆参考链接
【翻译】Android中的AOP编程
Android之AOP
Android Studio 中自定义 Gradle 插件
看AspectJ在Android中的强势插入
jarryleo / MagicBuriedPoint
言归正传
1.新建一个Library的module
新建一个module,类型选Library。
比如这里的TrackPoint
切面代码的定义基本就写在这里。
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