以往的埋點方式都是人爲進行定義名稱和選擇性埋點,版本迭代屢次後形成埋點數量持續增長。java
public interface ActivityLifecycleCallbacks {
void onActivityCreated(@NonNull Activity activity, @Nullable Bundle savedInstanceState);
void onActivityStarted(@NonNull Activity activity);
void onActivityResumed(@NonNull Activity activity);
void onActivityPaused(@NonNull Activity activity);
void onActivityStopped(@NonNull Activity activity);
void onActivitySaveInstanceState(@NonNull Activity activity, @NonNull Bundle outState);
void onActivityDestroyed(@NonNull Activity activity);
}
複製代碼
在ActivityLifecycleCallbacks接口中監聽start和pause,並使用SP和ContentProvider進行輔助記錄應用的開啓時間和pause時間,若是用戶App在後臺被強殺或者手動退出,那麼下次從新使用APP的時候會進行檢測Sp中的時間和當前的時間,而後進行對比,判斷用戶是否爲從新啓動APP,仍是僅僅切換到後臺再切換回來。android
注意⚠️:start中進行檢測,pause中進行時間數據更新。編程
總體思路:根據ActivityLifecycleCallbacks接口監聽回調,在onActivityResume回調中拿到當前的Activity,而後利用DecorView遞歸遍歷全部子view進行代理onClickListener方法。同時在Activity啓動的時候進行ViewTree的observer,ViewTree改動的時候(好比設置了view的不可見不可點擊等)從新進行一遍hook。json
hook:利用反射獲取到View已經設置的onClickListener對象、區別view的對象類型(button,textView.....)進而設置不一樣的listener。app
缺點:基本每一個View或者Viewgroup都會有本身的點擊事件,而且點擊事件接口都爲class內部的藉口,沒有頂層的接口進行兼容檢測,因此須要作大量的wrapperListener,工做繁瑣重複。此外,每建立一個頁面就要進行一次Hook,性能不高,效率低。框架
每次點擊的事件分發函數——dispatchTouchEvent(MotionEvent event),進行hook,利用當前activity的RootView的信息再結合event的信息進行埋點。maven
具體:判斷點擊的座標是否位於view(利用rootView循環判斷)之中、該view是否處於可見狀態;ide
缺點:每次點擊都要去遍歷一次rootView,而且逐個判斷,效率低下。函數
面向切面編程。使用AspectJ,性能
思路:在程序編譯期間,在相應的onClick方法調用前或後插入埋點代碼。
字節碼函數插樁目前有如下兩種框架
思路:應用程序打包成APK以前會先編譯成.class文件,而後打包成dex,最後組成apk。因此在打包成dex文件和編譯成.class文件之間進行源文件的替換就行。
缺點:目前沒什麼缺點
與ASM思路一致,可是和ASM對比,效率不夠高。
通過上述方案的對比,最終採用ASM進行字節碼插樁。主要是對代碼的侵入低,可定製化配置(過濾採集頁面,過濾時長,配置頁面映射等)。
下圖箭頭指向處就是進行函數插樁的位置。
方案實現是在代碼文件編譯成class文件以後進行方法的插入,無需在編寫階段進行。
比java中使用反射快,在ASM的官網中也有介紹。ASM的設計和實現是儘量的小和儘量快,因此它很是適合在動態系統中使用(但固然也能夠以靜態方式使用,例如在編譯器中使用)。
更多關於框架ASM的遠離和具體使用在這裏就不贅述了。
在project的build.gradle添加:
buildscript {
repositories {
google()
jcenter()
maven {
url uri('repo')
}
}
dependencies {
classpath 'com.cage:autotrack.android:1.0.0'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
複製代碼
在APP模塊中:
apply plugin: 'com.cage.plugin'
dependencies{
implementation project(':cgtrack_support')
}
複製代碼
初始化:
//Application中初始化
//kotlin
TrackApi.init(this)
//java
TrackApi.INSTANCE.init(this);
//配置
ConfigOptions.INSTANCE.addTrackInfoCallBack(new TrackInfoCallback() {
@Override
public void trackInfo(String eventName, JSONObject json) {
//這裏進行埋點事件上報
//固然回調的類型也能夠從JSONObjetc變爲String
}
});
複製代碼
在APP中進行點擊瀏覽頁面,相應的事件進行觸發:
頁面點擊的時候觸發:
頁面退出的時候觸發:
進入頁面的時候觸發:
目前已經覆蓋了View,Dialog,CompoundButton,AdapterView,BottomNavigationView。
後續若是缺乏相應的控件,那麼能夠根據相應的控件進行添加對應的字節碼描述便可:
例如在APP中的底部控件爲Google的design控件,添加:
SDK_API_CLASS = "com/cage/cgtrack/TrackUtils"
//普通設置點擊事件
if(mInterfaces.contains('android/support/design/widget/BottomNavigationView$OnNavigationItemSelectedListener') && nameDesc == 'onNavigationItemSelected(Landroid/view/MenuItem;)Z') {
//插入變量
methodVisitor.visitVarInsn(ALOAD, 1)
//插入方法
methodVisitor.visitMethodInsn(INVOKESTATIC, SDK_API_CLASS, "trackViewOnClick", "(Landroid/view/MenuItem;)V", false)
}
//使用Lambda形式設置
MethodCell onNavigationItemSelected = new MethodCell(
'onNavigationItemSelected',
'(Landroid/view/ MenuItem;)Z',
'Landroid/support/design/widget/BottomNavigationView$OnNavigationItemSelectedListener',
'trackViewOnClick',
'(Landroid/view/MenuItem;)V',
1, 1,
[Opcodes.ALOAD])
LAMBDA_METHODS.put(onNavigationItemSelected.parent + onNavigationItemSelected.name + onNavigationItemSelected.desc, onNavigationItemSelected)
複製代碼
上述步驟的意思:
先判斷該類中實現的接口是否包含OnNavigationItemSelectedListener接口,接着判斷實現該接口的方法是否是onNavigationItemSelected,若是符合,那麼表明這個類包含該接口並實現了方法,能夠進行埋點代碼的插入。