$AppClick 全埋點之代理 View.AccessibilityDelegate
Accessibility
Accessibility,即輔助功能。許多 Android 使用者有不同的能力(限制),這要求他們可能會以不同的方式使用他們的 Android 裝置 。這些限制包括視力、肢體、年齡等,這些 限制阻礙了他們看到或充分使用觸控式螢幕,而使用者的聽力喪失,讓他們可能無法感知聲音 資訊和警報。
Android 提供了輔助功能的特性和服務,幫助這些使用者更容易的使用他們的裝置,這些 功能包括語音合成、觸覺反饋、手勢導航、軌跡球和方向鍵導航。Android 應用程式開 發人員可以利用這些服務,使他們的應用程式更貼近使用者。該輔助服務工作在後臺,由 系統呼叫,使用者介面的一些狀態(比如Button 被點選了)的改變可以通過回撥 Accessibilityservice 方法來通知。
比如下面的這個 Button:
< android.support.v7.widget.AppCompatButton
android:layout width="wrap content"
android:layout height="wrap content" android:text=" 神策資料 "
android:contentDescription="SensorsData"/>
由於添加了 android:contentDescription 屬性,當用戶移動焦點到這個按鈕或將滑鼠懸停在它上面時,提供口頭反饋的輔助功能服務就會發出“SensorsData”的聲音。
View.AccessibilityDelegate
我們先看一下 View.java 的 performClick() 原始碼:
/**
*Call this view's OnClickListener, if it is defined.
Performs all normal
*actions associated with clicking: reporting accessibility event, playing
*a sound, etc.
*
*@return True there was an assigned OnClick- Listener that was called, false
*otherwise is returned.
*/
public boolean performClick() {
final boolean result;
final ListenerInfo li = mListenerInfo;
if (li != null && li.mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.- CLICK);
li.mOnClickListener.onClick(this);
result = true;
} else {
result = false;
}
sendAccessibilityEvent(AccessibilityEvent.- TYPE VIEW CLICKED);
notifyEnterOrExitForAutoFillIfNeeded(true);
return result;
}
......
public void sendAccessibilityEvent(int eventType){
if (mAccessibilityDelegate != null) {
mAccessibilityDelegate.sendAccessibilit-
yEvent(this, eventType);
} else {
sendAccessibilityEventInternal(eventType);
}
}
......
public void setAccessibilityDelegate(@Nullable AccessibilityDelegate delegate){
mAccessibilityDelegate = delegate;
}
從上面的原始碼可以很容易的看出來,當一個 View 被點選的時候,系 統會先呼叫當前 View 已設定的 mOnClickListener 的 onClick(view)方法,然後再呼叫 sendAccessibilityEvent(AccessibilityEvent.- TYPE VIEW CLICKED) 的內部方法。在 sendAccessibili- tyEvent(int eventType) 方法的內部實現裡,其實是呼叫 mAccessibilityDelegate 的 sendAccessibilityEvent 方 法,並傳入當前 View 物件和 AccessibilityEvent.- TYPE VIEW CLICKED。因此,我們只需要代理 View 的 mAccessibilityDelegate,當一個 View 被點選時,在原有 mOnClickListener 執行之後,我們就能收到“訊息”。 代理 mAccessibilityDelegate 之後,我們就能拿到當前被點選的 View 物件,從而可以加入自動埋點的邏輯,達到 自動埋點的效果。
原理概述
在應用程式自定義的 Application 的 onCreate() 方法中初始化埋點 SDK,並傳入當前的 Application 物件。SDK 就可以拿到這個 Application 物件,然後我們就可以通過 application.registerActivityLifecycleCallback 這個方法來注 冊 Application.ActivityLifecycleCallbacks。這樣 SDK 就可以對 App 中所有的 Activity 的生命週期事件進行集中處理(監 控)了。在 ActivityLifecycleCallbacks 的 onActivityResumed(Activity activity, Bundle bundle) 方法中,我們可以拿到當前正在顯示的 Activity 物件,然後再通過 activity.getWindow().getDecorView() 方法或者 activity.findViewById(android.R.id.content) 方法拿到當前 Activity 的 RootView,通過 rootView.get- ViewTreeObserver() 方法可以拿到 RootView 的 ViewTreeObserver 物件,然後再通過 addOnGlobalLay- outListener() 方法給 RootView 註冊 ViewTreeObserv- er.OnGlobalLayoutListener 監聽器,這樣我們就可以在收到當前 Activity 的檢視狀態發生改變時去主動遍歷一次 RootView,並用我們自定義的 SensorsDataAccessibili- tyDelegate 代理當前 View 的 mAccessibilityDelegate 屬性。在我們自定義的 SensorsDataAccessibilityDelegate 檔案中的 public void sendAccessibilityEvent(View host, int eventType) 方法中,我們先呼叫原有的 mAccessibili- tyDelegate 的 sendAccessibilityEvent 方法,然後再插入埋點程式碼,其中 host 即是當前被點選的 View 物件,從而 可以做到自動埋點的效果。
實現步驟
完整的專案原始碼後續會 release 給大家。
缺點
• Application.ActivityLifecycleCallbacks 要求 API 14+ • view.hasOnClickListeners() 要求 API 15+ • 無法採集 Dialog、PopupWindow 的點選事件 • 每次點選都需要遍歷一次 RootView,效率比較低
知識點
• Application.ActivityLifecycleCallbacks • Android 系統事件處理機制 • View Elevation
參考資料
[1]https://github.com/foolchen/AndroidTracker
注:該內容來自神策資料使用者行為洞察研究院出品的《Android 全埋點解決方案》白皮書,檢視完整白皮書可點選 360775e118954518de2c?utm_source=WeChat&utm_medium=free&utm_term=%e9%98%85%e8%af%bb%e5%8e%9f%e6%96%87&utm_content=%e7%99%bd%e7%9a%ae%e4%b9%a6-Android%e5%85%a8%e5%9f%8b%e7%82%b9&utm_campaign=sensorsdata2" rel="nofollow,noindex" target="_blank">《Android 全埋點解決方案》
更多白皮書、報告、乾貨和案例,可以關注“神策資料”和“使用者行為洞察研究院”公眾號瞭解~