自动化埋点的实现

综上我们可以整理一下自动化埋点的思路。对于自动化埋点第一个功能,可以通过创建基类BaseActivity重写Activity的所有的生命周期。对 于自动化埋点的第二个功能,实现方式是,通过重写Activity的dispatchTouchEvent方法,点击事件发生时,通过 MotionEvent对象获取点击位置坐标,然后遍历Activity界面中所有的View(控件也都是View),判断哪个View区域包含点击位 置,从而判断哪个View被点击了。另外有个问题,当拦截到这些操作信息,如何将它放到一个统一的地方去处理呢?可以采用广播的方式,将相关数据发送出 去,然后在一个BroadcastReceiver中统一处理埋点的log生成。看如下代码:

public BaseActivity extends Activity{
    //其他的Activity生命周期重写类似
    protected void onStart() {
        super.onStart();
        LocalBroadcastManager broadcastManager = LocalBroadcastManager.getInstance(this);
        Intent intent = new Intent(ACTIVITY_START);
        intent.putExtra(ACTIVITY_START, event);
        broadcastManager.sendBroadcast(intent);
    }
    protected boolean dispatchTouchEvent(MotionEvent ev) {
        if (event.getAction() == MotionEvent.ACTION_UP) {
            LocalBroadcastManager broadcastManager = LocalBroadcastManager.getInstance(this);
            Intent intent = new Intent(VIEW_CLICK);
            intent.putExtra(VIEW_CLICK, event);
            broadcastManager.sendBroadcast(intent);
        }
    }}public class AutoMonitorReceiver extends BroadcastReceiver {
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        if(action == VIEW_CLICK){
            MotionEvent event = intent.getParcelableExtra(VIEW_CLICK);
            //1.递归遍历Activity(就是Context)中的所有View,找出被点击的View
            View clickView = searchClickView(view, event);
            //2.生成log记录下来
            writeLog(); 
        }else if(action == ACTIVITY_START){
            //可以知道某个界面被打开了,然后记录此次操作行为
            writeLog();
        }
    }
    private View searchClickView(View view, MotionEvent event) {
        View clickView = null;
        if (isInView(view, event) && 
            view.getVisibility() == View.VISIBLE) {  //这里一定要判断View是可见的
            if (view instanceof ViewGroup) {    //遇到一些Layout之类的ViewGroup,继续遍历它下面的子View
                ViewGroup group = (ViewGroup) view;
                for (int i = group.getChildCount() - 1; i >= 0; i--) {
                    View chilView = group.getChildAt(i);
                    clickView = searchClickView(chilView, event);
                    if (clickView != null) {
                        return clickView;
                    }
                }
            }
            clickView = view;
        }   
        return clickView;
    }}

又一个问题,代码中的writeLog方法到底要记录哪些数据作为log信息呢?log信息中最重要的是能让开发者看出来哪个界面被打开或者哪个控件被点 击。对于界面,可以记录其类名;对于控件,一般没有确定的名称,那么可以记录下来这个控件在界面中的路径。比如上文中介绍Android UI布局的实例,如果要定位记录那个Button,则可以记录它所在界面的类名和Button的布局路径作为它的标识。那个Button的路径可以表示为DecorView>LinearLayout[0]>FrameLayout[1]>RelativeLayout[0]>Button[0],由于LinearLayout有多个子View,因此可以在子View中加入编号来区分。这样就解决了log信息的记录问题,log信息的格式大致要有如下几个字段:

monitor_type | ui_name | view_ui_path

比如是控件点击埋点log,则可以记录为

VIEW_CLICK | MainActivity | DecorView>LinearLayout[0]>FrameLayout[1]>RelativeLayout[0]>Button[0]

比如是MainActivity界面打开的埋点log,则可以记录为

ACTIVITY_START | MainActivity | NULL

这样我们分析这些日志信息,就可以统计出一个应用中各个页面被打开过多少次,某个界面的控件被点击过多少次。

对于自动化埋点的第三个功能,实现埋点的定制,这个比较好实现。如果我们需要对那些界面或者控件进行埋点,我们可以定制一个埋点列表。这个列表在应用启动 的时候被下载到用户的手机上,然后AutoMonitorReceiver需要多做一点事,就是将广播发送过来的埋点信息与埋点列表进行比对,看是不是需 要埋的点,如果是,就将其记录;如果不是,就不做处理。假设我们用json存储这个埋点列表,大致的结构如下:

{
    "version": "app_1.1.1",
    "view_id":"MainActivity": {
        "DecorView>LinearLayout[0]>FrameLayout[1]>RelativeLayout[0]>Button[0]":"button_0"
        "DecorView>LinearLayout[0]>FrameLayout[1]>RelativeLayout[0]>Button[1]":"button_1"
    }}

当然这个埋点列表如何生成,这个需要开发者自己写代码去处理。

自动化埋点存在的问题和难点

poptest是国内唯一一家培养测试开发工程师的培训机构,以学员能胜任自动化测试,性能测试,测试工具开发等工作为目标。如果对课程感兴趣,请大家咨询qq:908821478,咨询电话010-84505200。

本文提供的自动化埋点的思路是,通过拦截屏幕点击事件来搜索被点击的控件,然后将其界面类名和控件的UI路径记录下来作为log信息。将控件的UI路径和所在的界面类名作为一个控件的标识,有时候会出现一些问题,比如:

1.Android版本不同会造成控件的UI路径不同。比如Android2.2与Android4.1版本下,获取到上文中Button的UI路径分别为

//android2.2DecorView>FrameLayout[1]>RelativeLayout[0]>Button[0]//android4.1DecorView>LinearLayout[0]>FrameLayout[1]>RelativeLayout[0]>Button[0]

这个问题可以通过将控件的UI路径缩短来解决,比如就只用FrameLayout[1]>RelativeLayout[0]>Button[0]路径来标识Button控件。

2.对一些隐藏控件、弹出窗口或者浮动窗口不好处理。比如,在上文中的Button同样位置存在另外一个Button,不过是隐藏的,有时出现,有时不出现。当一个Button被点击,单纯依靠DecorView>FrameLayout[1]>RelativeLayout[0]>Button[0]路 径不能判断出是哪个Button被点击。解决这个问题,有时做一些特殊处理可以解决,比如扩展log的字段,多加入一些控件的信息,比如Button上的 文字等。但是有时,开发应用的控件布局千变万化,一些控件确实不能通过UI路径进行唯一标识,这种就没法自动化埋点了。只能通过手动埋点来来补充了。

3.文中提到为了实现埋点的定制,需要开发者自己写代码生成一个埋点列表,这个也是比较麻烦的。要遍历一个应用中所有界面和界面中控件的UI路径,这个比 较容易,但是取出自己想要埋点的控件UI路径,这个可能需要人工去查看比对。另外,一些大型应用开发的时候,界面随时发生着变化,一些控件的布局在随时发 生变化。每次发布应用的时候,都需要扫描一下应用控件信息,以及重新找一下埋点控件的UI路径,这个是相当麻烦的。如何实现这部分的自动化,也是一个难 题。