通过aspectj对Android数据统计的简单实现

功能需求

一个项目实现之后,我们并不知道用户对某个部分的使用频率是对少,为了更好的来对项目各个功能的使用统计,我们需要做一些数据埋点的功能,也就是每当用户点击按钮的时候,都对这次点击进行保存处理,然后再之后统一上传到服务器,进行数据分析。

实现思路

条件

假如,当前有两个方法进行数据埋点:登录和注册。
功能表的数据结构如下:功能id、操作次数、操作人id

public class FunctionsTable {

    //功能Id
    private long functionId;
    //操作次数
    private int operateCounts;
    //操作用户Id
    private long operatorId;

    public long getFunctionId() {
        return functionId;
    }

    public void setFunctionId(long functionId) {
        this.functionId = functionId;
    }

    public int getOperateCounts() {
        return operateCounts;
    }

    public void setOperateCounts(int operateCounts) {
        this.operateCounts = operateCounts;
    }

    public long getOperatorId() {
        return operatorId;
    }

    public void setOperatorId(long operatorId) {
        this.operatorId = operatorId;
    }
}

下载我们需要做的就是,每次用户点登陆和注册的时候,把数据库中的operateCount字段每次+1。

分析

首先,最直接的实现方法也就是封装一个保存数据的数据库静态操作方法,然后将每次需要操作的地方传入functionId,来进行操作。
这种方法看似可行,但是实际操作的时候会发现很可能需要修改原来的代码结构,同时,一旦function的数量到达一定量以后,这时候产品告诉我们,需求要改,还需要加入额外的操作,这时候一旦运气不好,比如增加一个方法参数,所有调用方法地方都得修改一遍。这时候就比较麻烦了。
有没有什么更好的方法?肯定是有的。首先分析上述方案,它是一个单一重复的调用过程,唯一的区别就是传入的functionId,这是一个单一却又重复的操作,那很明显,aop思想来解决这种问题是最好的。

具体实现

首先我们选用aop一个常用的类库:aspectj。因此这里我们通过字节码插桩的方式,修改编译之后的class来进行代码的自动生成,这样就不会对我们敲代码的逻辑产生任何影响。

代码原逻辑

非常简单,就是两个点击事件,模拟一下登陆注册的点击。

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button btnLogin = findViewById(R.id.btn_login);
        Button btnRegister = findViewById(R.id.btn_register);

        btnLogin.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.e("Statistics", "登陆");
            }
        });

        btnRegister.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.e("Statistics", "注册");
            }
        });
    }
}
aspectJ字节码插桩的实现

1.新建一个枚举类来定义需要埋点的功能:登陆、注册

public enum Function {

    LOGIN(1, "登陆"),
    REGISTER(2, "注册");

    int functionId;
    String functionName;

    Function(int functionId, String functionName) {
        this.functionId = functionId;
        this.functionName = functionName;
    }

    public String getFunctionName() {
        return functionName;
    }
}

2.新建一个注解类来标记需要统计点击次数的方法:
使用的时候只需要将需要统计的方法加上注解即可

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Statistics {
    Function function();
}

3.Aspectj的简单实现:

    @Aspect
    public class StatisticsInstrumentation {

        public static final String TAG = "Statistics";

        @Around("execution(@com.noob.databurialpoint.Statistics * *(..)) && @annotation(statistics)")
        public void aroundJoinPoint(ProceedingJoinPoint joinPoint, Statistics statistics) throws Throwable {
            calculate(statistics);
            joinPoint.proceed();//执行原方法
        }

        private void calculate(Statistics statistics){
            if(statistics != null){
                Log.e(TAG, "对" + statistics.function().getFunctionName() + "进行统计");
                // select * from FunctionsTable where operatorId=statistics.getFunctionId()
                //if(size > 0){
                // int counts = operateCounts ++
                // update FunctionsTable set operateCounts = counts
                // }else {
                // insert into FunctionsTable values (xxx, statistics.getFunctionId(), 1)
                // }
            }
        }
}

代码解释:

@Around("execution(@com.noob.databurialpoint.Statistics * *(..)) && @annotation(statistics)")
  • com.noob.databurialpoint.Statistics是Statistics注解的具体包名
  • @annotation(statistics) 代表执行方法中传入注解参数,才能再aroundJoinPoint方法里获取这个注解对象
  • calculate是点击统计的伪代码
注解使用
 public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button btnLogin = findViewById(R.id.btn_login);
        Button btnRegister = findViewById(R.id.btn_register);

        btnLogin.setOnClickListener(new View.OnClickListener() {
            @Override
            @Statistics(function = Function.LOGIN)
            public void onClick(View v) {
                Log.e("Statistics", "登陆");
            }
        });

        btnRegister.setOnClickListener(new View.OnClickListener() {
            @Override
            @Statistics(function = Function.REGISTER)
            public void onClick(View v) {
                Log.e("Statistics", "注册");
            }
        });
    }
}

测试结果:

相信大家也都看到了,我们根本没有对之前的方法进行修改,唯一的区别就是在调用方法上添加一了一个
@Statistics注解,如果我们需要修改逻辑,也只需要修改一次aspectJ的实现类StatisticsInstrumentation即可,这样就开发的时候就非常方便。
究其原因是因为aspectj修改了MainActivity.class类,修改后编译生成的class代码如下:

不再只是一个简单的log打印,而是回去调用我们额外写的StatisticsInstrumentation中的方法.这就是aspectj的作用。

总结

aspectj是一个很好的aop框架,此处只是aspectj的一个简单使用示例,关于更深入的用法这里就不再介绍,大家可以去网上寻找相关代码。

上述demo地址:https://github.com/JavaNoober/Databurialpoint

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值