《Android全埋点解决方案》 —1 全埋点概述

举报
华章计算机 发表于 2019/12/19 11:38:11 2019/12/19
【摘要】 本节书摘来自华章计算机《Android全埋点解决方案》 一书中第1章,第1.1节,作者是王灼洲 。

 

第1章

全埋点概述

全埋点,也叫无埋点、***埋点、无痕埋点、自动埋点。全埋点是指无须Android应用程序开发工程师写代码或者只写少量的代码,就能预先自动收集用户的所有行为数据,然后就可以根据实际的业务分析需求从中筛选出所需行为数据并进行分析。

全埋点采集的事件目前主要包括以下四种(事件名称前面的$ 符号,是指该事件是预置事件,与之对应的是自定义事件)。

$AppStart事件

是指应用程序启动,同时包括冷启动和热启动场景。热启动也就是指应用程序从后台恢复的情况。

$AppEnd事件

是指应用程序退出,包括应用程序的正常退出、按 Home 键进入后台、应用程序被强杀、应用程序崩溃等场景。

$AppViewScreen事件

是指应用程序页面浏览,对于Android应用程序来说,就是指切换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)方法的前面或者后面“插入”相应的埋点逻辑代码。按照“在什么时候去代理或者插入代码”这个条件来区分的话,$AppClick 事件的全埋点技术可以大致分为如下两种方式。

静态代理

所谓静态代理,就是指通过Gradle Plugin 在应用程序编译期间“插入”代码或者修改代码(.class文件)。比如AspectJ、ASM、Javassist、AST等方案均属于这种方式。这几种方案,我们在后面会一一进行介绍。

这几种方式处理的时机可以参考图 1-1。

 image.png

图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、DataBinding、Fragment 等新技术的出现,都会给全埋点带来很多兼容性方面的问题。

扩展性

随着业务的快速发展和对数据分析需求的不断提高,对使用全埋点进行数据采集,也提出了更高的要求。一方面要求可以全部自动采集(采集的范围),同时又要求能有更精细化的采集控制粒度(采集可以自定义)。比如,如何给某个控件添加自定义属性?如果不想采集某个控件的点击事件应该如何控制?如果不想采集某种控件类型(ImageView)的点击事件又该如何处理?如果某个页面(Activity)上所有控件的点击事件都不想采集又该如何处理等。

任何一种全埋点的技术方案,都有优点和缺点,没有一种普适的完美解决方案。我们只需要针对不同的应用场景,选择最合适的数据采集方案即可。能满足实际数据采集需求的方案,才是最优的方案。

1.1 Android View 类型

在Android 系统中,控件(View)的类型非常丰富。分类方式也是多种多样的。我们根据控件设置的监听器(listener)的不同,可以大致将控件分为如下几类。

Button、CheckedTextView、TextView、ImageButton、ImageView 等

为这些控件设置的listener均是 android.view.View.OnClickListener。

下面以 Button 为例:

Button button = findViewById(R.id.button);

button.setOnClickListener(new View.OnClickListener() {

    @Override

    public void onClick(View view) {

        //do something

    }

});

SeekBar

SeekBar设置的listener是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 设置的 listener 是 android.widget.TabHost.OnTabChangeListener,如:

TabHost tabHost = findViewById(R.id.tabhost);

tabHost.setOnTabChangedListener(new TabHost.OnTabChangeListener() {

    @Override

    public void onTabChanged(String tabName) {

        //do something

    }

});

RatingBar

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属于同一种类型,它们都是属于带有“状态”的按钮,它们设置的listener 均是CompoundButton.OnCheckedChangeListener。

下面以CheckBox为例:

CheckBox checkBox = findViewById(R.id.checkbox);

checkBox.setOnCheckedChangeListener(newCompoundButton.OnCheckedChangeListener(){

    @Override

    public void onCheckedChanged(CompoundButton compoundButton, boolean isChecked) {

        //do something

    }

});

Spinner

Spinner设置的 listener是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) {

    }

});

MenuItem

主要是通过重写Activity的相关方法(onOptionsItemSelected、onContextItemSelected)来设置listener,如:

//选项菜单

@Override

public boolean onOptionsItemSelected(android.view.MenuItem) {

    //do something

}

 

//上下文菜单

@Override

public boolean onContextItemSelected(android.view.MenuItem) {

    //do something

}

ListView、GridView

ListView和GridView都是AdapterView的子类,显示的内容都是一个“集合”。它们设置的listener均是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

ExpandableListView也是 AdapterView的子类,同时也是ListView的子类。它的点击分为ChildClick和GroupClick两种情况,所以,它设置的 listener也是分为两种情况,即:android.widget.ExpandableListView.OnChildClickListener 和android.widget.ExpandableList-View.OnGroupClickListener,如:

ExpandableListView listview = findViewById(R.id.expandablelistview);

//ChildClick

listview.setOnChildClickListener(new ExpandableListView.OnChildClickListener() {

    @Override

    public boolean onChildClick(ExpandableListView expandableListView,

                        View view, int groupPosition, int childPosition, long id) {

        //do something

        return true;

    }

});

 

//GroupClick

listview.setOnGroupClickListener(new ExpandableListView.OnGroupClickListener() {

    @Override

    public boolean onGroupClick(ExpandableListView expandableListView,

                    View view, int childPosition, long id) {

        //do something

        return true;

    }

});

Dialog

Dialog设置的listener分为两种情况。对于常见的普通Dialog,设置的 listener是android. content.DialogInterface.OnClickListener,如:

AlertDialog.Builder builder = new AlertDialog.Builder(context);

builder.setPositiveButton("确定", new DialogInterface.OnClickListener() {

    @Override

    public void onClick(DialogInterface dialog, int which) {

        //do something

    }

});

还有一种是显示列表的Dialog,它设置的listener 是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

    }

};


【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。