实操AspectJ实现Android埋点以及问题汇总

Posted 隔壁小王66

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了实操AspectJ实现Android埋点以及问题汇总相关的知识,希望对你有一定的参考价值。

公司为了安全考虑,决定自己做埋点统计,之前做了一版,查询了很多资料,大多数都是在baseActivity监听声明周期以及拦截触摸事件,第一版任务急,也就采用这种方式,配合手动埋点,算是完成第一版的埋点组件。

但是这种有很多问题,比如弹窗,popwindow,以及fragment无法监听,所以,私下查询资料,学习了AspectJ,动手完成了第二版埋点组件,自测兼容所有的点击事件。

AspectJ埋点方案

1:在root build.gradle中添加

  classpath 'com.hujiang.aspectjx:gradle-android-plugin-aspectjx:2.0.8'

2:新建module,在build.gradle中添加

apply plugin: 'android-aspectjx'

3:在app的build.gradle中添加

apply plugin: 'android-aspectjx'

构建一下就具备了调用AspectJ的基础

例如

/**
 * author : wangchang
 * date   : 2019-11-29 10:55
 * desc   : aop实现监听方法
 */
@Aspect
public class AspectJController 

    /**
     * 第一个*所在的位置表示的是返回值,*表示的是任意的返回值,.. 所在位置是方法参数的位置,.. 表示的是任意类型、任意个数的参数
     * Pointcut 切入点,告诉代码注入工具,在何处注入一段特定代码的表达式。
     */
    @Pointcut("execution(* onClick(..))")
    public void onClick() 
    

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

    @Around("execution(* onResume()) && within(android.support.v4.app.Fragment)")
    public void onFragmentResume(final ProceedingJoinPoint joinPoint) throws Throwable 
        Object target = joinPoint.getTarget();
        String className = target.getClass().getName();
        AspectJManager.onFragmentOpen(className);
        joinPoint.proceed();
    

    @Around("execution(* onPause()) && within(android.support.v4.app.Fragment)")
    public void onFragmentPause(final ProceedingJoinPoint joinPoint) throws Throwable 
        Object target = joinPoint.getTarget();
        String className = target.getClass().getName();
        AspectJManager.onFragmentClose(className);
        joinPoint.proceed();
    

    @Around("execution(* onHiddenChanged(..))&& within(android.support.v4.app.Fragment)")
    public void onHiddenChanged(final ProceedingJoinPoint joinPoint) throws Throwable 
        //访问目标方法的参数
        Object[] args = joinPoint.getArgs();
        boolean hidden = false;
        if (args != null && args.length > 0 && args[0].getClass() == Boolean.class) 
            hidden = (boolean) args[0];
        
        Object target = joinPoint.getTarget();
        String className = target.getClass().getName();
        if (hidden) 
            AspectJManager.onFragmentClose(className);
         else 
            AspectJManager.onFragmentOpen(className);
        
        joinPoint.proceed();
    

    @Around("execution(* setUserVisibleHint(..))&& within(android.support.v4.app.Fragment)")
    public void setUserVisibleHint(final ProceedingJoinPoint joinPoint) throws Throwable 
        Object[] args = joinPoint.getArgs();
        boolean hidden = false;
        if (args != null && args.length > 0 && args[0].getClass() == Boolean.class) 
            hidden = (boolean) args[0];
        
        Object target = joinPoint.getTarget();
        String className = target.getClass().getName();
        if (hidden) 
            AspectJManager.onFragmentOpen(className);
         else 
            AspectJManager.onFragmentClose(className);
        
        joinPoint.proceed();
    

    @Pointcut("execution(* onResume()) && within(com.yunshuxie.bearword.base.BaseActivityM)")
    public void openActivity() 

    

    @Pointcut("execution(* onDestroy()) && within(com.yunshuxie.bearword.base.BaseActivityM)")
    public void closeActivity() 
    


    @Around("openActivity()")
    public void openActivityMethodAround(final ProceedingJoinPoint joinPoint) throws Throwable 
        Object target = joinPoint.getTarget();
        String className = target.getClass().getName();
        AspectJManager.onActivityOpen(className);
        joinPoint.proceed();
    

    @Around("closeActivity()")
    public void closeActivityMethodAround(final ProceedingJoinPoint joinPoint) throws Throwable 
        Object target = joinPoint.getTarget();
        String className = target.getClass().getName();
        AspectJManager.onActivityClose(className);
        joinPoint.proceed();
    




@Aspect: 注解表明这是一个AspectJ文件,编译器在编译的时候,就会自动去解析,然后将代码注入到相应的JPonit中。

这是一个主要的方法,不需要手动调用,会自动调用

通过监听

 @Pointcut("execution(* onClick(..))")

监听所有的点击事件,并在onClickMethodAround方法中执行相应操作

通过如下监听页面切换

@Pointcut("execution(* onResume()) && within(xxx.BaseActivityM)")
    public void openActivity() 

    

    @Pointcut("execution(* onDestroy()) && within(xxx.BaseActivityM)")
    public void closeActivity() 
    

通过onFragmentResume,onFragmentPause,onHiddenChanged,setUserVisibleHint来监听fragment的显示隐藏

注意的问题

1:onItemCick问题,项目中集成了BaseRecyclerViewAdapterHelper,运用了它自带的onItemCick点击事件,需要将item
layout设置根id,然后可获取到点击id
2:类名被混淆问题,在release版本测试中,发现BaseRecyclerViewAdapterHelper实现的列表点击,类名被混淆了,需要添加混淆。
3:在继承DialogFragment的弹窗中,发现类名被混淆了,需要添加相应混淆
4:获取的类名会有包含特殊符号的情况,需要过滤,例如在集成butterknife_compiler时
5:需要判断获取到的id小于0的情况,小于0的时候回去view的id名称会崩溃,需要兼容一下

具体可见github

语法参考
https://blog.csdn.net/innost/article/details/49387395

以上是关于实操AspectJ实现Android埋点以及问题汇总的主要内容,如果未能解决你的问题,请参考以下文章

Android aspectJ Aop

Android面向切面AspectJ

Android面向切面AspectJ

框架手写系列---AspectJ方式实现埋点上传框架

面向切面编程(AOP)在Android中的应用

通过Gradle Transform和aspectj来实现代码动态修改