精品欧美一区二区三区在线观看 _久久久久国色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技術棧公眾號

欧美精品一区二区在线播放| 99精品国产视频| 丝袜亚洲欧美日韩综合| 中文字幕永久有效| 欧美亚洲系列| 久久综合色婷婷| 国产精品久久久久久搜索| 91 在线视频| 国产厕拍一区| 欧美日韩一区二区三区在线| 日本a级片在线播放| 色播色播色播色播色播在线| 日本亚洲最大的色成网站www| 久青草国产97香蕉在线视频| 尤物网站在线观看| 免费视频观看成人| 亚洲成av人影院在线观看网| 日韩一区二区电影在线观看| 亚洲黄色在线免费观看| 肉色丝袜一区二区| 成人97在线观看视频| 亚洲一区二区三区无码久久| 综合久久av| 欧美日韩综合视频| 男人日女人的bb| 国产高清美女一级毛片久久| 国产91精品在线观看| 国产精品久久久久福利| 国产精品suv一区二区| 日本高清免费电影一区| 亚洲成人黄色网| 日本美女视频一区| 欧美最新精品| 亚洲大型综合色站| 国产女人18毛片| 国产高清在线看| 成人动漫av在线| 亚洲伊人久久大香线蕉av| 老熟妇一区二区三区| 尤物精品在线| 美日韩精品免费观看视频| av电影网站在线观看| 久久九九热re6这里有精品| 日韩一级大片在线观看| 国内外成人免费在线视频| 超级碰碰久久| 天天色 色综合| 毛片av在线播放| 高清免费电影在线观看| 久久精品一区二区三区四区 | 九九99久久精品在免费线bt| 在线观看日韩电影| 免费激情视频在线观看| 中文字幕在线看片| 欧美日韩在线视频观看| 东北少妇不带套对白| 色老头在线观看| 亚洲综合无码一区二区| 免费的av在线| 在线观看电影av| 亚洲欧美日韩一区| 毛片在线视频观看| 99久久精品免费看国产小宝寻花| 亚洲一区二区三区小说| 丰满的少妇愉情hd高清果冻传媒| 毛片在线导航| 香蕉av福利精品导航| 久久成人福利视频| 台湾佬中文娱乐网欧美电影| 欧美日韩中文字幕在线| 精品免费国产一区二区| 成人看片在线观看| 欧美日本国产一区| 日本黄色三级网站| 高清一区二区三区| 精品一区二区三区四区| 尤物视频最新网址| 久久亚洲专区| 久久69精品久久久久久国产越南| 久久久精品国产sm调教| 一本色道久久| 欧美有码在线视频| 中文字幕一区2区3区| 精久久久久久久久久久| 99re视频在线观看| 深夜影院在线观看| 国产精品短视频| 九九久久九九久久| 蜜桃视频在线观看免费视频| 色中色一区二区| 毛片毛片毛片毛| 一区二区三区高清在线观看| 国产视频丨精品|在线观看| 国产一区二区三区四区在线| 一区二区电影| 97超碰蝌蚪网人人做人人爽| 波多野结衣网站| 久久99精品国产91久久来源| 成人自拍视频网站| 性感美女一级片| 国产精品久线观看视频| 69sex久久精品国产麻豆| 精品国产免费人成网站| 欧美精品精品一区| 丰满少妇一区二区三区| 日韩午夜电影网| 久久人人爽人人爽人人片av高清| 无码人妻黑人中文字幕| 国产精品中文字幕一区二区三区| 国产在线资源一区| 国产鲁鲁视频在线观看免费| 亚洲精品菠萝久久久久久久| 久久国产色av免费观看| 成人精品在线| 亚洲视频在线观看视频| 久久久一二三区| 日本不卡一二三区黄网| 国产久一道中文一区| 免费av在线网址| 日韩欧美成人免费视频| gogo亚洲国模私拍人体| 日韩精品2区| 8x海外华人永久免费日韩内陆视频| 亚洲一区精品在线观看| 91在线播放网址| 国产 欧美 日本| 欧美在线一级| 亚洲色图欧美制服丝袜另类第一页| 欧美黄色免费观看| 麻豆成人av在线| 欧美高清性xxxxhd| 国产福利在线免费观看| 欧美高清视频www夜色资源网| 国产精品jizz| 午夜日韩av| 成人国产精品一区| 91在线不卡| 色88888久久久久久影院按摩| 性感美女一区二区三区| 女人香蕉久久**毛片精品| 国产免费成人av| 国产大学生校花援交在线播放 | 亚洲日韩中文字幕在线播放| 日本网站免费观看| 国产高清一区日本| 麻豆md0077饥渴少妇| 99久久精品一区二区成人| 亚洲欧美中文日韩在线| 午夜偷拍福利视频| 国产91综合一区在线观看| 男人天堂新网址| 欧美在线在线| 欧美另类69精品久久久久9999| 一级黄色免费片| 国产精品天美传媒| 中文字幕天天干| 人人狠狠综合久久亚洲婷| 国产精品嫩草影院久久久| 成人免费在线电影| 欧美影片第一页| 91狠狠综合久久久久久| 蜜臀av一区二区三区| 亚洲一区二区精品在线| 欧美激情啪啪| 久久久久999| 国产免费黄色大片| 一区二区三区日本| 国产视频精品视频| 国产美女诱惑一区二区| 欧洲亚洲一区二区| 成人精品三级| 伦理中文字幕亚洲| 丰满少妇高潮在线观看| 午夜电影网一区| www在线观看免费视频| 日本三级亚洲精品| 久久久一二三四| 91精品丝袜国产高跟在线| 91精品国产91久久久久久久久| 日韩二区三区| 欧美日韩一区成人| 欧美成欧美va| 91一区二区在线观看| 九热视频在线观看| 91tv精品福利国产在线观看| 国产精选一区二区| 国产一区二区主播在线| 久久久精品一区| 手机av免费在线观看| 在线观看不卡一区| 日本妇女毛茸茸| av在线不卡免费看| 亚洲 欧美 日韩系列| 欧美精品偷拍| 欧洲一区二区在线| 九九九九九九精品任你躁| 欧美一二三视频| 日本成a人片在线观看| 精品对白一区国产伦| 色老头在线视频| 一区二区三区久久久| 国产美女精品久久| 国产在线精品一区二区三区不卡 | 亚洲综合av影视| 大胆人体一区二区| 欧美超级乱淫片喷水| 视频午夜在线| 日韩欧美视频在线| 日本视频www色| 亚洲成在人线免费| 黄色激情小视频| av一区二区久久| 污免费在线观看| 午夜一区不卡| 国产午夜精品视频一区二区三区| 国产成人一区| 国产高清一区视频| 欧美爱爱视频| 欧美做爰性生交视频| 激情图片在线观看高清国产| 最近2019中文字幕在线高清| 神马午夜在线观看| 欧美一级理论片| 一区二区国产欧美| 在线精品视频免费观看| 国产一区二区三区影院| 亚洲精品久久7777| 精品女人久久久| 国产三级精品三级在线专区| 超碰caoprom| 国产成人午夜精品影院观看视频| 久久久精品麻豆| 麻豆久久婷婷| 国模吧无码一区二区三区| 午夜视频一区| 艳母动漫在线观看| 图片区亚洲欧美小说区| 亚洲日本理论电影| 不卡一区2区| 日韩欧美在线电影| 久久99国内| 久久亚洲免费| 香蕉视频一区| 精品一区二区国产| 国产精品调教视频| 国产日韩欧美二区| 加勒比色综合久久久久久久久 | 免费激情视频在线观看| 亚洲欧美日韩在线观看a三区 | 懂色av蜜臀av粉嫩av永久| 久久久久国产免费免费| 蜜桃av免费看| 久久精品在线免费观看| 三年中国中文观看免费播放| 久久精品亚洲乱码伦伦中文| 波多野结衣一本| wwwwww.欧美系列| 黄瓜视频污在线观看| 久久精品夜夜夜夜久久| 性欧美精品男男| 国产欧美一区二区精品性色| xxxxx99| 国产精品私人影院| 手机在线中文字幕| 亚洲精品综合在线| 国产福利久久久| 精品免费在线视频| 欧美黄色一级大片| 欧美三级在线视频| 99精品久久久久久中文字幕| 日韩欧美电影在线| 特黄aaaaaaaaa真人毛片| 精品亚洲一区二区三区在线观看| 黄视频在线播放| 少妇av一区二区三区| 超碰公开在线| 韩日欧美一区二区| 欧美二三四区| 国产在线观看91精品一区| 国产精品亚洲一区二区在线观看| 97netav| 日本在线中文字幕一区| 色播五月综合| 欧美成人日韩| 69堂免费视频| 蜜臂av日日欢夜夜爽一区| 男女视频在线观看网站| av亚洲精华国产精华精华| 亚洲精品视频久久久| 中文字幕在线一区| 国产无遮挡又黄又爽又色| 色婷婷久久久久swag精品| 亚洲资源在线播放| 精品久久久久av影院| 国产天堂在线| 欧美国产日韩一区二区| 暖暖成人免费视频| 91在线看www| 蜜桃精品wwwmitaows| 日本黄色播放器| 中国女人久久久| 中文字幕在线观看日| www.色综合.com| 乱老熟女一区二区三区| 精品人伦一区二区三区蜜桃网站| 亚洲午夜精品久久久| 日韩av网址在线| 久操视频在线观看| 2025国产精品视频| 欧美专区一区| 亚洲一卡二卡三卡四卡无卡网站在线看| 韩国欧美一区| 色91精品久久久久久久久| 99精品1区2区| 国产一级片免费观看| 欧美日韩色一区| 欧美视频综合| 国内精品在线一区| 精品亚洲a∨一区二区三区18| 欧美一区二区福利| 亚洲国产网站| 青娱乐精品在线| 国产精品污网站| 久久亚洲精品石原莉奈 | 国产91久久婷婷一区二区| 亚洲精品午夜| 亚洲一区二区自拍偷拍| 久久精品盗摄| 日本黄色动态图| 亚洲综合精品久久| 国产情侣av在线| 色偷偷av亚洲男人的天堂| 成人爽a毛片免费啪啪| 国产精品日韩一区二区三区| 亚洲精品在线观看91| 亚欧美在线观看| 国产色婷婷亚洲99精品小说| 国产成人亚洲精品自产在线| 精品国产乱码久久久久久久久| av网站在线免费| 91久久久久久久久久久| 久久要要av| 国产三级生活片| 亚洲欧洲成人精品av97| 在线视频1卡二卡三卡| 在线播放国产精品| 第四色男人最爱上成人网| 欧美lavv| 亚洲综合社区| 久久久久久亚洲中文字幕无码| 精品国产老师黑色丝袜高跟鞋| 国产自产一区二区| 久久久久久九九九| 国内毛片久久| 欧美在线一区视频| 99国产欧美另类久久久精品| 国产污污视频在线观看 | 岛国精品资源网站| 天天亚洲美女在线视频| 欧美精品久久久久久久久久丰满| 欧美在线视频免费| 国产精品欧美在线观看| 日本美女高潮视频| 中文字幕av一区 二区| 91超薄丝袜肉丝一区二区| 久久精品这里热有精品| 日本久久伊人| 欧美黑人经典片免费观看| 久久理论电影网| 中文 欧美 日韩| 久久精品久久久久久国产 免费| 国产精品欧美一区二区三区不卡| 日本一级黄视频| www.色精品| 少妇又紧又色又爽又刺激视频 | 麻豆av免费观看| 欧美日韩电影在线| 婷婷色在线播放| 久久99精品久久久久久三级| 日韩经典一区二区| 极品魔鬼身材女神啪啪精品| 亚洲成人av片| 第四色男人最爱上成人网| 公共露出暴露狂另类av| 成人高清伦理免费影院在线观看| 亚洲日本视频在线观看| 中文字幕日韩有码| www.爱久久| 天天干在线影院| 一区二区三区在线播放| 日本私人网站在线观看| 91精品国产综合久久香蕉的用户体验 | 久久久不卡网国产精品二区| 中文字幕黄色av| 久久久999国产| 神马香蕉久久| 国产精品久久久久久9999| 精品福利一区二区| 秋霞a级毛片在线看| 久久久一本精品99久久精品|