文末有彩蛋哦ca88亚洲城网站☺,那用研人士须求和开发职员逐一确认控件数据的path

前言

是因为日益显然的精细化运转须要,今日头条志愿从二零一八年开端营造大数目平台,<<无埋点数据收集SDK>>由此立项,用于向大数据平台提供全量,完整,准确的客户端数据.
  <<无埋点数量收集SDK>>Android端从开端,到经验重构,稳步周详到今后已经有快一年的时日了.时期从开源社区以及同行中赢得了一部分很有意义的技巧参考,因而在这几个SDK趋于完美的明日,大家也设想将这一块儿在技术上的钻探经验和获得分享出来.

  1. 4月16-18日,QCon都城2017满世界软件开发大会上有同事表示Android/IOS两端进行统一的技术分享,欢迎大家前去调换
  2. 我们会日渐整理一些技能小说到那些简书账号“移步端数据搜集和剖析

事先关于Android端的<<无埋点数量搜集SDK>>使用的技术,写了一篇小说<<Android
AOP之字节码插桩>>
,那一个是Android端实行任何收集的起源,大家正是用那个点子轻松得到种种"Hook"点的.
  本篇文章则接着讲一下关于收集SDK内部募集逻辑的局地重中之重技术.


版权归属于微信公众号小说天涯论坛哈勃勒Data之Android无埋点实践
文末有彩蛋哦☺

客户端埋点是多少收集的最基本手段,但由于事情迭代速度飞速,手动埋点方案纵然灵活多变,不过极大的充实了客户端开发人士的工作量。开发完结工作功效供给开销非常大的活力处理埋点事宜,而且随着迭代版本,埋点的多少会愈发多,那几个老旧埋点的保卫安全工作也须要提交一点都不小的鼎力。并且,手动埋点的没错同样是个最好考验开发人士的耐性和认真程度的题材,在所难免会现出那样那样的标题。所以,如若能够研发出一款不供给只怕很少须求开发职员参预就能兑现基于分歧工作场景埋点的效率sdk对于进步版本迭代速度和开发人士的幸福感相对是一件十三分有价值的工作。

目录

一、概述
1.1 SDK数据搜集能力现状
1.2 关键技术点概述
二 、View的绝无仅有标识(ID)
2.1 调研
2.2 利用ViewTree构建ViewID
2.3 ViewPath的生成
2.4 ViewPath的优化
叁 、页面包车型地铁撤销合并
3.1 合理划分页面包车型客车严重性
3.2 Android中的页面
3.3 页面名组成
④ 、无需埋点轻松收集定制的政工数据
4.1 配置示范
4.2 无埋点采访流程
4.3 数据路径(DataPath)
五、结语


1 背景

腾讯网HubbleData是二个着眼用户作为的数据分析系统,提供一套完整的数额化解方案。一个数一数二的数额平台,对于数据的拍卖,是由如下的伍个步骤组成的:

ca88亚洲城网站 1

里头,第①个步骤,也即数据搜集是最大旨的题材。腾讯网HubbleData扶助全端数据收集,包涵iOS、Android、JS、JAVA等几个平台。本文首要商量Android平台的数量收集方案。行业内部各家集团从不一致角度,提议了各个技术方案,那么些方案大致上能够归为三类:

(1)
代码埋点:在某些事件产生时调用SDK里面相应的接口发送埋点多少,百度总结、友盟、TalkingData、Sensors
Analytics等第1方数据计算服务商大都选拔这种方案。

  • 可取:使用者控制精准,自由地挑选如什么日期候发送数据;使用者控制精准,自由地挑选什么样时候发送数据。
  • 症结:开发及测试代价大;必要等待APP更新。

(2)
可视化埋点:通过可视化学工业具配置采集节点,在Android端自动分析配置并反馈埋点数据,从而完成所谓的全自动埋点,代表方案是现已开源的Mixpanel。

  • 优点:解松开发人士,化解了代码埋点代价大的题目;通过服务端配置埋点,消除等待APP更新的难题。
  • 缺点:覆盖效率有限,只可以配备部分集体性质;埋点只好从此时此刻无时无刻开首,无法“回溯”。

(3)
无埋点:它并不是真的的不必要埋点,而是Android端自动采集全部风云并反馈埋点数据,在后端数据测算时过滤出有用数据,代表方案是国内的GrowingIO。

  • 可取:解松手发人员,消除了代码埋点代价大的难题;化解了等待APP更新和多少“回溯”的题材;能够活动获得很多启发性的音讯。
  • 缺点:覆盖的机能有限,不可能灵活地自定义属性;给网络传输和功耗等天性带来更大的载荷。

天涯论坛哈勃勒Data的Android
SDK早已有之,公司里面诸如考拉、易信、LOFTEQashqai、美学、漫画等多款产品都已接入使用。原有Android
SDK接纳手动代码埋点的方案,首要关切的是事件模型、埋点接口、上报策略等题材。全体架构如下图所示:

ca88亚洲城网站 2

代码埋点尽管采用起来灵活,不过开发耗费较高,并且只要上线就很难修改。参考产业界先进方案并结成乐乎集团之中产品的埋点必要,和讯哈勃勒Data的Android
SDK在代码埋点全部架构的根基上增加产量了无埋点作用,本文首要针对今日头条哈勃勒Data在Android
SDK中无埋点实践进行简短分享。

更大的市场股票总值还在于,不供给开发职员参与,启动可能用研的同桌就足以每一日动态调整数据收集方案。

一、概述

本有的首先简要介绍一下大家的募集方案时下可以收集到哪边数据,然后对于本文重点介绍的八个技术点进展概述.

2 无埋点关键技术

综观方今比较成熟的无埋点方案,存在着如下难题:

1.1 SDK数据搜集能力现状

当下大家的SDK进行多少搜集时基本有多少个力量:

a. 通用数据全量收集
  通用数据指的是与事务毫不相关的用户作为数据,无论是电商应用照旧社区应用,接入SDK后通用数据的搜集上都以无差的,这个通用数据大约有:

事件 描述
冷启动事件 App第一次启动时的,版本号、设备ID、渠道、内存使用情况,磁盘使用情况等信息
前后台事件 App进入前台或者后台
页面事件 页面(Activity或Fragment)显示(Show)/隐藏(Hide)
控件点击事件 某个控件(包括页面上控件和弹窗中控件)被用户点击
列表浏览事件[可选] 某个列表的哪些条目被用户浏览了
位置事件[可选] 上报用户地理位置信息
其它事件 省略描述

b. 业务相关数据要求通过发出配置进行无埋点定制收集
  除了上述通用数据,与具体育工作作有关的多少搜集。拿今日头条贵金属的首页举个例子:

图1-1 无埋点采访工作数据示例

假如需求在用户点击上图红框区域时,把“粤贵银”那些交易品的ID(只怕下方展现的指数等,只要在内部存款和储蓄器中存在的数量都能够)一起报上来。
  对于此种需要,数据搜集SDK做到了无需埋点不依靠开发周期,通过线上下发一些布局信息,即可即时举行数量收集。具体原理第一节讲述。

2.1 View的唯一ID

标题1:通过XPath定位控件,理论上有效性,但执行注解这几个方案的复杂度万分高,尤其对于拍卖像GridView,ListView,RecyclerView的控件更是捉襟见肘。不仅如此,生成xpath的长河本人就是四个会同费用质量的一坐一起,它须求遍历view
tree,存款和储蓄相当多的途径消息到view上。

1.2最首要技术点概述

a. View的绝无仅有标识(ID),(详见本文首节)
  当大家采访控件数据时遇见的率先个难题正是:怎么着把界面上的其他二个View与其余View区分开来.

比如说:有些Button被点击了
我们在报告数据的时候必要把那些Button和其余兼具控件(比如另八个Button,另1个ImageView等)区分开来,那样那条反馈的数据才能表示"正是不行Button被点击了须臾间".

那就需求为界面上的每3个控件生成三个唯一的ID.
此ID除了有着区分性,还亟需用于一致性一致性是同多少个View无论界面布局怎么着动态变化,恐怕说数次跻身同一页面,此ID要求保证不变.

b. 页面包车型的士分割,(详见本文第三节)
  除了Activity有个别Fragment也急需用作页面,那就供给:

  • 在Fragment show/hide时申报相关页面事件.
  • 页面Fragment中爆发的用户交互事件也要求归于此Fragment页面,即点击有些View须求报告页面Fragment的音讯(从View中怎么获取Fragment消息?)

c. 无需埋点轻松收集定制的业务数据,(详见本文第二节)
  如前方所述,暗许情形下数据搜集SDK会收集全量的用户交互数据,对于定制的事体收集供给,数据收集SDK也形成了不用代码埋点,通过线上颁发一些安排实行立即收集


2.1.1 如何唯一地标识1个View?

SDK内部在机动收集控件数据时,要求将界面上的其他3个View与其余View区分开来。那就必要为界面上的每三个控件分配三个唯一的ViewID。此ViewID除了拥有区分性,还须要拥有一致性,即同2个View无论界面布局如何动态变化,只怕说多次进去同一页面,此ViewID理论上保持不变。

View中得以找到的风味音信:

  • Id: 静态整数。在编写翻译期,aapt会生成兰德酷路泽类,当中蕴藏全数能源ID。

  • Resource
    Id:开发者操作控件的唯一标识。一般由开发者在布局文件中钦点android:id,通过findViewById找到View。

  • Class
    Name:View所属的Class,例如TextView、LinearLayout、ListView、ViewPager等。

这么些特点音信中的Id借使能够利用,是足以直接用作ViewID的,不过,从aapt生成id的口径来看,区别版本相同的resource
Id对应的平头Id 是有或者不平等的,所以没有艺术使用Id来唯一标识。

Resource Id是开发者定义的View标识,对于有Resource Id
的View能够说全部了唯一标识,那么没有Resource
Id的View,我们考虑通过一个index属性来区分,index属性能够取各种控件所属父组件的index(也即各类控件是其父控件的第多少个男女),并逐级向上遍历找到根节点,最终形成三个View
帕特h即可用来唯一地方统一标准识那个View。

题材2:获取控件对应的数额是通过 data
path的不二法门缓解,每一趟添加新埋点时,假若必要汇报数据,那用研人士供给和开发人士逐一确认控件数据的path,那巨大的界定了客户端支付的自由度,固然简单的重构也会使得在此之前布置的埋点消息失效。

② 、View的唯一标识(ID)

2.1.2 ViewID构造

由此上述分析,我们取得一条View
Path:获取每一个控件本身的ID、类名、Resource
Id以及身处所属父组件的Index等个性音讯,并逐级向上遍历找到根节点。

并组成该View所在的页面新闻,大家获取ViewID的协会样式如下:

sha-256(page : path)
  • page: ActivityName
  • path:
    view在控件树中的全路径,依据如下情势实行拼接,个中index为当下view所属父组件的index,id为编写布局文件时的android:id属性值,有则拼接,且index固定为0,无则不凑合。

parent1[index]#id/parent2[index]#id/.../view[index]#id

不难示例如下:

ca88亚洲城网站 3

针对如上难点,大家经过深挖内在逻辑关系及相比优劣,总计出了一套更灵敏,更客观的无埋点方案,上边分七个部分每个介绍达成勘查衡量及其间机制。

2.1 调研

用于区分界面上每种View的ID? Android系统是不是提供给了笔者们以此ID?

当真,Android系统提供了四个ID,view.getId()即可获取二个int型的id用于区分View,可是那几个ID因为以下两个原因却并无法满意大家的供给.

  1. 有突出一部分view是NO_ID,比如在布局文件中未钦命id,恐怕直接在代码里面new出来view,view.getId()重临的全部都以NO_ID
  2. 其一ID是不平稳的,由于这个ID其实便是每一趟编写翻译发生的R文件中的int常量,由此同贰个按钮,七个本子编写翻译出来的ID很恐怕时不雷同的.

由此,大家只能协调出手构建大家的ID喽,怎么营造?答案是行使所属Page+ViewTree营造ViewID.

2.1.3 ViewID优化

设想到在骨子里布局中有大概存在一些动态插入、删除的控件,或者说控件被复用,都或者滋生View
Path的成形,从而导致ViewID不唯一。为了保证ViewID的一致性,我们从以下多少个地点动手,对ViewID举行了自然水平地优化。

壹 、定位与用户发生互动行为的靶子控件

2.2 利用ViewTree构建ViewID

在Android的概念里,每一个Window(ActivityWindow/DialogWindow/PopupWindow等)上边都生长着一棵ViewTree.而显示屏中看看的各个控件(ImageView/Button等)都以那棵ViewTree上的节点.
  有Android开发条件的校友只需求打开AndroidDeviceMonitor-dump view
hierarchy 就足以看看ViewTree的姿容,如下图:

图2-1 ViewTree概念图

所以,大家萌生出叁个设法:

使用Page+ViewTree中的地方营造ViewID.

View在ViewTree中的地点主要用两点来明显:

  • 纵向的深度
  • 横向的index

考虑那多少个因素后,大家定义三个View帕特h:

ViewPath:当前view到ViewTree根节点的一条路线,用于在ViewTree中绝无仅有定位当前view。路径中的各个节点包罗两有的音信,即节点View类型音信,以及节点View在兄弟中的index。

正如图,是三个简练的ViewTree模型(不难到深度唯有两层,每层唯有两八个控件)

图2-2 ViewTree模型图

服从在此之前给的概念,上海体育场面中央控制件1,2,3,4的ViewPath如下

控件1ViewPath: RootView/LinearLayout[0]   index为1表示此节点是兄弟节点中第一个控件
控件4ViewPath: RootView/LinearLayout[0]/ChildView1[0]
控件2ViewPath: RootView/RelativeLayout[1]
控件3ViewPath: RootView/LinearLayout[2]

上述给出的ViewPath中,每一个节点(除了第二节点)有两片段内容:

  • LinearLayout,RelativeLayout,ChildView1等ViewType信息(节点View的类型
  • “[]”内的index消息,此index提醒此节点是男士节点的第多少个

那是最初的View帕特h,用ViewPath定位view,有两点专门首要性:

  • 一致性: 同一个view的View帕特h在ViewTree的动态变化中应维持不变
  • 区分度: 分歧view的ViewPath应该例外

根据这几个最初的View帕特h定义在实践中还不可能在一致性和区分度上满足大家的供给,前面会对ViewPath进行优化。

(1) Index

ca88亚洲城网站 4

如上图所示,当页面布局产生动态变化时,比如说删除二个子view,其他子view所属父组件的index也可能会变动,为此,大家对view所属父组件的index进行改造,通过如下算法对index赋值:

  • 各种ViewGroup下的有所View作为三个数组,从0早先;

  • 每一个ViewGroup下的拥有View先遵照Class分类,然后再把各种门类中的数据依据数组的法子,从0早先;

  • 种种ViewGroup下的富有View先依据Class分类,再确认是否有Resource
    Id,如若存在,则index为0,不然index为所属Class类型数组下的序号。

该优化处理对负有View适用。优化后效果如下:即动态改变部分控件后,只会潜移默化同品种的控件,其他品种控件的index不受影响,也即ViewID不受影响。

ca88亚洲城网站 5

关于稳定交互控件,大家也考虑过xpath的方案,然则考虑到其促成的复杂度,不灵活和各样神秘的难点,大家抛开了那种方案。通过反复的开卷View的touch事件处理相关的源码,我们算是意识了缓解难点的更好的法子。

2.3 ViewPath的生成

地点我们由营造ViewID的必要引出了ViewPath的定义,那么当相互事件(例如:按钮点击)发生时,我们怎么样生成此控件的ViewPath?
  如上一篇小说<<Android
AOP之字节码插桩>>
所述,当用户点击某些按钮时,大家插入OnClickListener.OnClick方法中的如下代码将会被调用:

Monitor.onViewClick(view);    

下边,入参view即为当前被点击的view,获取此view的ViewPath伪代码如下:

  public static ViewPath getPath(View view) {
    do {
      //1. 构造ViewPath中于view对应的节点:ViewType[index]
      ViewType=view.getClass().getSimpleName();
      index=view在兄弟节点中的index;
      ViewPath节点=ViewType[index];
    }while ((view=view.getParent())instanceof View);//2. 将view指向上一级的节点
  }

结构出来的View帕特h如上边例子所示:

DecorView/LinearLayout[0]/FrameLayout[0]/ActionBarOverlayLayout[0]/ContentFrameLayout[0]/FrameLayout[0]/LinearLayout[0]/ViewPager[0]/ButtonFragment[0]/AppCompatButton[0]

(2) 可复用View

先来看2个用参加景:

ca88亚洲城网站 6

如图所示,当ListView上海滑稽剧团时,显示屏下方即将展现的<成分6>其实复用了显示屏上方即将滑出的<成分0>,相当于说<成分6>与<成分0>的index均为0,在这种情景下,我们不恐怕透过前述index的概念来分裂那多少个列表Item。

ca88亚洲城网站 7

所幸,针对那种境况,大家能够用position的取值举办区分,也正是令index =
position。

透过进行发现,产生上述复用景况的View首要有以下几类:AdapterView、RecyclerView和ViewPager,其api都提供了收获position的接口。

a. AdapterView

AdapterView的派生类均可由此getPositionForView获取position。

index = position = ((AdapterView) group).getPositionForView(child);

作为AdapterView的派生类之一,ExpandableListView因为涉及到groupPosition和childPosition,因而必要独特处理。在结构ViewID时,将能够收集到的position新闻都拉长到View
Path中,具体政策如下:

  • 先将ExpandableListView作为一般AdapterView总括position

  • 列表Item为header元素,View Path中添加[header:position]

  • 列表Item为footer成分,footer的index需求十三分总计,总结公式如下,View
    Path中添加[footer:footerIndex]

    // Calculates the footer index among footers; 
    // For instance, there are five footers, so the footer index ranges from zero to four.
    // The first footer index is zero.
    footerIndex = position - (expandableListView.getCount() - expandableListView.getFooterViewsCount());
    
  • 列表Item为组成分,View Path中添加[group:groupPosition]

  • 列表Item为组内成分,View
    Path中添加[group:groupPosition,child:childPosition]

涉及到的api接口如下:

((AdapterView) expandableListView).getPositionForView();
public long getExpandableListPosition(int flatListPosition);
public static int getPackedPositionType(long packedPosition);
public static int getPackedPositionGroup(long packedPosition);
public static int getPackedPositionChild(long packedPosition);

以身作则如下:

ca88亚洲城网站 8

b. V7-RecyclerView

RecyclerView的景观比较简单,可经过调用getChildPositiongetChildAdapterPosition获取position。

@Deprecated
public int getChildPosition(View child);

public int getChildAdapterPosition(View child);

c. V4 – ViewPager

V4 – ViewPager可通过调用getCurrentItem获取position。

public int getCurrentItem();

ViewGroup中有3个TouchTarget 类型的变量
mFirstTouchTarget,表示费用当前触摸事件的控件列表。例如,点击荧屏上1个按钮,那么按钮所在ViewGroup的mFirstTouchTarget
变量就针对那么些按钮。当ViewGroup派发触摸事件时,他会率先判断变量mFirstTouchTarget是或不是留存,如若变量存在,会循环遍历TouchTarget链表成分,找到能处理该事件的View并将Motion伊夫nt
派发给该View。尽管不存在TouchTarget,ViewGroup 会循环遍历全部child
view,直到找到二个能处理该事件的View,并将该View作为first touch target
赋值给mFirstTouchTarget。

2.4 ViewPath的优化

a. 一致性优化1
情景:

在图2-2
ViewTree模型图中,若是像下边图中所示,在控件2和3中动态插入三个FrameLayout呢?

图2-3 Android界面动态性别变化化情景1

这会儿根据原始ViewPath的定义,咱们来探望控件3的ViewPath发生了如何变化?

ViewTree动态变化前: RootView/LinearLayout[2]
ViewTree动态变化后: RootView/LinearLayout[3]

优化:

ViewPath节点中index的意思从“兄弟节点的第多少个”优化为:“相同档次兄弟节点的第多少个”

优化后,爆发图2-3所示界面布局动态变化时,控件3的ViewPath变化为:

ViewTree动态变化前: RootView/LinearLayout[1]   index为1表示此节点是兄弟节点中第二个LinearLayout
ViewTree动态变化后: RootView/LinearLayout[1]

可以见见,此处优化使控件3的ViewPath在ViewTree动态插入除了LinearLayout之外其他任何项目时都保持前后一致。

b. 一致性优化2
情景:

在图2-2
ViewTree模型图中,借使像上面图中所示,在控件2和3中动态插入3个LinearLayout时,控件3的ViewPath能无法持续保障前后一致?

规行矩步上述场景,控件3ViewPath的转移如下:

ViewTree动态变化前: RootView/LinearLayout[1]   index为1表示此节点是兄弟节点中第二个LinearLayout
ViewTree动态变化后: RootView/LinearLayout[2]   前面插入一个LinearLayout导致此节点变为兄弟节点中第三个LinearLayout了

问题
上述场景指的莫过于是2个题材:ViewTree中同类别兄弟节点动态变化(插入/移除/移位)影响ViewPath的一致性

  • ViewPath节点中的index,在同类别(ViewType相同,例如都以LinearLayout)兄弟节点动态参与/删除时,当前节点的index不可能在扭转前后保持一致。
  • “一致性优化1”中的优化能够抵抗不一致品类兄弟节点的熏陶,却对同类型兄弟节点的震慑无可奈何

从ViewPath的定义上麻烦找到在同体系兄弟节点动态变化前后保持一致的措施,但我们得以分析发生此种界面动态变化的现象:

  1. 选取Fragment的动态布局
      Android界面包车型大巴动态布局产生景况中,使用Fragment实现界面动态变化的效用和震慑控件数量依旧比较大的(相对于直接addView())
  2. ListView(等可复用View)中同类型的itemViews。
      此种情状尽管并未生出在叁个itemView前动态插入3个itemView,可是由于itemView的复用,导致itemView显示的内容和在父节点listView内的index的相应关系动态变化,因而也归入此类。

第22中学所说“ListView等可复用View”造成的难题背后会有优化,此处针对第11中学的情景研究。第11中学场景产生时如下图:

图2-4 使用Fragment造成界面动态性的场景

上图中FragmentA,FragmentB,FragmentC的顶层视图控件全体是LinearLayout同类型),此时那两个Fragment参加的一一将招致ViewPath在此处各样差别,从而造成ViewPath在动态变化前后不可能保持一致(如前方:ViewTree动态变化前后控件3ViewPath的变动所示)。
优化:

在View帕特h节点中,使用Fragment的名字替换ViewType

优化后,产生图2-4所示界面布局动态变化时,控件3的ViewPath变化为:

ViewTree动态变化前: RootView/FragmentB[0]   index为0表示此节点是兄弟节点中第一个FragmentB
ViewTree动态变化后: RootView/FragmentB[0]  

如上,此次优化使得,在顶层视图ViewType相同的Fragment动态拉长/删除到ViewTree时,ViewPath在扭转前后保持一致。

c. 针对可复用View的优化
情景
  以最常使用的ListView为例,假诺有一ListView满屏只展现三个条款,那么此ListView或然唯有三个子控件(ItemView),而此ListView上海滑稽剧团之后能够显示100项内容
  那三个ItemView与100项内容是一对多的附和关系,而且映射并无可信赖规律。
  此时,大家期望ViewPath能够区分那100项突显的始末条目,而非仅仅区分2个ItemView

上边情景中的难点可用下图表明:

图2-5 可复用View的ViewPath区分性优化

如上海体育场合中,内容条目1和4都以用itemView1来显现的,依照事先的ViewPath定义,图2-5中各种内容条目标ViewPath如下:

内容条目1: ListView/ItemView[0]   index为0表示此节点是兄弟节点中第一个ItemView
内容条目4: ListView/ItemView[0]   
内容条目2: ListView/ItemView[1]  
内容条目3: ListView/ItemView[2]  

能够看出内容条目1和4的ViewPath区分不开。此种难题得以总结为:

展示内容与ViewTree中的控件不是逐一对应的状态导致基于ViewTree的ViewPath区分度不够

  • 可复用View,比如:ListView,RecyclerView,Spinner等,显示出来子View的多少和实际子View的多少未必相同
  • ViewPager设置缓存页面数为1,第一页呈现时,第一个页面顶尖View其实是ViewPager的第二个ChildView。此种情形也会促成展现内容(第②页)与ViewTree中的控件(第一个ChildView)不对应的意况。

所以大家对于View帕特h作如下优化:

ViewPath节点的index取内容的第几项,而非第几个ItemView。

优化:
优化后图2-5中相继内容条指标ViewPath如下:

内容条目1: ListView/ItemView[0]   index为0表示此节点是ListView显示的第一个内容条目
内容条目4: ListView/ItemView[3]   
内容条目2: ListView/ItemView[1]  
内容条目3: ListView/ItemView[2]  

可见,此前ViewPath不大概区分的始末条目1和4未来能够区分别了。各个可复用View取内容的第几项的代码方法如下:

ListView,Spinner等AdapterView------------ListView.getPositionForView(itemView)
RecyclerView------------------------------------RecyclerView.getChildAdapterPosition(itemView)
ViewPager----------------------------------------ViewPager.getCurrentItem()

d. ViewPath起源优化
  ViewPath从ContentView为起点,而非DecorView

  • DecorView : Window上的根视图,ViewTree中的根,最顶层视图
  • ContentView:
    客户端程序员定义的富有视图的父节点,如Actvity中普遍的setContentView(view)

二个事实上中的ViewPath如下:

DecorView/LinearLayout[0]/FrameLayout[0]/ActionBarOverlayLayout[0]/ContentFrameLayout[0]/FrameLayout[0]/LinearLayout[0]/ViewPager[0]/ButtonFragment[0]/AppCompatButton[0]

上面的“ContentFrameLayout[0]”以此节点代表的正是ContentView,程序员在xml大概代码里面构建的View都在ContentView中。

从DecorView到“ContentFrameLayout[0]”的这一段Path是Android系统Framework层决定的,理论上应有是同等的,不过由于碎片化等原因或然ViewPath的这一段发生变化.在实践中,大家也意识确实有局地Rom产生了此类情形,不过比率十分的小.
  为了挡住那种恐怕引致同1个View在分化装备上发出ViewPath区其余情况,ViewPath的源点定义在ContentView相比好.如上面的ViewPath可优化为:

ContentView/FrameLayout[0]/LinearLayout[0]/ViewPager[0]/ButtonFragment[0]/AppCompatButton[0]#mybutton

做法:
  构造每贰个ViewPath节点时得以取view.getId(),看看id的packageId部分是还是不是系统的(系统能源id以16进制的0x01,0x00上马),如若是,生成ViewPath时屏蔽那段即可.


(3) Fragment节点

主流App的主页均是使用如图所示的Tab切换Fragment的筹划。在那种气象下,假若主页内嵌的Fragment选取“懒加载”方案,则尾部Tab的点击顺序决定了该Tab对应Fragment的先河化顺序,从而致使Fragment所属父组件的index动态变化。

ca88亚洲城网站 9

也便是说,Fragment初叶化顺序影响ViewID。而前述Index优化方案并不能消除这一标题。

当用户触发Down事件时,会实行如下逻辑,寻找消费当前事件的TouchTarget。

叁 、页面包车型地铁撤销合并

Fragment节点特殊处理

本着Fragment开始化顺序影响ViewID的题材,我们运用的解决方案是:

要是能够收获到Fragment实例的类名,则采用Fragment实例的类名替换View
Path中的Fragment,并设置[index]为优秀标记[-]。例如:使用控件篇Tab对应的Fragment实例ControlSetFragment以及特种标记[-]替换原View
Path中的Fragment[3]

ca88亚洲城网站 10

if (actionMasked == MotionEvent.ACTION_DOWN){
    //如果是down事件,遍历child,找到TouchTarget
    ..
    ..
    final View[] children = mChildren;
    for (int i = childrenCount - 1; i >= 0; i--) {
       final int childIndex = getAndVerifyPreorderedIndexchildrenCount, i, customOrder);
       final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);
       ..
       ..
       if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
          // child 消费了触摸事件
          ..
          ..
          // 根据消费了触摸事件的View创建TouchTarget
           newTouchTarget = addTouchTarget(child, idBitsToAssign);
          ..
          ..
          break;
      }
}

3.1 合理划分页面包车型大巴关键

页面在Android中对应于Activity和部分Fragment(比如很多app首页多tab的设计,若每一种tab是行使Fragment达成的,那么那种tab一般作为壹个页面).页面包车型客车剪切很重点,因为两点:

  1. 对于页面,须求取得Show/Hide五个机会,在那儿机上报页面Show/Hide事件,非页面则不要求
  2. 页面包车型地铁分割关系着用户交互事件的所属,例如,按钮点击事件反映格式如下:
事件名称 所属页面 ViewPath 其他属性
ButtonClicked MainActivity XXX 省略

报表中的"所属页面"意味着这次按钮点击事件发生在MainActivity中.将竞相事件归属于页面这样对前面无论是进行路径分析还是计算量控制件点击量分布都有很大的好处.

哪些获取Fragment实例?

应用代码埋点或继续即将讲到的插件埋点,在Fragment各实例类中重载上面包车型大巴多少个措施,并在各艺术中插入SDK提供的法门调用,从而完成Fragment生命周期监听:

@Override
public void onResume() {
    super.onResume();
    DATracker.getInstance().onFragmentResume(this);
}

@Override
public void onPause() {
    super.onPause();
    DATracker.getInstance().onFragmentPause(this);
}

@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
    super.setUserVisibleHint(isVisibleToUser);
    DATracker.getInstance().setFragmentUserVisibleHint(this, isVisibleToUser);
}

@Override
public void onHiddenChanged(boolean hidden) {
    super.onHiddenChanged(hidden);
    DATracker.getInstance().onFragmentHiddenChanged(this, hidden);
}

透过上述调用,当Fragment生命周期变化时,SDK能够记录当前活蹦乱跳的兼具Fragment。当某些活跃的Fragment上的控件被点击了,SDK构造该控件的ViewID时,会活动将该Fragment实例的类名写入View
Path。

当触发Down事件同时找到TouchTarget,或然触发非Down事件时,执行如下处理逻辑。

3.2 Android中的页面

Android中不乏先例必要当作页面包车型客车有Activity和Fragment(对于像全屏Dialog或许全屏的View暂不考虑).对于Activity,上节中涉嫌的两点都很不难办到.

a. Activity页面

  1. 从Application.ActivityLifecycleCallbacks的onActivityResumed/onActivityPaused那四个回调方法就能够独家取得Activity页面Show/Hide的时机,并在那时机上报相应页面事件
  2. 相互之间归属的Activity页面能够因此Context轻松取得,例如上篇作品<<Android
    AOP之字节码插桩>>
    关联,当按钮点击时,会触发我们插桩的代码:

Monitor.onViewClick(view)

入参view即为大家点击的view,通过view.getContext()大家一般就能够拿走此View所属的Activity,伪代码如下:

//从View中利用context获取所属Activity的名字
public static String getActivityName(View view) {
    Context context = view.getContext();
    if (context instanceof Activity) {
      //context本身是Activity的实例
      return context.getClass().getSimpleName().;
    } else if (context instanceof ContextWrapper) {
      //Activity有可能被系统"装饰",看看context.base是不是Activity
      Activity activity = getActivityFromContextWrapper((ContextWrapper) context);
      if (activity != null) {
        return activity.getClass().getSimpleName();
      } else {
        //如果从view.getContext()拿不到Activity的信息(比如view的context是Application),则返回当前栈顶Activity的名字
        return currentActivityName;
      }
    }
    return "";
  }

b. fragment页面
  相对于Activity,将一些Fragment看作页面包车型客车逻辑就要有点复杂一些了.那之中涉及下边多少个难题:

  • 怎么Fragment能够须求当作页面?
      这是内需人工决策的,机器做不了那么些决定.
      近期大家这厮工干预是付诸用户钻探组织,全体Fragment截图等消息均显得在平台上,由用研同事选取需求用作页面包车型客车那二个,用研选用的结果将自动化配置到SDK中
  • 怎么获得Fragment页面包车型客车Show/Hide页面事件?
      由于fragment使用情况比较多种,单单依靠OnResume/OnPause多少个回调表示fragment
    Show/Hide是不规范的,比如:
    场景一
      首页多少个Activity承载多少个Fragment
    Tab的意况,此时tab间切换并不会触发Fragment的OnResume/OnPause.接触的回调函数是onHiddenChanged(boolean
    hidden)

    场景二:
      3个ViewPager承载八个页面包车型地铁Fragment时
        a.当第6个Fragment1彰显时,纵然第一个Fragment2那时候从未显示,不过Fragment2的OnResume却以及实施,处于resumed的状态.
        b.ViewPager页面切换OnResume/OnPause/onHiddenChanged均未触及,触发的回调是setUserVisibleHint
      那时判断Fragment Show/Hide应该用setUserVisibleHint,而非OnResume/OnPause
      如前一篇文章XXX,所述,大家通过插桩的办法Hook到了fragment的如下生命周期函数用于包装成为Show/Hide事件:

onResume()
onPause()
onHiddenChanged(boolean hidden)
setUserVisibleHint(boolean isVisibleToUser)

使用那多少个回调包装成适用于各样气象的FragmentShow/Hide事件的伪代码如下:

//此回调发生,则证明是场景一中使用情景,
  onHiddenChanged(boolean hidden) {
    hidden == true ------FragmentShow
    hidden == false------FragmentHide
  }
//场景二中ViewPager页面切换时触发Fragment的此回调,
  setUserVisibleHint(boolean isVisibleToUser) {
    if (fragment.isResumed()) {//只有resumed状态的fragment适用此情景
      isVisibleToUser == true ------FragmentShow
      isVisibleToUser == false------FragmentHide
    }
  }
//上述使用情景之外的一般场景
  OnResume/OnPause{
 //fragment没有被hide,并且UserVisibleHint为可见的情景
    if (!fragment.isHidden() && fragment.getUserVisibleHint()) {
      OnResume ------ FragmentShow
      OnPause  ------ FragmentHide
    }
  }
  • 如何将Fragment内部的互相之间归属到Fragment页面,也正是说怎么样在互相产生时从view实例获得Fragment页面包车型大巴名字(像此前得到Activity页面名字如出一辙)?
      view能够经过context拿到Activity的新闻,不过却尚未路子得到fragment的引用。那么,当某些View交互产生,大家又要求获得Fragment页面名字的情事下,大家只可以优先将Fragment页面名写入此View的习性中。
      做法大概如下:
        a.
    遵照前一篇小说xxx里面的办法,在Fragment.OnCreateView方法的最后插桩,获得return的view(即为此Fragment的顶层视图)
        b. 判断此Fragment是或不是被钦命为Fragment页面,倘使是,下一步
        c.遍历以Fragment的顶层视图为根节点的ViewTree,
    Fragment名设置到此ViewTree的每三个view上。设置格局如下所示:

view.setTag(0xff000001, fragmentName);

注意:View类有八个名为setTag的艺术

public void setTag(final Object tag)

此方式,类内部用一Object对象存款和储蓄tag,protected Object mTag =
null;。listAdapter中常用于安装holder。大家那里用的不是其一,不会于此用法争论

public void setTag(int key, final Object tag)

此办法,类内部有一稀疏数组存款和储蓄tag,private SparseArray<Object>
mKeyedTags;
  tag的key官方推荐能源id,因而我们能够选择类似0xff000001之类的app用不到的资源id进行tag存款和储蓄以防止争执
    d. 当需求动用Fragment名时,如下调用即可获得:

view.getTag(0xff000001)
V4 – ViewPager内嵌Fragment

那边要证实的是,ViewPager内嵌的View不仅是可复用的,同时,由于其“懒加载”、“预加载”机制,其内嵌View的加载顺序也是动态的。尤其地,当ViewPager内嵌Fragment时,依据前述对Fragment节点的拍卖,我们会使用Fragment实例的类名替换View
Path中的Fragment,并安装[index]为独特标记[-]。之所以将[index]设置为新鲜标记[-],是因为Fragment动态加载导致index不可靠,而ViewPager中内嵌的Fragment却得以调用ViewPager的getCurrentItem得到position作为index,那种情状下,是足以将index的值添加到View
Path中的。

ca88亚洲城网站 11

if (mFirstTouchTarget == null) {
    // No touch targets so treat this as an ordinary view.
    handled = dispatchTransformedTouchEvent(ev, canceled, null,TouchTarget.ALL_POINTER_IDS);
} else {
    //Down事件发生时找到TouchTarget,或者非Down事件直接执行如下逻辑

    // 将事件派发给TouchTarget表示的View
    TouchTarget predecessor = null;
    TouchTarget target = mFirstTouchTarget;

    while (target != null) {
        final TouchTarget next = target.next;

        if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
            handled = true;
        } else {
            final boolean cancelChild = resetCancelNextUpFlag(target.child)|| intercepted;

            if (dispatchTransformedTouchEvent(ev, cancelChild,target.child,target.pointerIdBits)) {
               //指定TouchTarget对应的View正确消费了事件
                handled = true;
             }
             ..
             ..
         }
     ..
     ..
     }
}

3.3 页面名组成

面前讲了将相互事件(比如点击事件)归属到某2个页面的法子是:

在相互事件中安装一个字段,值为页面名称。

页面能够是Activity可能Activity承载的Fragment,我们的页面名称组成如下:

Activity类名[Activity别名][Fragment类名][Fragment别名]

证实如下:

  1. “[]”内的组成都部队分是可选的,或许有或然没有。其余,各样组成都部队分之间有分隔符分割。
  2. 页面名组成人中学,Activity的讲述(类名/别名)是首先层,Fragment的描述(类名/别名)是第贰层
  3. 别名的产出是为着消除独自注重类名不能精确区分页面包车型地铁少数景况,比如:
    在有些电商应用中,“商品详情页”(同3个Activity)用于体现各样货品(iphone,TV等),如若供给把“分化商品的商品详情页“区分成分化页面来总括pv等目的的话,必要安装别名,如:

商品详情页#iphone
商品详情页#电视

对此别名的安装,供给程序员在工作代码里面(如Activity.OnCreate,Fragment.onCreate等)显式设置.


2.2 无埋点完成

因而前述方案,大家能够运用ViewID唯一地方统一标准识显示屏上的控件。那么,比如三个Button,当这么些Button被点击了,SDK又是何许捕捉到这点击事件,并且得到Button实例的啊,约等于怎么着促成自动埋点的呢?这里,大家提供了二种方案。

提示:由于消费触摸事件的控件只怕为多个(splitting touch
events),所以须要遍历TouchTarget链表。引用官方原文:
This behavior is enabled by default for applications that target an SDK
version of 11 (Honeycomb) or newer. On earlier platform versions this
feature was not supported and this method is a no-op.

四 、无需埋点轻松收集定制的作业数据

2.2.1 代理监听

MotionEvents may be split and dispatched to different child views
depending on where each pointer initially went down. This allows for
user interactions such as scrolling two panes of content independently,
chording of buttons, and performing independent gestures on different
pieces of content.

4.1 配置示范

事先涉嫌过,数据收集SDK能够因此配备下发即时募集定制的数码,那么在Android端那么些是如何做到的吗?
第③,看一下下发的安顿样例:

//第一部分:描述
PageName:MainActivity
ViewPath:DecorView/.../ViewPager[0]/ButtonFragment[0]/AppCompatButton[0]
EventType:ViewClick
//第二部分:数据路径(当描述符合时,按照此路径取数据)
DataPath:this.context.demoList[5]

地点例子翻译成数据供给正是:

1. 当页面(MainActivity)
2. 中的控件(DecorView/.../ViewPager[0]/ButtonFragment[0]/AppCompatButton[0])
3. 发生点击事件(ViewClick)时
4. 按照路径(this.context.demoList[5])取出数据
5. 并附加到点击事件上面一起上报

依据这几个描述,我们还足以描述如下等等各个数据须求:

当(某页面)发生事件(Show)时,按照路径(xxx)取出数据,并附加到页面Show事件上面一起上报

小结下描述的组成部分,如下:

第一层 第二层 含义
描述部分 页面 限定页面
ViewPath 限定按钮
EventType 限定时机(点击/前台/PageShow)
数据路径 一种DSL,指示目标数据在内存中的位置(可理解为“引用路径”)

原理

在应用程序中,帮忙成效事件是用户与可视界面组件交互的消息。那么些新闻是由匡助功能服务处理。协助成效服务应用在那些事件中的消息产生附加的上报和提醒。Android
4.0(API14)及更高版本上,帮助效率方法属于View类的一有个别,也是View.AccessibilityDelegate的一有的。个中可用来落到实处无埋点的法门如下:

sendAccessibilityEvent()

当用户在贰个视图上操作时调用此措施。事件遵照用户操作类型分类,涵盖以下事件类型:

  • TYPE_VIEW_CLICKED
  • TYPE_VIEW_LONG_CLICKED
  • TYPE_VIEW_FOCUSED
  • TYPE_VIEW_SELECTED
  • TYPE_VIEW_HOVER_ENTER
  • TYPE_VIEW_SCROLLED
  • TYPE_VIEW_TEXT_CHANGED

动用支持效用事件完结无埋点,简单的说,就是给View设置AccessibilityDelegate,当View爆发了click,long_click等事件时,会在响应原有的Listener方法后,发送新闻给AccessibilityDelegate,然后在sendAccessibility伊夫nt方法下搜集自动埋点事件。

        private class TrackingAccessibilityDelegate extends View.AccessibilityDelegate {

            public TrackingAccessibilityDelegate(ViewNode viewNode, View.AccessibilityDelegate realDelegate) {
                mViewNode = viewNode;
                mRealDelegate = realDelegate;
            }

            public View.AccessibilityDelegate getRealDelegate() {
                return mRealDelegate;
            }

            @Override
            public void sendAccessibilityEvent(View host, int eventType) {
                if (eventType == mEventType && host == mViewNode.getView()) {
                        ...
                        // 自动埋点
                    fireEvent(mViewNode, type);// sends tracking data
                }

                    // 响应原AccessibilityDelegate
                if (null != mRealDelegate) {
                    mRealDelegate.sendAccessibilityEvent(host, eventType);
                }
            }

            private View.AccessibilityDelegate mRealDelegate;
            private ViewNode mViewNode;
        }

利用ViewGroup的那种事件处理机制,大家因此在Activity的window上调用window.setCallback()
接管窗口的轩然大波派发,并在dispatchTouch伊芙nt处理函数中添加analyzeMotionEvent()方法。假使接受到up事件,执行拍卖逻辑,通过ViewGroup
TouchTarget链表,找到这次交互行为的靶子控件。得到控件后,通过
Activity的类名+控件所在的layout文件名+控件id对应的财富名,大家就足以显然指标控件的绝无仅有标识。

4.2 无埋点采访流程

上节呈现了用来无埋点定制业务数据收集的配备,那么SDK收到这么的一份配置哪些最后把想要的多少收集上来吗?

  • 步骤一:发出原始事件。比如点击时采访,当点击时会触发我们插桩的代码,并扭转原始的点击事件

Monitor.onViewClick(view)
  • 步骤二:万分配置
    在onViewClick方法中十分下发的安顿消息,看看Page,ViewPath是还是不是与最近view匹配,伊夫ntType是还是不是与眼下风云类型匹配,若匹配则进行下一步
    注:ViewPath的匹配能够有可信匹配和模糊匹配,精确匹配时一个ViewPath精确匹配唯一多个控件.模糊匹配时一个ViewPath可卓殊三个控件,例如能够用用一个ViewPath模糊匹配二个列表中的全体条目.
  • 手续三:遵照数据路径(DataPath)逐级反射获得对象数据,并将找到的数目附在原始的点击事件上进展上报。

设置代理的空子

兑现Application.ActivityLifecycleCallbacks,用来监听Activity生命周期,当监听到有些Activity进入onResumed状态时,通过以下方法赢得RootView:

mViewRoot = this.mActivity.getWindow().getDecorView().getRootView()

从RootView出发深度优先遍历控件树,为满意特定条件的View设置代理监听。

dispatchTouch伊夫nt源码如下:

4.3 数据路径(DataPath)

上述手续三展开数据搜集主假如根据DataPath的描述进行(例如示例中涉及的"this.context.demoList[5]"),DataPath是一种大家用来收集定制数据而定义的一种DSL.含义如下:

a. 含义

DataPath:
指向要采访的靶子数据的一条引用路径,解析此路径并逐级反射最后得到指标数据.

DataPath写法中的一些至关心重视要字(符):

关键字(符) 含义
. 表示对象所属关系,如:a.b 表示实例a中的字段b
.() 表示公有方法调用,如:a.b() 表示调用实例a中的方法b.注意:方法入参可以是DataPath指向的Object
[] 数组/线性表的index. 注意:此index可以是常量数字,也可以是一个DataPath指向的数字
this DataPath字符串的起点,表示起点为当前实例(当前View)
item DataPath字符串的起点,表示起点为当前View父节点中AdapterView adapter中当前条目. 常用于列表中的数据获取
parent DataPath节点中的关键字,用于表示当前view的parentView.效果同view.getParent(),使用此关键字可减少视图引用中的反射
childAt(x) DataPath节点中的关键字,用于表示当前view的第x个childView.效果同view.getChildAt(x),使用此关键字可减少视图引用中的反射

b. 应用示范
  上面用七个例子表达什么从DataPath找到对象数据.

图4-1 DataPath示例

演示1:列表数据得到
  上海体育场地中显得是3个列表,红框中是列表的第③个条目.那么,若是我们想要在列表中条目点击时,将列表显示的交易品ID(只怕合作方ID)等不在界面上呈现而又存在于内部存款和储蓄器中的数量跟随点击事件反映.此处Data帕特h该怎么写?

item.productId

DataPath解释:

  1. 起源定为"item",则意味未来ListView(或然RecylerView)绑定的Adapter中当前数量item为起源取数据.
    假设此ListView绑定的Adapter如下:

public class DemoAdapter extends BaseAdapter {
  private ArrayList<DataItem> mDataItems;
  ......
}

则此处”item”代表的正是mDataItems[x] (x代表近日被点击条指标itemId)

2."productId"是model类DataItem中代表"交易品ID"的字段名称.

经过Data帕特h获取数据:

  1. 当第x条目被点击时,假若发现有协作的陈设,对于起源为”item”的DataPath,先经过view.getParent找到上层ListView实例,然后通过listView.getAdapter()获得绑定的Adapter实例,最终经过Adapter.getItem(ListView.getPositionForView(itemView))拿到数码中第x个item,即mDataItems[x]
  2. 反射获取mDataItems[x]中的productId字段,即可取得第x个条目标"交易品ID",将此ID跟随第x条目标点击事件进展反馈即可.

实例2:界面数据得到
  同样时图4-1所示,参与大家想在列表中条目点击时,将条目中展现的”最新价”跟随点击事件反映.此处Data帕特h该怎么写?
  红框所示ViewTree子树如下:

图4-2 列表Item ViewTree子树结构

如上海体育场所,选中部分是列表的ItemView(RelativeLayout),可知”最新价”是由index为2的TextView所体现,因此可得,列表中条目点击获取”最新价”数据的DataPath如下:

this.childAt(2).mText

DataPath解释:

  1. 起源为"this",表示近年来被点击的view实例(图4-第22中学被选中的RelativeLayout)
  2. “childAt(2)”表示RelativeLayout.getChildAt(2),得到图4-2中index为2的TextView
  3. “mText”
    表示取出步骤2中拿走TextView实例的mText字段(TextView控件突显的文字内容存款和储蓄在mText字段内)
  4. 将取出的界面上海展览中心示的”最新价”数据增进到原来点击事件中,一起上报.

c. DataPath注意事项:
1.混淆.
  由于DataPath本质上讲述的时内部存款和储蓄器中的"引用路径",并且依照DataPath取数据时用了反光的点子,因而DataPath应该描述的是混淆之后的"引用路径".
  固然DataPath大概碰到混淆的熏陶,不过

* 用于存储数据的model类通常是不被混淆的.如我们之前的item关键字直接将起点设置为列表条目的model类对象,不受混淆影响.
* 通过关键字parent/childAt(x)可以在视图的引用中不受混淆影响
* 接口的方法通常不受混淆影响.因此在DataPath中多用接口方法调用

为此支付在布署Data帕特h时应尽或然用上述不被歪曲影响的字段及艺术.但是,如若实在采用了模糊过的字段怎么做.我们的方案是:

多少报告警方

例如版本1上配备的DataPath
“a.b”,在升级新版本2后不再适用,则新版本2遵照"a.b"收集时将采集不到,发生报告警方音讯到后台.后台接受大批量此种音信会提醒开发为新本子配置适用新本子的DataPath.

2.代码变化造成引用路径变化,从而导致此前安插的DataPath失效.
  与代码中埋点相比,线上配置举办征集数据与代码的浮动是互为的,无关的.这就有或者造成原有代码修改导致DataPath失效.其实固然客户端架构划设想计合理,效率迭代越多是在进展代码的壮大,而非修改,那种导致DataPath失效的情形相应会大大下落的.
  不过无论怎么着:

计划的DataPath摆脱不了与版本的相关性

对此此种难题大家照样是由此后边提到的"数据报警"实行监督及制止的.


界面动态变化如何是好?

落到实处ViewTreeObserver.OnGlobalLayoutListener,用来监听界面变化。当监听到界面变化时,重新遍历控件树,为满足特定条件的View设置代理监听,已经设置过代理的View不再重复设置。

界面包车型地铁监测操作供给放在界面主线程中,初步我们担心那样会对使用自己的界面交互发生震慑,所幸,经超过实际际测试,那样实现是卓有成效的,界面交互感知不到此外影响。

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (!AutoPointer.isAutoPointEnable()) {
            return super.dispatchTouchEvent(ev);
        }

        int actionMasked = ev.getActionMasked();

        if (actionMasked != MotionEvent.ACTION_UP) {
            return super.dispatchTouchEvent(ev);
        }

        long t = System.currentTimeMillis();
        analyzeMotionEvent();

        //非线上版本,打印执行时间
        if (!AutoPointer.isOnlineEnv()) {
            long time = System.currentTimeMillis() - t;
            DDLogger.d(TAG, String.format(Locale.CHINA, "处理时间:%d 毫秒", time));
        }

        return super.dispatchTouchEvent(ev);
    }

五、结语

综上,本文介绍了数据搜集逻辑中3个相比较重要的点(ViewID/Page/Data帕特h),结合上一篇小说的(AOP原理),Android端无埋点数据搜集技术上相比较重庆大学的点都以计算达成.
  当然完结SDK进程中面临过很多相比较好玩的技能难题,后续也会陆续展开整理.

监督哪些View?

  • AutoCompleteTextView(搜索框)

    增加 Text沃特cher 监听文本变化,2s 后延时发送文书输入结果

  • AbsListView(列表)

    OnItemClickListener 存在 –
    对原来OnItemClickListener作一层包装,在响应原有的Listener方法后,搜集自动埋点事件。

  • 一般View

    hasOnClickListeners 或 isClickable 返回 true –
    设置AccessibilityDelegate

analyzeMotion伊芙nt源码如下:

2.2.2 gradle插件

    /**
     * 分析用户的点击行为
     */
    private void analyzeMotionEvent() {
        if (mViewRef == null || mViewRef.get() == null) {
            DDLogger.e(TAG, "window is null");
            return;
        }

        ViewGroup decorView = (ViewGroup) mViewRef.get();
        int content_id = android.R.id.content;
        ViewGroup content = (ViewGroup) decorView.findViewById(content_id);
        if (content == null) {
            content = decorView; //对于非Activity DecorView 的情况处理
        }

        Pair<View, Object> targets = findActionTargets(content);
        if (targets == null) {
            DDLogger.e(TAG, "has no action targets!!!");
            return;
        }

        //发送任务在单线程池中
        int hashcode = targets.first.hashCode();
        if (mIgnoreViews.contains(hashcode)) return;

        PointerExecutor.getHandler().post(PointPostAction.create(targets.first, targets.second));
    }

原理

试想一下我们代码埋点的经过:首先定位到事件响应函数,例如Button的onClick函数,然后在该事件响应函数中调用SDK数据收集接口。下边,大家介绍使用gradle插件自动在对象响应函数中插入SDK数据搜集代码,达到机关埋点的目标。

我们的gradle插件选取 Android gradle 插件提供的流行的Transform
API,在Apk编写翻译环节中、class打包成dex在此之前,插入了中间环节,调用 ASM
API对class文件的字节码进行围观,当扫描到对象事件响应函数时,在函数底部或底部插入SDK数据收集代码。

ca88亚洲城网站 12

② 、获取与指标控件对应的工作数据

监察哪些View?

我们在对象View的风云响应函数中插入SDK数据搜集代码,即可兑现对该类型View的监督。例如,在Button的点击事件响应函数onClick中插入SDK数据收集代码后,当Button被点击,便会执行到onClick中的SDK数据征集代码,从而完结Button点击事件的自动采集。

对象事件响应函数(方法):

  • onClick(Landroid/view/View;)V
  • onClick(Landroid/content/DialogInterface;I)V
  • onItemClick(Landroid/widget/AdapterView;Landroid/view/View;IJ)V
  • onItemSelected(Landroid/widget/AdapterView;Landroid/view/View;IJ)V
  • onGroupClick(Landroid/widget/ExpandableListView;Landroid/view/View;IJ)Z
  • onChildClick(Landroid/widget/ExpandableListView;Landroid/view/View;IIJ)Z
  • onRatingChanged(Landroid/widget/RatingBar;FZ)V
  • onStopTrackingTouch(Landroid/widget/SeekBar;)V
  • onCheckedChanged(Landroid/widget/CompoundButton;Z)V
  • onCheckedChanged(Landroid/widget/RadioGroup;I)V

实际落到实处:

  • 对app中钦定包实行围观,筛选出实现了指标接口的类,在对象措施中添加多少收集代码。

比如,筛选出完结了android/view/View$OnClickListener接口的类,然后在onClick(Landroid/view/View;)V方法中流入采集数据的代码。

对象意义:

public class MainActivity extends AppCompatActivity implements OnClickListener, 
    android.content.DialogInterface.OnClickListener, 
    OnItemClickListener, 
    OnItemSelectedListener, 
    OnRatingBarChangeListener, 
    OnSeekBarChangeListener, 
    OnCheckedChangeListener, 
    android.widget.RadioGroup.OnCheckedChangeListener, 
    OnGroupClickListener, OnChildClickListener {

    public void onClick(View var1) {
        PluginAgent.onClick(var1);
    }

    public void onClick(DialogInterface var1, int var2) {
        PluginAgent.onClick(this, var1, var2);
    }

    public void onItemClick(AdapterView<?> var1, View var2, int var3, long var4) {
        PluginAgent.onItemClick(this, var1, var2, var3, var4);
    }
    ...
}

对此获得控件数据,为了最大化获取速度,我们在系统中安插了多个数据获得策略。要是指标控件是AbsListView或许RecyclerView
的child view及child view 的chid,那大家得以因而child
view在adapter中的地方获取到咱们想要的数额。那种方法能够处理当先47%页面控件数据的获得难点。系统布置策略的措施如下:

Fragment生命周期追踪

在ViewID优化中,大家讲到Fragment节点的优化时,提到可通过重写Fragment的多少个与生命周期相关的函数监听Fragment生命周期。那些历程除了运用代码埋点,也可凭借插件自动实现:扫描class文件,定位Fragment的多少个与生命周期相关的函数,自动插入代码。

指标函数(方法):

  • onResume()V
  • onPause()V
  • setUserVisibleHint(Z)V
  • onHiddenChanged(Z)V

切切实实达成:

  • 对app中钦定包实行围观,筛选出全体父类为下列当中之一的子类。以下是Fragment及系统内置的多少个广大的Fragment派生类。

    android/app/Fragment
    android/app/DialogFragment
    android/app/ListFragment
    android/support/v4/app/Fragment
    android/support/v4/app/DialogFragment
    android/support/v4/app/ListFragment
    
  • 对这些Fragment子类的onResumedonPausedonHiddenChangedsetFragmentUserVisibleHint措施的字节码举办改动,添加多少收集代码。

对象效果:

public class BaseFragment extends Fragment {
    public BaseFragment() {
    }

    public void onResume() {
        super.onResume();
        PluginAgent.onFragmentResume(this);
    }

    public void onHiddenChanged(boolean var1) {
        super.onHiddenChanged(var1);
        PluginAgent.onFragmentHiddenChanged(this);
    }

    public void onPause() {
        super.onPause();
        PluginAgent.onFragmentPause(this);
    }

    public void setUserVisibleHint(boolean var1) {
        super.setUserVisibleHint(var1);
        PluginAgent.setFragmentUserVisibleHint(this, var1);
    }
}
    private static Map<String, DataStrategy> mStrategies = new HashMap<>();

    static {
        //configure RecyclerView and subclass's search strategy
        DataStrategy recyclerViewStrategy = new RecyclerViewStrategy();
        mStrategies.put("RecyclerView", recyclerViewStrategy);
        mStrategies.put("DDCollectionView", recyclerViewStrategy);

        //ExpandableListView
        DataStrategy EListViewStrategy = new ExpandableListViewStrategy();
        mStrategies.put("ExpandableListView", EListViewStrategy);
        mStrategies.put("DDExpandableListView", EListViewStrategy);

        DataStrategy adapterViewStrategy = new AdapterViewStrategy();
        //ListView
        mStrategies.put("ListView", adapterViewStrategy);
        mStrategies.put("DDListView", adapterViewStrategy);
        mStrategies.put("ListViewCompat", adapterViewStrategy);

        //GridView
        mStrategies.put("GridView", adapterViewStrategy);
        mStrategies.put("DDGridView", adapterViewStrategy);

        //ViewPager
        DataStrategy viewPagerStrategy = new ViewPagerStrategy();
        mStrategies.put("ViewPager", viewPagerStrategy);

        //TabLayout
        DataStrategy tabLayoutStrategy = new TabLayoutStrategy();
        mStrategies.put("TabLayout", tabLayoutStrategy);
    }

2.2.3 代理监听 vs gradle插件

插件埋点方案,产生在编译期,当对象事件响应函数被执行时,才会触发我们插入的代码主动搜集事件。除了开支一点编译速度,应用运营时期基本不受影响。

代办监听方案,由于事先并不精晓用户会触发什么交互事件,所以要求为全数可相互的View设置代理,涉及到控件树遍历,因而质量略逊于gradle插件方案。但辛亏控件树遍历消耗的年华是纳秒级的,不会影响界面交互。

上边总计一下那二种方案的利害。

对此那一个完全自定义布局绘制的页面,例如个人宗旨等页面,业务开发人士须要经过框架api建立一个控件树到多少的映射关系,这样框架在急需获取数据时,通过那些关系就足以格外简单的收获到想要的数额。

(1) 代理监听方案

缺点:

  • 遍历,被动等待被触发
  • 阻碍弹窗相比较辛劳
  • Fragment生命周期需手动拦截

优点:

  • 对于可点击但又未安装点击监听器的View,可设置监听器
    /**
     * 配制自定义布局的数据绑定关系,自定义布局内的任何
     * 控件发生点击行为时,发送的埋点都会携带改数据
     *
     * @param id
     * @param object
     * @return
     */
    @NonNull
    @Override
    public DataConfigureImp configLayoutData(@IdRes int id, @NonNull Object object) {
        Preconditions.checkNotNull(object);

        mDataLayout.put(id, object);
        return this;
    }

(2) gradle插件方案

优点:

  • 不必遍历,主动触发事件
  • 积极拦截弹窗(待扩张)

缺点:

  • 方今只协理Gradle1.5+构建筑工程具

基于TouchTarget找到数据获得策略或然数额映射关系,大家得以卓殊不难的拿走到绑定的数目,获取数据的算法如下:

3 计算与展望

上述便是博客园哈勃勒Data在Android端的无埋点实践中计算的重点难题。还有一部分边边角角的点就不一一细述了。

当然,大家的无埋点方案也并不完善,还有一些未缓解的标题。例如,ViewID的协会及优化方案并无法适用于拥有景况;通过无埋点搜集的数额也仅限控件的部分原始属性,并从未收集到更有价值的事情数据…

今日头条HubbleData也将持续跟进产业界先进埋点技术,及时升级埋点方案。后续针对相比好玩的技术点,也会继续整理出来分享给大家。

假设对该类型感兴趣,能够联系
zhangdan_only@163.com
,欢迎一起探讨。

预见更加多,请猛戳⬇️
用来Android客户端无埋点数据收集的Gradle插件
微博哈勃勒Data无埋点SDK在iOS端的设计与贯彻

        if (strategyView != null) {
            Object data = strategy.fetchTargetData(strategyView);

            return Pair.create(touchTarget, data);
        }

        if (configDataView != null) {
            return Pair.create(touchTarget, mDataLayout.get(configId));
        }

        //解决自定义布局的数据绑定问题
        if (dataAdapter != null) {
            return Pair.create(touchTarget, dataAdapter.getData());
        }

③ 、实现埋点的动态可铺排

在测试环境下,用研职员会因此手动模拟点击的法门赢得sdk上报的控件唯一id和数据消息,在承认id,和数量的不错之后,供给手动配置id和埋点事件的照应关系,及申报的数额字段,并蕴藏到陈设仓库。在线上环境,当用户运行app会拉取配置新闻并加载到内部存款和储蓄器。那样,当用户触发点击行为时,会依据第②步获取的id消息查询配置,假诺在布局中查到相应的条规,会将相应的事件及数量上报到服务器。

为了处理配置下拉退步不可能发送埋点的情事,我们必要将一律的铺排放在主项目标assets目录下,每一次运维app请求配置接口判断配置音讯是不是发生变化,假设布置没有成形,直接运用assets中的配置文件,不然,下拉风行布置,使用新型的埋点配置新闻。

④ 、无痕埋点方案对现有项目标牢笼

动用无埋点sdk供给依据一定的支出规范,关于切实的支出规范请查看工程README。为了确认保障项目编码的规范性,我们付出了一密密麻麻lint检查规则来帮衬发现错误。
lint 工程代码
https://github.com/jessie345/CustomLintRules.git
集成lint功能
https://github.com/jessie345/CustomLintsUsage.git

五 、继续优化
方今,集成这一个无埋点方案有部分应用约束并且要求在主项目中添加一些一定的安插函数。下一步必要做的正是解耦。通过javasist技术,尽量将全数约束迁移到用动态技术确认保障,而不是通过lint规范,将其侵入性降到最低。

迄今,无埋点sdk的宗旨运维体制已经整整梳理清楚。

相关文章