精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

Android埋點技術分析

移動開發 Android
埋點,是對網站、App或者后臺等應用程序進行數據采集的一種方法。通過埋點,可以收集用戶在應用中的產生行為,進而用于分析和優化產品后續的體驗,也可以為產品的運營提供數據支撐,其中常見的指標有PV、UV、頁面時長和按鈕的點擊等。

一、埋點,是對網站、App或者后臺等應用程序進行數據采集的一種方法。通過埋點,可以收集用戶在應用中的產生行為,進而用于分析和優化產品后續的體驗,也可以為產品的運營提供數據支撐,其中常見的指標有PV、UV、頁面時長和按鈕的點擊等。

Android埋點技術分析

采集行為數據時,通常需要在Web頁面/App里面添加一些代碼,當用戶的行為達到某種條件時,就會向服務器上報用戶的行為。其實添加這些代碼的過程就可以叫做“埋點”,在很久以前就已經出現了這種技術。隨著技術的發展和大家對數據采集要求的不斷提高,我認為埋點的技術方案走過了下面幾個階段:

代碼埋點:代碼埋點是指開發人員按照產品/運營的需求,在Web頁面/App的源碼里面添加行為上報的代碼,當用戶的行為滿足某一個條件時,這些代碼就會被執行,向服務器上報行為數據。這種方案是最基礎的方案,每次增加或者修改數據上報的條件,都需要開發人員的參與,并且只能在下一個版本上線后才能看到效果。很多公司都提供了這類數據上報的SDK,將行為上報的后臺服務器接口封裝成了簡單的客戶端SDK接口。開發者可以通過嵌入這類SDK,在埋點的地方調用少量的代碼就可以上報行為數據。

全埋點:全埋點指的是將Web頁面/App內產生的所有的、滿足某個條件的行為,全部上報到后臺服務器。例如把App中所有的按鈕點擊都進行上報,然后由產品/運營去后臺篩選所需要的行為數據。這種方案的優點非常明顯,就是可以不用在新增/修改行為上報條件時,再找開發人員去修改埋點的代碼。然而它的缺點也和優點一樣明顯,那就是上報的數據量比代碼埋點大很多,里面可能很多是沒有價值的數據。此外,這種方案更傾向于獨立去看待用戶的行為,而沒有關注行為的上下文,給數據分析帶來了一些難度。很多公司也提供了這類功能的SDK,通過靜態或者動態的方式,“Hook”了原有的App代碼,從而實現了行為的監測,在數據上報時通常是采用累積多條再上報的方案來合并請求。

hook直譯是鉤子的意思,以前學信息安全的時候在windows上聽到過,大體意思是通過某種手段去改變系統API的一個行為,繞過系統的某個方法,或者改變系統的工作流程。在這里其實是指把本來要執行某個方法的對象替換成另一個,一般用的是反射或者代理,需要找到hook的代碼位置,甚至還可以在編譯階段實現替換。

可視化埋點: 可視化埋點是指產品/運營在Web頁面/App的界面上進行圈選,配置需要監測界面上哪一個元素,然后保存這個配置,當App啟動時會從后臺服務器獲得產品/運營預先圈選好的配置,然后根據這份配置監測App界面上的元素,當某一個元素滿足條件時,就會上報行為數據到后臺服務器。有了全埋點技術方案,從體驗優化的角度很容易想到按需埋點,可視化埋點就是一種按需配置埋點的方案。現在也有一些公司提供了這類SDK,圈選監測元素時,一般都是提供一個Web管理界面,手機在安裝并初始化了SDK之后,可以和管理界面了連接,讓用戶在Web管理界面上配置需要監測的元素。

業界有多家SDK都支持上面介紹的3種埋點方案中的一種或者全部,例如Mixpanel、Sensorsdata、TalkingData、GrowingIO、諸葛IO、Heap Analytics、MTA、Umeng Analytics、百度,只是大家對后兩種埋點的稱呼不完全相同,有的叫無埋點或者codeless埋點。由于 Mixpanel (支持代碼埋點、可視化埋點)和 Sensorsdata (全部支持)都開源了自己的全部SDK,技術方案也比較類似,下面以它們的Android SDK為例,簡單分析一下3種埋點方案的技術實現。關于JS的SDK技術實現,可以看下我的另一篇博客-JS埋點SDK技術分析。

二、代碼埋點

包含Mixpanel SDK在內的大部分SDK,都會把這種埋點方案封裝成一個比較簡單的接口,在這里是 track(String eventName, JSONObject properties) ,開發者在調用這個接口時,可以把一個事件名稱和事件的屬性傳入,然后就可以上報到后臺了。

在實現上,Mixpanel SDK默認采用一條HandlerThread線程來處理事件,當開發者調用 track(String eventName, JSONObject properties) 方法時, 主線程切換到HandlerThread 當中,并先將事件存入數據庫。然后看SDK中是否累計到了40個事件,如果累計到40個事件的話,就合并它們上報到后臺。

當開發者設置為debug模式,或者手動調用 flush 接口時,可以立即上報累計的所有事件,不過由于只有一條線程,所以如果在flush的時候,前面的事件還沒有處理完成,SDK會間隔1分鐘再次去處理后面的這些事件。

開發者可以設置累計上報的事件數量閾值、事件阻塞時再次嘗試上報的時間間隔等。這種方案比較基礎,相信大部分開發者都接觸過,不需要過多分析。

三、全埋點

3.1 AOP基礎

Mixpanel現在的Android SDK沒有提供這個功能,但是神策Android SDK提供了,實現方式是依賴AOP。那么什么是AOP?

在軟件業,AOP為Aspect Oriented Programming的縮寫,意為:面向切面編程,通過預編譯方式和運行期動態代理實現程序功能的統一維護的一種技術。AOP是OOP的延續,是軟件開發中的一個熱點,也是Spring框架中的一個重要內容,是函數式編程的一種衍生范型。利用AOP可以對業務邏輯的各個部分進行隔離,從而使得業務邏輯各部分之間的耦合度降低,提高程序的可重用性,同時提高了開發的效率。(from baidu baike)

簡而言之,AOP是可以通過預編譯方式和運行期動態代理實現在不修改源代碼的情況下給程序動態統一添加功能的一種技術。

Sensors Analytics AndroidSDK全埋點的實現就是通過在代碼編譯階段,找到源代碼中需要上報事件的位置,插入SDK的事件上報代碼。它用到的框架是 AspectJ 。

說到這里,必須簡單了解一下AspectJ以及它里面的一些概念.它是AOP的領跑者,在很多地方我們可以看到它的身影,例如JakeWartson大神貢獻的一個注解日志和性能調優框架 Hugo ,在Spring框架里面也大量應用到AspectJ。我理解AspectJ里面的主要幾個概念有:

  • JPoint: 代碼切點(就是我們要插入代碼的地方)
  • Aspect: 代碼切點的描述
  • Pointcut: 描述切點具體是什么樣的點,如函數被調用的地方( Call(MethodSignature) )、函數執行的內部( execution(MethodSignature) )
  • Advice: 描述在切點的什么位置插入代碼,如在Pointcut前面( @Before )還是后面( @After ),還是環繞整個Pointcut( @Around )

由此可見,在實現AOP功能時,需要做下面幾件事:

  • 定義一個Aspect,這個Aspect里面必須有Pointcut和Advice兩個屬性
  • 編寫在匹配到符合Pointcut和Advice描述的代碼時,需要注入的代碼
  • 在代碼編譯時,通過特殊的java編譯器(Aspect的ajc編譯器),找到符合我們定義的Aspect的代碼,將需要注入的代碼插入到Advice指定的位置。

如果你對AspectJ有了解的話,已經可以猜到SDK內部是怎么實現全埋點的了;如果沒有接觸,我覺得也不用急于全面地去學習AspectJ,因為SDK內部只用到了AspectJ當中的一小部分功能而已,可以直接看下面的分析。

3.2 全埋點-技術實現

神策SDK里面是如何監測View點擊事件呢?我把SDK代碼簡化一下進行分析,有下面幾個步驟:

3.2.1 定義一個Aspect 

  1. import org.aspectj.lang.JoinPoint; 
  2. import org.aspectj.lang.annotation.After
  3. import org.aspectj.lang.annotation.Aspect; 
  4. import org.aspectj.lang.annotation.Pointcut; 
  5.  
  6. @Aspect 
  7. public class ViewOnClickListenerAspectj{ 
  8.  
  9.     /** 
  10. * android.view.View.OnClickListener.onClick(android.view.View
  11. *@paramjoinPoint JoinPoint 
  12. *@throwsThrowable Exception 
  13. */ 
  14.     @After("execution(* android.view.View.OnClickListener.onClick(android.view.View))"
  15.     public void onViewClickAOP(final JoinPoint joinPoint)throws Throwable { 
  16.         AopUtil.sendTrackEventToSDK(joinPoint, "onViewOnClick"); 
  17.     } 

這段Aspect的代碼定義了: 在執行android.view.View.OnClickListener.onClick(android.view.View)方法原有的實現后面,需要插入 AopUtil.sendTrackEventToSDK(joinPoint, "onViewOnClick"); 這段代碼。

AopUtil.sendTrackEventToSDK(joinPoint, "onViewOnClick"); 這段代碼做的事情就是點擊事件的上報。因為神策SDK將全埋點功能和主SDK包分離成了兩個jar包,所以通過AopUtil工具去調用真正的事件上報代碼,這里不細述其實現,下面直接看這段代碼背后真正的點擊上報實現。

  1. SensorsDataAPI.sharedInstance().track(AopConstants.APP_CLICK_EVENT_NAME, properties); 

可以看到AOP實現的點擊監測,***也走 track 方法進行上報了。

3.2.2 使用ajc編譯器向源代碼中插入Aspect代碼

采用AspectJ框架編寫的代碼,想要注入原來的工程的代碼,需要在 /app/build.gradle 中引用ajc編譯器,腳本如下: 

  1. ... 
  2. import org.aspectj.bridge.IMessage 
  3. import org.aspectj.bridge.MessageHandler 
  4. import org.aspectj.tools.ajc.Main 
  5.  
  6. buildscript { 
  7.     repositories { 
  8.         mavenCentral() 
  9.     } 
  10.     dependencies { 
  11.         classpath 'org.aspectj:aspectjtools:1.8.10' 
  12.     } 
  13.  
  14. repositories { 
  15.     mavenCentral() 
  16.  
  17. android { 
  18.     ... 
  19.  
  20. dependencies { 
  21.     ... 
  22.     compile 'org.aspectj:aspectjrt:1.8.10' 
  23.  
  24. final def log = project.logger 
  25. final def variants = project.android.applicationVariants 
  26.  
  27. variants.all { variant -> 
  28.     if (!variant.buildType.isDebuggable()) { 
  29.         log.debug("Skipping non-debuggable build type '${variant.buildType.name}'."
  30.         return
  31.     } 
  32.  
  33.     JavaCompile javaCompile = variant.javaCompile 
  34.     javaCompile.doLast { 
  35.         String[] args = ["-showWeaveInfo"
  36.                      "-1.5"
  37.                      "-inpath", javaCompile.destinationDir.toString(), 
  38.                      "-aspectpath", javaCompile.classpath.asPath, 
  39.                      "-d", javaCompile.destinationDir.toString(), 
  40.                      "-classpath", javaCompile.classpath.asPath, 
  41.                      "-bootclasspath", project.android.bootClasspath.join(File.pathSeparator)] 
  42.         log.debug "ajc args: " + Arrays.toString(args) 
  43.  
  44.         MessageHandler handler = new MessageHandler(true); 
  45.         new Main().run(args, handler); 
  46.         for (IMessage message : handler.getMessages(nulltrue)) { 
  47.            switch (message.getKind()) { 
  48.                 case IMessage.ABORT: 
  49.                 case IMessage.ERROR: 
  50.                 case IMessage.FAIL: 
  51.                     log.error message.message, message.thrown 
  52.                     break; 
  53.                 case IMessage.WARNING: 
  54.                     log.warn message.message, message.thrown 
  55.                     break; 
  56.                 case IMessage.INFO: 
  57.                     log.info message.message, message.thrown 
  58.                     break; 
  59.                 case IMessage.DEBUG: 
  60.                     log.debug message.message, message.thrown 
  61.                     break; 
  62.             } 
  63.         } 
  64.     } 

在SensorsAndroidSDK中,把上面這段腳本編寫成了一個 gradle插件 ,開發者只需要在 app/build.gradle 引用這個插件即可。

  1. apply plugin: 'com.sensorsdata.analytics.android' 

3.2.3 完成代碼插入,查看插入之后的效果

完成上面兩步,就可以實現在 android.view.View.OnClickListener.onClick(android.view.View) 方法中插入我們的數據上報代碼了。我們在demo代碼中加一個Button,并給它set一個OnClickListener,編譯一下代碼,查看 /build/intermediates/classes/debug/ 里面class文件,經過ajc編譯之后,原始代碼中插入了Aspect的代碼,并調用了 ViewOnClickListenerAspectj 里面的 onViewClickAOP 方法。 

  1. public class MainActivityextends Activity{ 
  2.     public MainActivity(){ 
  3.     } 
  4.  
  5.     protected void onCreate(Bundle savedInstanceState){ 
  6.         super.onCreate(savedInstanceState); 
  7.         this.setContentView(2130968603); 
  8.         Button btnTst = (Button)this.findViewById(2131427422); 
  9.         btnTst.setOnClickListener(new OnClickListener() { 
  10.             public void onClick(View v){ 
  11.                 JoinPoint var2 = Factory.makeJP(ajc$tjp_0, this, this, v); 
  12.  
  13.                 try { 
  14.                     Log.i("MainActivity""button clicked"); 
  15.                 } catch (Throwable var5) { 
  16.                     ViewOnClickListenerAspectj.aspectOf().onViewClickAOP(var2); 
  17.                     throw var5; 
  18.                 } 
  19.  
  20.                 ViewOnClickListenerAspectj.aspectOf().onViewClickAOP(var2); 
  21.             } 
  22.  
  23.             static { 
  24.                 ajc$preClinit(); 
  25.             } 
  26.         }); 
  27.     } 

AspectJ的基本用法就是這樣,SensorsAndroidSDK借助AspectJ插入了Aspect代碼,這是一種靜態的方式。靜態的全埋點方案,本質上是對字節碼進行修改,插入事件上報的代碼。

修改字節碼,除了這種方案之外,還有Android Gradle插件提供的trasform api(1.5.0版本以上)、ASM、Javassist。在網易樂得的埋點方案,Nuwa熱修復項目都可以見到這些技術的實踐。

3.3 AspectJ相關資料

  • Aspect Oriented Programming in Android: https://fernandocejas.com/2014/08/03/aspect-oriented-programming-in-android/
  • AOP之AspectJ全面剖析in Android: http://www.jianshu.com/p/f90e04bcb326
  • 滬江開源了一個叫做AspectJX的插件,擴展了AspectJ,除了對src代碼進行AOP,還支持kotlin、工程中引用的jar和aar進行AOP: https://github.com/HujiangTechnology/gradle_plugin_android_aspectjx
  • 關于 Spring AOP (AspectJ) 你該知曉的一切: http://blog.csdn.net/javazejian/article/details/56267036

3.4 其他思路

上面介紹的是以AspectJ為代表的 “靜態Hook” 實現方案,有沒有其他辦法可以不修改源代碼,只是在App運行的時候去 “動態Hook” 點擊行為的處理呢?答案是肯定的,在Java的世界,還有反射大法啊,下面看一下怎么實現點擊事件的替換吧。

在 android.view.View.java 的源碼( API>=14 )中,有這么幾個關鍵的方法: 

  1. // getListenerInfo方法:返回所有的監聽器信息mListenerInfo 
  2. ListenerInfogetListenerInfo(){ 
  3.     if (mListenerInfo != null) { 
  4.         return mListenerInfo; 
  5.     } 
  6.     mListenerInfo = new ListenerInfo(); 
  7.     return mListenerInfo; 
  8.  
  9. // 監聽器信息 
  10. static class ListenerInfo{ 
  11.     ... // 此處省略各種xxxListener 
  12.     /** 
  13. * Listener used to dispatch click events. 
  14. * This field should be made private, so it is hidden from the SDK. 
  15. * {@hide} 
  16. */ 
  17.     public OnClickListener mOnClickListener; 
  18.  
  19.     /** 
  20. * Listener used to dispatch long click events. 
  21. * This field should be made private, so it is hidden from the SDK. 
  22. * {@hide} 
  23. */ 
  24.     protected OnLongClickListener mOnLongClickListener; 
  25.  
  26.     ... 
  27. ListenerInfo mListenerInfo; 
  28.  
  29. // 我們非常熟悉的方法,內部其實是把mListenerInfo的mOnClickListener設成了我們創建的OnclickListner對象 
  30. public void setOnClickListener(@Nullable OnClickListener l){ 
  31.     if (!isClickable()) { 
  32.         setClickable(true); 
  33.     } 
  34.     getListenerInfo().mOnClickListener = l; 
  35.  
  36. /** 
  37. * 判斷這個View是否設置了點擊監聽器 
  38. Return whether this view has an attached OnClickListener. Returns 
  39. true if there is a listener, false if there is none. 
  40. */ 
  41. public boolean hasOnClickListeners(){ 
  42.     ListenerInfo li = mListenerInfo; 
  43.     return (li != null && li.mOnClickListener != null); 

通過上面幾個方法可以看到,點擊監聽器其實被保存在了 mListenerInfo.mOnClickListener 里面。那么實現 Hook點擊監聽器 時,只要將這個 mOnClickListener 替換成我們包裝的 點擊監聽器代理對象 就行了。簡單看一下實現思路:

1. 創建點擊監聽器的代理類 

  1. // 點擊監聽器的代理類,具有上報點擊行為的功能 
  2. class OnClickListenerWrapperimplements View.OnClickListener{ 
  3.     // 原始的點擊監聽器對象 
  4.     private View.OnClickListener onClickListener; 
  5.  
  6.     public OnClickListenerWrapper(View.OnClickListener onClickListener){ 
  7.         this.onClickListener = onClickListener; 
  8.     } 
  9.  
  10.     @Override 
  11.     public void onClick(View view){ 
  12.         // 讓原來的點擊監聽器正常工作 
  13.         if(onClickListener != null){ 
  14.             onClickListener.onClick(view); 
  15.         } 
  16.         // 點擊事件上報,可以獲取被點擊view的一些屬性 
  17.         track(APP_CLICK_EVENT_NAME, getSomeProperties(view)); 
  18.     } 

2. 反射獲取一個View的mListenerInfo.mOnClickListener,替換成代理的點擊監聽器 

  1. // 對一個View的點擊監聽器進行hook 
  2. public void hookView(View view) { 
  3.     // 1. 反射調用View的getListenerInfo方法(API>=14),獲得mListenerInfo對象 
  4.     Class viewClazz = Class.forName("android.view.View"); 
  5.     Method getListenerInfoMethod = viewClazz.getDeclaredMethod("getListenerInfo"); 
  6.     if (!getListenerInfoMethod.isAccessible()) { 
  7.         getListenerInfoMethod.setAccessible(true); 
  8.     } 
  9.     Object mListenerInfo = listenerInfoMethod.invoke(view); 
  10.      
  11.     // 2. 然后從mListenerInfo中反射獲取mOnClickListener對象 
  12.     Class listenerInfoClazz = Class.forName("android.view.View$ListenerInfo"); 
  13.     Field onClickListenerField = listenerInfoClazz.getDeclaredField("mOnClickListener"); 
  14.     if (!onClickListenerField.isAccessible()) { 
  15.         onClickListenerField.setAccessible(true); 
  16.     } 
  17.     View.OnClickListener mOnClickListener = (View.OnClickListener) onClickListenerField.get(mListenerInfo); 
  18.      
  19.     // 3. 創建代理的點擊監聽器對象 
  20.     View.OnClickListener mOnClickListenerWrapper = new OnClickListenerWrapper(mOnClickListener); 
  21.      
  22.     // 4. 把mListenerInfo的mOnClickListener設成新的onClickListenerWrapper 
  23.     onClickListenerField.set(mListenerInfo, mOnClickListenerWrapper); 
  24.     // 用這個似乎也可以:view.setOnClickListener(mOnClickListenerWrapper); 

注意,如果是 API<14 的話,mOnClickListener直接是直接以一個Field保存在View對象中的,沒有ListenerInfo,因此反射的次數要更少一些。

3. 對App中所有的View進行Hook

我們在分析的是全埋點,那么怎樣把App里面所有的View點擊都Hook到呢?有兩種方式:

***種:當Activity創建完成后,開始從Activity的DecorView開始自頂向下深度遍歷ViewTree,遍歷到一個View的時候,對它進行hookView操作。這種方式有點暴力,由于這里面遍歷ViewTree的時候用到了大量反射,性能會有影響。

第二種:比***種方式稍微優秀一些,來源是一個Github上的開源庫 AndroidTracker (Kotlin實現)。他的處理方式是當Activity創建完成后,在DecorView中添加一個透明的View作為子View,在這個子View的onTouchEvent方法中,根據觸摸坐標找到屏幕中包含了這個坐標的View,再對這些View嘗試進行hookView操作。 這種方式比較取巧,首先是拿到了手指按下的位置,根據這個位置來找需要被Hook的View,避免了在遍歷ViewTree的同時對View進行反射。具體實現是在遍歷ViewTree中的每個View時,判斷這個View的坐標是否包含手指按下的坐標,以及View是否Visible,如果滿足這兩個條件,就把這個View保存到一個ArrayList hitViews。然后再遍歷這個ArrayList里面的View,如果一個View#hasOnClickListeners返回true,那么才對他進行hookView操作。

整體來看,動態Hook的思路用到了反射,難免對程序性能產生影響,如果要采用這種方式實現全埋點方案,還需要好好評估。

四、可視化埋點

4.1 可視化埋點-技術實現

可視化埋點,需要經過兩個步驟,可以由非技術人員操作完成。***步,使用嵌入了Mixpanel/SensorsSDK的App連接后臺,當手機App與后臺同步時,后臺管理界面上會顯示和手機App一樣的界面,用戶可以在管理界面上用鼠標選擇需要監測的元素,設置事件名稱,需要監測的元素屬性等(據說有些SDK的圈選操作是在手機上進行的,不管是什么方式本質上是一樣的,需要保存一份配置到后臺)。第二步,嵌入了SDK的App啟動時,會從服務器獲取到一份配置,再根據這份配置去檢測App中的界面及其元素,滿足配置的條件時向服務器上報事件。下面以Mixpanel、SensorsdataSDK為例,簡單分析一下技術方案的實現。

4.1.1 圈選需要監測的元素,保存配置

1.創建WebSocket連接后臺

采用WebSocket連接是因為要讓手機和后臺長時間保持連接,是一個持續的雙向通信。連接到后臺時,把手機的設備信息發送到后臺。

2.把App界面截圖發送到后臺

創建Socket連接后,在主線程中,對App中啟動的Activity進行掃描,找到界面的RootView(其實是DecorView)。在查找RootView的同時,會對RootView進行截圖,截圖時采用反射調用View類 createSnapshot 方法。

截圖之后,SDK內部會判斷圖片的hash值,如果圖片發生了變化,會采用遞歸的方法深度遍歷Activity的ViewTree,遍歷同時讀取View的屬性(id、top、left、width、height、class名稱、layoutRules等等)。

***,將上面收集到數據發送到連接的后臺,由后臺解析之后,把App界面展示在Web頁面。用戶可以在這個Web頁面圈選需要監測的元素,設置這個元素的時間名稱(event_type和event_name),并保存這個配置。

4.1.2 獲取配置,監測元素的行為,上報事件

1.獲取配置

SDK啟動時,會從服務器拉取一份JSON格式的配置,保存到sharedPreference里,同時SDK會掃描 android.R 文件里面的資源id和資源的name并保存起來。

SDK得到配置之后,解析成JSON對象,讀取 event_bindings 字段,再進一步讀取 events 字段,這個字段下面包含了一個數組,數組的每個元素都描述了一類事件,并包含了這類事件需要監測的元素所在的Activity和元素的路徑。這份配置基本上是這樣的一個結構: 

  1. event_bindings: { 
  2.     events:[ 
  3.         { 
  4.             target_activity: "" 
  5.             event_name: "" 
  6.             event_type: "" 
  7.             path: [ 
  8.                 { 
  9.                     prefix: 
  10.                     view_class: 
  11.                     index
  12.                     id: 
  13.                     id_name: 
  14.                 },  
  15.                 ... 
  16.             ] 
  17.         } 
  18.     ] 

收到了這份配置之后,SDK會把根據每個event信息,生成一個 ViewVisitor 。 ViewVisitor 的作用就是把 path 數組里面指向的所有View元素都找到,并且根據event_type,給這個View元素設置相應的行為監測器,當這個View發生指定行為時,監測器就會監測到,并上報行為。

生成ViewVisitor之后,SDK內部是以 Map 結構保存它們的,這也比較容易理解。

2.監測元素,上報事件

ViewVisitor 是怎么監測元素的產生的行為呢?答案就是 View.AccessibilityDelegate 。

在Android SDK里面,AccessibilityService)為我們提供了一系列的事件回調,幫助我們指示一些用戶界面的狀態變化。我們可以派生輔助功能類,進而對不同的AccessibilityEvent進行處理,我們看下AccessibilityEvent里面有哪些事件類型: 

  1. /** 
  2. * Represents the event of clicking on a {@linkandroid.view.Viewlike 
  3. * {@linkandroid.widget.Button}, {@linkandroid.widget.CompoundButton}, etc. 
  4. */ 
  5. public static final int TYPE_VIEW_CLICKED = 0x00000001; 
  6.  
  7. /** 
  8. * Represents the event of long clicking on a {@linkandroid.view.Viewlike 
  9. * {@linkandroid.widget.Button}, {@linkandroid.widget.CompoundButton}, etc. 
  10. */ 
  11. public static final int TYPE_VIEW_LONG_CLICKED = 0x00000002; 
  12.  
  13. /** 
  14. * Represents the event of selecting an item usually in the context of an 
  15. * {@linkandroid.widget.AdapterView}. 
  16. */ 
  17. public static final int TYPE_VIEW_SELECTED = 0x00000004; 
  18.  
  19. /** 
  20. * Represents the event of setting input focus of a {@linkandroid.view.View}. 
  21. */ 
  22. public static final int TYPE_VIEW_FOCUSED = 0x00000008; 
  23.  
  24. /** 
  25. * Represents the event of changing the text of an {@linkandroid.widget.EditText}. 
  26. */ 
  27. public static final int TYPE_VIEW_TEXT_CHANGED = 0x00000010; 
  28. ... 

以點擊事件 TYPE_VIEW_CLICKED 為例 ,當Activity界面的RootView開始繪制的時候(ViewTreeObserver.OnGlobalLayoutListener的onGlobalLayout回調時),ViewVisitor也會開始尋找指定的View,并給這個View設置新的AccessibilityDelegate。簡單看一下這個新的View.AccessibilityDelegate是怎么寫的: 

  1. private class TrackingAccessibilityDelegateextends View.AccessibilityDelegate{ 
  2. ... 
  3.             public TrackingAccessibilityDelegate(View.AccessibilityDelegate realDelegate){ 
  4.                 mRealDelegate = realDelegate; 
  5.             } 
  6.  
  7.             public View.AccessibilityDelegategetRealDelegate(){ 
  8.                 return mRealDelegate; 
  9.             } 
  10.  
  11.             ... 
  12.              
  13.             @Override 
  14.             public void sendAccessibilityEvent(View host,int eventType){ 
  15.                 if (eventType == mEventType) { 
  16.                     fireEvent(host); // 事件上報 
  17.                 } 
  18.  
  19.                 if (null != mRealDelegate) { 
  20.                     mRealDelegate.sendAccessibilityEvent(host, eventType); 
  21.                 } 
  22.             } 
  23.  
  24.             private View.AccessibilityDelegate mRealDelegate; 
  25.         } 
  26.         ... 

可以看到在SDK的 TrackingAccessibilityDelegate#sendAccessibilityEvent 方法里面,發出了事件上報。

那么View在點擊方法的內部實現里有調用 sendAccessibilityEvent 方法嗎?通過View處理點擊事件時調用的 View.performClick 方法,看一下源碼: 

  1. public boolean performClick(){ 
  2.     final boolean result; 
  3.     final ListenerInfo li = mListenerInfo; 
  4.     if (li != null && li.mOnClickListener != null) { 
  5.         playSoundEffect(SoundEffectConstants.CLICK); 
  6.         li.mOnClickListener.onClick(this); 
  7.         result = true
  8.     } else { 
  9.         result = false
  10.     } 
  11.     sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED); 
  12.     return result; 
  13. ... 
  14. public void sendAccessibilityEvent(int eventType){ 
  15.     if (mAccessibilityDelegate != null) { 
  16.         mAccessibilityDelegate.sendAccessibilityEvent(this, eventType); 
  17.     } else { 
  18.         sendAccessibilityEventInternal(eventType); 
  19.     } 
  20. ... 
  21. public void setAccessibilityDelegate(@Nullable AccessibilityDelegate delegate){ 
  22.     mAccessibilityDelegate = delegate; 

由此可以見在RootView開始繪制的時候,給View注冊AccessibilityDelegate可以監測到它的點擊事件。

4.2 可視化埋點的難點和問題

上面簡單分析了Mixpanel和SensorsSDK可視化埋點的基本實現,里面還有一個難點需要仔細琢磨,那就是 如何唯一標識App中的一個View? 需要記錄View的哪些信息,如何生成View的唯一ID,保證在不同手機上這些ID是固定的,而且保證App每次啟動,ID也不會變化,同時ID也要能應對一定程度的界面調整。

另外在網上看到有網友提出,setAccessibilityDelegate來監測View的點擊對大多數廠商的機型和版本都是可以的,但是有部分機型是無法成功捕獲監控到點擊事件。從View的標識生成,以及監測原理來講,這個方案的穩定性存在一些疑問。

4.3 參考資料

  • sensorsdata git,包含了Android、iOS、js、JAVA等多個版本的SDK: https://github.com/sensorsdata
  • Mixpanel git,包含了Android、iOS、js、JAVA等多個版本的SDK: https://github.com/mixpanel
  • 網易移動端數據收集和分析博客: http://www.jianshu.com/c/ee326e36f556

五、總結

***簡單總結一下幾種方案的優缺點和使用場景,在實際應用中多種方式配合使用,平衡效率和可靠性,適合自己的業務才是***的。 

Android埋點技術分析

責任編輯:未麗燕 來源: Uncle Chen
相關推薦

2016-12-12 13:42:54

數據分析大數據埋點

2021-06-17 13:35:23

數據埋點分析客戶端

2023-12-13 18:46:50

FlutterAOP業務層

2018-11-14 11:26:49

神策數據

2025-07-11 09:09:00

2024-08-27 08:27:19

2023-04-19 09:05:44

2023-01-10 09:08:53

埋點數據數據處理

2021-02-19 07:59:21

數據埋點數據分析大數據

2021-08-10 13:50:24

iOS

2024-07-18 08:33:19

2021-08-31 19:14:38

技術埋點運營

2022-10-14 08:47:42

埋點統計優化

2022-11-01 18:21:14

數據埋點SDK

2024-08-29 14:44:01

質檢埋點

2023-09-05 07:28:02

Java自動埋點

2024-03-06 19:57:56

探索商家可視化

2020-04-29 16:24:55

開發iOS技術

2017-04-11 15:34:41

機票前臺埋點

2025-01-22 14:00:12

點贊
收藏

51CTO技術棧公眾號

欧美在线黄色| 精品国产一区二| 欧美国产亚洲另类动漫| 国产日韩欧美视频在线| 久久久久香蕉视频| 国产成人1区| 欧美一区二区精美| 99爱视频在线| 米奇777四色精品人人爽| www.在线欧美| 国产欧美韩国高清| 日韩欧美不卡视频| 婷婷亚洲综合| 亚洲精品资源在线| 中文字幕第10页| 亚洲播播91| 亚洲一区二区视频| 午夜久久资源| 手机看片国产1024| 精品亚洲欧美一区| 国产不卡在线观看| 国产午夜福利精品| 天天色天天射综合网| 亚洲女人被黑人巨大进入| 亚洲丝袜在线观看| 欧美日韩国产网站| 欧美日韩国产一中文字不卡| 国产精品啪啪啪视频| 女人偷人在线视频| k8久久久一区二区三区 | 亚洲精品不卡在线| 黄色a级三级三级三级| 欧美成人精品三级网站| 天天色图综合网| 奇米777四色影视在线看| 中文字幕在线观看日本| 久久精品男人的天堂| 国产视频一区二区三区四区| h狠狠躁死你h高h| 看国产成人h片视频| 国产精品黄色av| 国产精品21p| 日韩视频二区| 久久露脸国产精品| 精品无码久久久久| 欧美日韩影院| 欧美激情在线狂野欧美精品| 极品盗摄国产盗摄合集| 99久久99热这里只有精品| 在线看欧美日韩| 久久视频精品在线观看| 亚洲免费专区| 亚洲视频在线播放| 亚洲人成人无码网www国产| 色婷婷综合久久久久久| 精品伊人久久97| 免费成人深夜夜行p站| 精品人人人人| 日韩精品丝袜在线| 蜜桃精品成人影片| 国产精品欧美在线观看| 国产一区二区三区欧美| av黄色在线免费观看| 国产精品一区二区99| 亚洲欧美一区二区激情| 欧美 日韩 国产 成人 在线观看| 精品欧美久久| 久久久精品999| 美女的奶胸大爽爽大片| 韩国亚洲精品| 91精品国产乱码久久久久久久久| 国产一级片毛片| 日韩精品电影在线观看| 国产日产亚洲精品| 国产喷水吹潮视频www| 国产精品一二三区| 国产嫩草一区二区三区在线观看| 污污视频在线观看网站| 久久精品在线免费观看| 亚洲一区综合| 九色91在线| 色屁屁一区二区| 色网站在线视频| 超碰地址久久| 亚洲午夜色婷婷在线| 欧美性x x x| 亚洲二区在线| 国产精品久久久久久久久久久久| 国产手机视频在线| www.在线成人| 中文字幕在线观看一区二区三区| 亚洲小说区图片区都市| 精品毛片三在线观看| 午夜欧美福利视频| 日韩精品三级| 亚洲欧美日韩天堂一区二区| 黑鬼狂亚洲人videos| 日韩一级精品| 国产中文字幕91| 香蕉视频国产在线| 成人欧美一区二区三区白人 | 不卡av免费观看| 色婷婷综合久久| 日本wwwwwww| 欧美日中文字幕| 韩国国内大量揄拍精品视频| 日本妇乱大交xxxxx| 不卡一区二区在线| 一区二区视频在线免费| 亚洲精品永久免费视频| 欧美一区二区三区视频免费播放| 国产精品揄拍100视频| 自拍偷拍欧美| 国产精品国产三级国产专播精品人 | 舐め犯し波多野结衣在线观看| 亚洲人metart人体| 日本久久久久久久久久久| 国产黄色片免费观看| 国产三级精品在线| av在线观看地址| 久久久免费人体| 亚洲美女av在线| 日韩av一二三区| 国产精品一区一区| 在线观看免费91| 高清电影一区| 亚洲人成电影在线播放| 日韩黄色a级片| 丁香激情综合国产| 中文字幕一区二区三区精彩视频| 韩日成人影院| 日韩av网站导航| 久久精品视频久久| 国产传媒日韩欧美成人| 日韩 欧美 自拍| 国产一区二区三区四区五区3d| 亚洲成人中文字幕| 免费在线视频观看| 国产乱子伦一区二区三区国色天香| 三区精品视频观看| 免费成人直播| 亚洲精品自产拍| 国产综合精品视频| 91美女精品福利| 人妻有码中文字幕| 天堂俺去俺来也www久久婷婷| 欧美福利视频在线观看| 午夜久久久久久噜噜噜噜| 亚洲靠逼com| 无码人妻一区二区三区一| 国内精品美女在线观看| 成人激情在线播放| 成人日日夜夜| 日韩欧美国产综合| 好吊色视频在线观看| 黄色日韩网站视频| 日日噜噜夜夜狠狠久久丁香五月 | 国产伦理久久久| 91福利区在线观看| 亚洲精品美女网站| 欧美超碰在线观看| 中文字幕精品一区二区精品绿巨人| 99视频精品免费| 第四色成人网| 亚洲iv一区二区三区| 制服丝袜在线播放| 精品福利一二区| 久久青青草原亚洲av无码麻豆| 久久久久久久久久美女| 亚洲欧美日韩一级| 一区二区三区国产精华| 成人av影视在线| 成人欧美一区二区三区的电影| 国产午夜精品全部视频播放| 国产一区二区小视频| 一区二区三区中文在线观看| 久久一区二区电影| 蜜桃一区二区三区在线| 激情视频小说图片| 欧美一级三级| 国产噜噜噜噜噜久久久久久久久 | 欧美区一区二| 久久精品国产精品国产精品污| 免费福利视频一区二区三区| www.99久久热国产日韩欧美.com| 成人久久久精品国产乱码一区二区| 精品福利樱桃av导航| xxxx日本黄色| 国产成人免费视频| 国产裸体舞一区二区三区| 99久久激情| 国产一区二区在线网站 | 一本久道久久综合婷婷鲸鱼| 色一情一乱一伦一区二区三区丨| www.成人在线.com| 456亚洲影院| www久久日com| 亚洲免费视频一区二区| 国产高清视频免费| 一本久久精品一区二区| 欧美xxxx黑人xyx性爽| 91免费观看视频| 韩国黄色一级片| 日韩精品一二区| 免费在线观看亚洲视频| 天天做天天爱综合| 日韩av高清在线播放| av成人app永久免费| 国产精品永久在线| 女人高潮被爽到呻吟在线观看| 北条麻妃一区二区三区中文字幕| 亚洲av成人无码久久精品老人| 91麻豆精品国产91久久久 | 国产精品—色呦呦| 日韩中文字幕不卡视频| 亚州av在线播放| 欧美mv和日韩mv的网站| 一级片一区二区三区| 色哟哟亚洲精品| 男女视频免费看| 亚洲美女淫视频| 日韩不卡av在线| 91视频观看视频| youjizz.com国产| 国产精品综合av一区二区国产馆| xxxx一级片| 日韩综合小视频| 成人免费观看视频在线观看| 国模大胆一区二区三区| 日本黄网站色大片免费观看| 操欧美老女人| 神马影院我不卡| 最新亚洲精品| 久久综合色一本| 女仆av观看一区| 国产精品美女黄网| 伊人久久亚洲| 99re在线视频上| 亚洲欧美激情一区二区三区| 亚洲精品成人一区| 国产mv免费观看入口亚洲| 国产精品一区二区日韩| 久久99久久亚洲国产| 黄色网址视频在线观看| 色婷婷综合成人av| 在线观看黄色av| 中文字幕日韩精品有码视频| 粉嫩av一区| 一区二区三区视频免费在线观看 | 亚洲电影在线看| 亚洲高清在线观看视频| 日韩欧美美女一区二区三区| www.我爱av| 精品福利二区三区| 婷婷亚洲一区二区三区| 日韩电影在线观看中文字幕 | 五十路熟女丰满大屁股| 亚洲第一伊人| 久热免费在线观看| 免费观看日韩电影| www.cao超碰| 国产老妇另类xxxxx| 国产麻豆剧传媒精品国产| 少妇一区视频| 国产欧美综合在线观看第十页| 图片区乱熟图片区亚洲| 国产精品一区三区| 国产精品九九视频| 久久麻豆一区二区| 亚洲黄色网址大全| 亚洲摸摸操操av| 精品无码一区二区三区电影桃花 | 中文字幕在线免费不卡| 欧美风情第一页| 亚洲综合色成人| 一级黄色在线视频| 欧美色图免费看| 国产熟女精品视频| 日韩精品免费在线播放| 91大神在线网站| 欧美激情18p| xxx欧美xxx| 91精品免费看| 精品亚洲自拍| 亚洲乱码一区二区三区| 欧美三级网页| 亚洲中文字幕久久精品无码喷水| 久久99精品国产麻豆婷婷洗澡| 亚洲最大视频网| 国产三级久久久| 国产一级做a爱免费视频| 色综合久久久久久久久久久| 国产喷水吹潮视频www| 亚洲美女在线看| 黄色成人影院| 日韩av免费看| 97品白浆高清久久久久久| 日韩亚洲视频| 国产日韩欧美一区在线 | 亚洲福利影视| 免费看成人午夜电影| 亚洲欧美日韩高清在线| 热久久精品国产| 国产91精品露脸国语对白| av免费播放网站| 精品国产91久久久久久| 国产精品国产精品国产专区| 日韩久久精品电影| 在线观看wwwxxxx| 国产伊人精品在线| 偷拍一区二区| 中文精品无码中文字幕无码专区| 日本亚洲视频在线| 亚洲观看黄色网| 一区二区三区在线观看视频| 中文字幕乱码人妻二区三区| 日韩激情av在线播放| 中文在线免费| 91免费看片网站| 久久一区二区三区电影| wwwxxx黄色片| 久久亚洲精品国产精品紫薇| 久久97人妻无码一区二区三区| 欧美日韩卡一卡二| 国产永久免费高清在线观看视频| 97精品国产91久久久久久| av在线播放一区二区| 先锋在线资源一区二区三区| 国产午夜久久| 大尺度做爰床戏呻吟舒畅| 亚洲男人的天堂在线观看| 中文字幕在线网站| 亚洲一区999| 香蕉成人影院| 日本欧美色综合网站免费| 亚洲女同在线| 亚洲黄色在线网站| 日韩欧美在线网址 | 欧美影院视频| 九九久久九九久久| 国产一区二区三区黄视频 | caoporm免费视频在线| 国产在线观看精品| 国产精品99久久精品| 久久久久久综合网| 亚洲视频一区在线观看| 一级特黄录像免费看| 北条麻妃久久精品| 国产一区二区久久久久| 无码毛片aaa在线| 国产精品一区二区不卡| 九九视频在线免费观看| 精品国一区二区三区| 欧美巨大xxxx做受沙滩| 国产精品一区二区欧美| 亚洲国产精品第一区二区| 国产精品福利导航| 欧美午夜片在线免费观看| 东热在线免费视频| 国产日韩在线视频| 欧美精品入口| 在线黄色免费网站| 色综合天天综合给合国产| 成人性生交大片免费看午夜| 国产欧美日韩高清| 欧美日本不卡| 波多野结衣影院| 欧美三级中文字幕| 成人影院在线观看| 精品一区二区三区国产| 蜜桃av一区| 91久久久久久久久久久久久久| 欧美一卡2卡三卡4卡5免费| jizzjizz中国精品麻豆| 欧美凹凸一区二区三区视频| 蜜臀久久99精品久久久画质超高清 | 日韩系列在线| 天天色综合天天色| 有码一区二区三区| 美女毛片在线看| 91久久精品美女| 日韩视频在线一区二区三区 | 国产美女精品| 精品一区二区6| 欧美精品一区二区久久久| www.com.cn成人| 一二三四中文字幕| 久久综合九色综合欧美就去吻| 91午夜交换视频| 91干在线观看| 亚洲乱码免费伦视频| 日韩人妻一区二区三区| 欧美一区日韩一区| 日韩精品影片| 国产av熟女一区二区三区| 国产日韩成人精品| 黄色av小说在线观看| 国产乱肥老妇国产一区二| 国产一区二区三区久久| 亚洲xxxx3d动漫|