目录
3.1.2尽量保证ViewGroup下新插入视图时View的ViewTree路径下的同一层级下index不变(如何保证?)
3.3ListView,RecyclerView,ViewPager等可复用View优化
1.埋点是什么?
埋点是应用中特定的流程收集一些信息,用来跟踪应用使用的情况,后续用来进一步优化产品或者提供运营的数据支撑,包括访问数(Visits),访客数(Visitor),停留时长(Time On Site),页面浏览数(Page Views)和跳出率(Bounce Rate)。这样的信息收集可以大致分为两种:页面统计(track this virtual page view),统计操作行为(track this button by an event)。
2.为什么需要无痕埋点?
就目前而言,客户端埋点最常见的方式还是以代码埋点为主。代码埋点的方式虽然灵活多变,可以准确的获取各种数据,但是也存在不少痛点:
a.业务需求总是多变的,漏埋点或者错埋点总是无法完全避免的,这时就只能等待下个版本迭代的时候补全了。
b.增加开发与测试的工作量,不规范的埋点代码可能造成App Crash。
c.埋点代码侵入业务代码中,埋点数量的不断增加,也给后续的版本迭代与代码维护增加难度。
产品、运营在版本发布前并不能完全预知自己需要收集的数据,等到版本发布之后才发现一些重要的埋点并没有采集,只能等待下个版本补充,可能为时已晚了。这时候我们就要引入无痕埋点的方案了,接下来我将详细讲解一下Android端在无痕埋点方面的具体实现方案。
3.自动无痕实现方案?
实现无痕埋点要解决几个问题:
a.如何准备识别每个View?
b.如何监听Activity和Fragment生命周期(页面事件采集)?
3.1如何准备识别每个View?
View的ID要保证唯一性,稳定性;
a.唯一性
唯一性保证每个View拥有唯一的ID,能够快速找到对应View;
实际在layout布局文件呢中View可以通过view.getId()获取唯一值,在R.java会为res的资源建立唯一ID,aapt打包资源时会生成resources.arsc描述文件,描述id和res下资源的对应关系;由于aapt生成资源的ID规则在不同的SDK工具版本下可能不一样,没法保证不会发生变化;在代码中new新的View时可能不会为view特意指定ID,view.getId()的结果都是NO_ID;
b.稳定性
稳定性保证ID不能随意变动,具有一定通用性;
可以采用Page+ViewTree的方式,Page分Activity和Fragment两种页面形式:
ActivityID规则:ActivityClassName:ViewTree
MainActivity:LinearLayout[0]/FrameLayout[0]/FitWindowsLinearLayout[0]/ContentFrameLayout[0]/LinearLayout[0]/LinearLayout[0]/AppCompatTextView[2]
FragmentID规则:ActivityClassName[FragmentClassName]:ViewTree
MainActivity[TwoFragment]:LinearLayout[0]/FrameLayout[0]/FitWindowsLinearLayout[0]/ContentFrameLayout[0]/LinearLayout[0]/FrameLayout[0]/LinearLayout[1]/AppCompatTextView[0]
3.1.1如何定位是那个视图?
通过View所属Activity和Fragment页面,层级(deep)View相对于rootView位于第几层级,View相对于同一层级下排在第几个(index);
直接用Android Studio--Tools--Layout Inspector就可以提取你App当前页面的View Tree了,如下图:
通过界面视图结构可以看到Activity页面View完整ViewTree路径 ;
例如:我们要定位TextView2的ViewTree路径:
TextView2父视图为RelativeLayout2,RelativeLayout2父视图为Root;
Root是跟视图 ,同一层级只有一个,则为Root;
RelativeLayout2为Root子视图,deep层级为1,同一层级下位置为1,则为Root/RelativeLayout[1];
TextView2为RelativeLayout2子视图,deep层级为2,同一层级下的位置为1,Root/RelativeLayout[1]/TextView[1];
TextView1的ViewTree路径为Root/RelativeLayout[1]/TextView[0];
Root,RelativeLayout,TextView指的是View的控件的类名;
'/'表示ViewTree的层级;
Root:指的是跟路径,通常指的是setContentView(layoutId)跟视图;
deep和index从0开始计算;
3.1.2保证View的ID不受Android版本影响
MainActivity:LinearLayout[0]/FrameLayout[0]/FitWindowsLinearLayout[0]/ContentFrameLayout[0]/LinearLayout[0]/LinearLayout[0]/AppCompatTextView[2]
View的ID结构构成,ActivityClassName(MainActivity):窗口视图(状态栏+内容视图-容器LinearLayout[0]/FrameLayout[0]/FitWindowsLinearLayout[0]/ContentFrameLayout[0])/通过setContentView(layoutId)自定义要显示内容视图ViewTree;
通常ActivityClassName和通过setContentView(layoutId)自定义要显示内容视图是不会受Android版本影响;Activity要显示的窗口视图受Android版本不同视图层级和结构可能发生变化;
AppCompatActivity
@Override
public void setContentView(@LayoutRes int layoutResID) {
getDelegate().setContentView(layoutResID);
}
AppCompatDelegate
private static AppCompatDelegate create(Context context, Window window,
AppCompatCallback callback) {
final int sdk = Build.VERSION.SDK_INT;
if (BuildCompat.isAtLeastN()) {
return new AppCompatDelegateImplN(context, window, callback);
} else if (sdk >= 23) {
return new AppCompatDelegateImplV23(context, window, callback);
} else if (sdk >= 14) {
return new AppCompatDelegateImplV14(context, window, callback);
} else if (sdk >= 11) {
return new AppCompatDelegateImplV11(context, window, callback);
} else {
return new AppCompatDelegateImplV9(context, window, callback);
}
}
不同Android版本AppCompatDelegate实现类
@Ov