全自动采集最新行业文章(Android全埋点解决方案王灼洲著第1章全埋点著概述)
优采云 发布时间: 2021-12-25 05:20全自动采集最新行业文章(Android全埋点解决方案王灼洲著第1章全埋点著概述)
点击查看第二章
点击查看第三章
Android全埋点解决方案
王卓舟第一章
所有埋点概览
全埋点,又称无埋点、*敏*感*词*埋点、无痕埋点、自动埋点。全埋点是指无需Android应用开发工程师编写代码或只编写少量代码,即可提前自动采集所有用户行为数据,然后根据实际业务分析过滤分析所需的行为数据要求 。
所有埋点采集的事件目前主要包括以下四种(事件名称前面的$符号表示该事件为预设事件,对应的事件为自定义事件)。
□$AppStart 事件
指应用启动,包括冷启动和热启动两种场景。热启动是指应用程序从后台恢复的情况。
□$AppEnd 事件
指应用退出,包括应用正常退出、按Home键进入后台、应用被强杀、应用崩溃等场景。
□$AppViewScreen 事件
指应用页面浏览。对于安卓应用,是指切换Activity或者Fragment。
□$AppClick 事件
指应用控件点击,即View被点击,比如点击Button、ListView等。
采集的四个事件中,最重要和采集的难点是$AppClick事件。因此,整点的解决方案基本上是围绕如何采集 $AppClick 事件。
对于$AppClick事件全埋点的整体解决方案,归根结底就是自动找到被点击控件的处理逻辑(以下统称原创处理逻辑),然后利用一定的技术原理来“拦截”原来的处理逻辑。, 或者在原处理逻辑执行前或者执行器后面“插入”相应的埋藏代码逻辑,从而达到自动埋藏的效果。
至于如何自动“拦截”控件的原创处理逻辑,一般参考Android系统的事件处理机制来进行。关于Android系统的事件处理机制,限于篇幅,本书不再详述。
至于如何自动“插入”嵌入的代码逻辑,基本上是指编译器对Java代码的整体处理流程,即:
JavaCode --> .java --> .class --> .dex
选择在不同的处理阶段“插入”嵌入点代码,使用的技术或原理不尽相同,因此对于全嵌入点有多种解决方案。
面对这么多全嵌入式解决方案,我们应该如何选择?
在选择全埋点方案时,需要从效率、兼容性、扩展性等方面综合考虑。
□效率
全埋点的基本原理,如上所说,其实就是利用某种技术拦截(或调用代理)某些方法(控件点击时的处理逻辑)或“插入”相关埋点代码。比如按钮Button,如果要为其设置点击处理逻辑,则需要设置android.view.View.OnClickListener并覆盖其onClick(android.view.View)方法。如果要实现$AppClick事件的全埋,可以“拦截”onClick(android.view.View)方法,或者在onClick(android.view.View)前后“插入”相应的埋藏逻辑方法代码。根据“什么时候去代理或插入代码”的条件来区分,
□静态代理
所谓静态代理,是指通过Gradle Plugin在应用编译过程中“插入”代码或修改代码(.class文件)。如AspectJ、ASM、Javassist、AST等程序都属于这种方式。后面我们会一一介绍这几种方案。
有关这些方法的时序,请参见图 1-1。
□动态代理
所谓动态代理是指代码运行时的代理(Runtime)。比如我们比较常见的代理View.OnClickListener、Window.Callback、View.AccessibilityDelegate等程序都属于这种方式。后面我们会一一介绍这几种方案。
不同的解决方案具有不同的处理能力和运行效率,同时对应用程序的入侵程度和对应用程序整体性能的影响也不同。一般来说,静态代理明显优于动态代理。这是因为静态代理的“动作”是在应用程序的编译阶段处理的,不会对应用程序的整体性能产生太大影响。“动作”发生在应用程序的运行阶段(即Runtime),因此会对应用程序的整体性能产生一定的影响。
□兼容性
随着Android生态系统的快速发展,无论是Android系统本身,还是Android应用开发相关的组件和技术都在快速发展、迭代迅速,这也给我们开发全埋解决方案带来了一定的困难。例如,不同的Android应用可以有不同的开发语言(Java、Kotlin)、不同的Java版本(Java7、Java8)、不同的开发IDE(eclipse、Android Studio)等等。方法(原生开发,H5、混合开发),使用不同的第三方开发框架(React Native,APICloud,Weex),不同的Gradle版本,以及Lambda,D8、Instant Run,
□可扩展性
随着业务的快速发展和数据分析需求的不断提高,对数据的全埋点使用提出了更高的要求采集。一方面,它需要全自动化的采集(采集的范围),同时它需要更精细的采集控制粒度(采集可以是定制)。例如,如何向控件添加自定义属性?如果不想采集怎么控制某个控件的点击事件?如果不想采集某类控件(ImageView)点击事件,如何处理?如果某个页面(Activity)上所有控件的点击事件都不要采集,如何处理等。
任何一种全埋点技术方案都有利有弊,没有普遍的完美方案。我们只需要针对不同的应用场景选择最适合的数据解决方案。能够满足采集实际数据需求的方案就是最好的方案。
1.1Android 视图类型
在Android系统中,控件(View)的种类非常丰富。分类方法也多种多样。根据控件设置的不同*敏*感*词*器,我们大致可以将控件分为以下几类。
□Button、CheckedTextView、TextView、ImageButton、ImageView等。
为这些控件设置的侦听器是 android.view.View.OnClickListener。
以按钮为例:
按钮按钮 = findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
//do something
}
});
□搜索栏
SeekBar设置的*敏*感*词*器为android.widget.SeekBar.OnSeekBarChangeListener,如:
SeekBar seekBar = findViewById(R.id.seekBar);
seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int i, boolean b) {
// do something
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
// do something
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
// do something
}
});
□TabHost
TabHost设置的*敏*感*词*器为android.widget.TabHost.OnTabChangeListener,如:
TabHost tabHost = findViewById(R.id.tabhost);
tabHost.setOnTabChangedListener(new TabHost.OnTabChangeListener() {
@Override
public void onTabChanged(String tabName) {
//do something
}
});
□评级栏
RatingBar设置的listerner为android.widget.RatingBar.OnRatingBarChangeListener,如:
RatingBar ratingBar = findViewById(R.id.ratingBar);
ratingBar.setOnRatingBarChangeListener(newRatingBar.OnRatingBarChangeListener(){
@Override
public void onRatingChanged(RatingBar ratingBar, float rating, boolean fromUser) {
//do something
}
});
□CheckBox、SwitchCompat、RadioButton、ToggleButton、RadioGroup等。
这些View属于同一种类型,都是有“状态”的按钮,它们设置的*敏*感*词*器是CompoundButton.OnCheckedChangeListener。
以 CheckBox 为例:
CheckBox checkBox = findViewById(R.id.checkbox);
checkBox.setOnCheckedChangeListener(newCompoundButton.OnCheckedChangeListener(){
@Override
public void onCheckedChanged(CompoundButton compoundButton, boolean isChecked) {
//do something
}
});
□纺纱机
Spinner设置的*敏*感*词*器为android.widget.AdapterView.OnItemSelectedListener,如:
Spinner spinner = findViewById(R.id.spinner);
spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView parent, View view, int position, long id) {
//do something
}
@Override
public void onNothingSelected(AdapterView parent) {
}
});
□菜单项
*敏*感*词*器主要是通过覆盖Activity的相关方法(onOptionsItemSelected、onContextItemSelected)来设置的,比如:
//选项菜单
@覆盖
公共布尔 onOptionsItemSelected(android.view.MenuItem) {
//do something
}
//上下文菜单
@覆盖
公共布尔 onContextItemSelected(android.view.MenuItem) {
//do something
}
□ListView、GridView
ListView和GridView都是AdapterView的子类,显示的内容是一个“集合”。他们设置的*敏*感*词*器是android.widget.AdapterView.OnItemClickListener,比如:
ListView listView = findViewById(R.id.listView);
listView.setOnItemClickListener(new AdapterView.OnItemClickListener(){
@Override
public void onItemClick(AdapterView parent, View view, int position, long id) {
//do something
}
});
□可扩展列表视图
ExpandableListView 也是 AdapterView 的子类和 ListView 的子类。它的点击分为两种情况,ChildClick和GroupClick,所以它设置的*敏*感*词*器也分为两种情况,即:android.widget.ExpandableListView.OnChildClickListener和android.widget.ExpandableList-View.OnGroupClickListener,如:
ExpandableListView listview = findViewById(R.id.expandablelistview);
//子点击
listview.setOnChildClickListener(new ExpandableListView.OnChildClickListener() {
@Override
public boolean onChildClick(ExpandableListView expandableListView,
View view, int groupPosition, int childPosition, long id) {
//do something
return true;
}
});
//组点击
listview.setOnGroupClickListener(new ExpandableListView.OnGroupClickListener() {
@Override
public boolean onGroupClick(ExpandableListView expandableListView,
View view, int childPosition, long id) {
//do something
return true;
}
});
□对话
Dialog设置的*敏*感*词*器分为两种情况。对于普通的普通Dialog,设置的*敏*感*词*器是android。content.DialogInterface.OnClickListener,如:
AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setPositiveButton("OK", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
//do something
}
});
还有一个显示列表的对话框。它设置的*敏*感*词*器是android.content.DialogInterface.OnMultiChoiceClickListener,比如:
AlertDialog.Builder builder = new AlertDialog.Builder(context);
DialogInterface.OnMultiChoiceClickListener mutiListener =
new DialogInterface.OnMultiChoiceClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int which, boolean isChecked) {
//do something
}
};
1.2View绑定*敏*感*词*方法
随着Android相关技术的不断更新和迭代,将*敏*感*词*器绑定到View的方式有很多种。下面以Button为例介绍一下日常开发中几种常见的绑定*敏*感*词*器的方式。
□通过代码设置*敏*感*词*器
按钮按钮 = findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
//do something
}
});
这种方法是目前开发中最常用的方法,也是我们全埋点解决方案需要重点关注和支持的方法。
□通过android:onClick属性绑定*敏*感*词*器
首先在布局文件中声明Button的android:onClick属性,如:
android:id="@+id/xmlOnClick"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="xmlOnClick"
android:text="android:onClick 绑定OnClickListener"/>
我们将android:onClick的属性值设置为“xmlOnClick”,此时“xmlOnClick”代表点击处理逻辑对应的方法名。然后在对应的Activity文件中声明android:onClick属性指定的方法xmlOnClick:
公共无效xmlOnClick(查看视图){
//do something
}
注意:此方法必须有一个且只有一个 View 类型的参数。
这种方法在一些新项目中不是很常见,在一些老的Android项目中可能会有如此大量的使用。
□通过注解绑定*敏*感*词*器
目前,很多第三方库都提供了类似的功能。以 ButterKnife 为例:
@OnClick({R2.id.butterknife})
public void butterKnifeButtonOnClick(View view) {
//do something
}
首先定义一个方法,该方法只有一个View类型参数,然后在方法上使用ButterKnife的@OnClick注解声明,其中参数表示控件的android:id。
这种方法也是比较流行的使用方法之一。
ButterKnife 更详细的使用方法请参考其官网:。
□listener 收录 Lambda 语法
Java 8 支持 Lambda,例如:
AppCompatButton 按钮 = findViewById(R.id.lamdbaButton);
button.setOnClickListener(view ->Log.i("MainActivity", "Lambda OnClick"));
这种方式也是目前比较流行的一种使用方式。
其实这根本就不是绑定*敏*感*词*器的方式,而是绑定的*敏*感*词*器收录了Lambda语法。之所以在这里提到,是因为这种方法在我们选择全埋点方案时会产生一定的影响。比如后面要介绍的AspectJ全埋点方案就不能支持这种使用Lambda语法的点击。事件。
Lambda 的详细信息请参考:。
□ 通过 DataBinding 绑定*敏*感*词*器
首先在布局文件中声明 android:onClick 属性:
android:onClick属性值为“@{handlers::dataBindingOnClick}”,表示按钮的点击处理逻辑是handlers对象的dataBindingOnClick方法,其中handlers对象是MainActivity的一个实例。
然后在对应的Java文件中声明android:onClick属性指定的方法dataBindingOnClick:
公共无效数据绑定OnClick(查看视图){
//do something
}
注意:此方法必须有一个且只有一个 View 类型的参数。
这种方法也是一种新的流行的使用方式。
DataBinding更详细的使用方法请参考官网:data-binding/index.html。
由于整点的重点是解决控件的点击行为数据,了解控件可以设置哪些*敏*感*词*器,以及设置或绑定*敏*感*词*器的不同方式,对我们学习或选择会有很大帮助全点的解决方案。.