1. 程式人生 > >通過aspectj對Android資料統計的簡單實現

通過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