带你读《Android全埋点解决方案》之二:$AppViewScreen全埋点方案

点击查看第一章
点击查看第三章

第2章

$AppViewScreen全埋点方案
$AppViewScreen事件,即页面浏览事件。在Android系统中,页面浏览其实就是指切换不同的Activity或Fragment(本书暂时只讨论切换Activity的情况)。对于一个 Activity,它的哪个生命周期执行了,代表该页面显示出来了呢?通过对 Activity生命周期的了解可知,其实就是onResume(Activity activity)的回调方法。所以,当一个Activity 执行到onResume(Activity activity)生命周期时,也就代表该页面已经显示出来了,即该页面被浏览了。我们只要自动地在onResume里触发$AppViewScreen事件,即可解决$AppViewScreen事件的全埋点。

2.1 关键技术Application.ActivityLifecycleCallbacks

ActivityLifecycleCallbacks是Application 的一个内部接口,是从 API 14(即Android 4.0)开始提供的。Application 类通过此接口提供了一系列的回调方法,用于让开发者可以对 Activity 的所有生命周期事件进行集中处理(或称监控)。我们可以通过Application类提供的registerActivityLifecycleCallback(ActivityLifecycleCallbacks callback)方法来注册 ActivityLifecycleCallbacks回调。
我们下面先看看Application.ActivityLifecycleCallbacks都提供了哪些回调方法。Application.ActivityLifecycleCallbacks接口定义如下:

public interface ActivityLifecycleCallbacks {
    void onActivityCreated(Activity activity, Bundle savedInstanceState);
    void onActivityStarted(Activity activity);
    void onActivityResumed(Activity activity);
    void onActivityPaused(Activity activity);
    void onActivityStopped(Activity activity);
    void onActivitySaveInstanceState(Activity activity, Bundle outState);
    void onActivityDestroyed(Activity activity);

}
以 Activity的onResume(Activity activity)生命周期为例,如果我们注册了 Activity-LifecycleCallbacks回调,Android 系统会先回调 ActivityLifecycleCallbacks 的 onActivity-Resumed(Activity activity)方法,然后再执行Activity本身的onResume函数(请注意这个调用顺序,因为不同的生命周期的执行顺序略有差异)。通过registerActivityLifecycleCallback 方法名中的“register”字样可以知道,一个 Application 是可以注册多个 ActivityLifecycleCallbacks回调的,我们通过registerActivityLifecycleCallback方法的内部实现也可以证实这一点。
public void registerActivityLifecycleCallbacks(ActivityLifecycleCallbacks callback) {

synchronized (mActivityLifecycleCallbacks) {
    mActivityLifecycleCallbacks.add(callback);
}

}
内部定义了一个list用来保存所有已注册的ActivityLifecycleCallbacks。

2.2原理概述

实现Activity的页面浏览事件,大家首先想到的是定义一个BaseActivity,然后让其他Activity继承这个 BaseActivity。这种方法理论上是可行的,但不是最优选择,有些特殊的场景是无法适应的。比如,你在应用程序里集成了一个第三方的库(比如 IM 相关的),而这个库里恰巧也包含 Activity,此时你是无法让这个第三方的库也去继承你的 BaseActivity(最起码驱使第三方服务商去做这件事的难度比较大)。所以,为了实现全埋点中的页面浏览事件,最优的方案还是基于我们上面讲的 Application.ActivityLifecycleCallbacks。
不过,使用Application.ActivityLifecycleCallbacks机制实现全埋点的页面浏览事件,也有一个明显的缺点,就是注册Application.ActivityLifecycleCallbacks 回调要求 API 14+。
在应用程序自定义的 Application类的 onCreate()方法中初始化埋点 SDK,并传入当前的Application 对象。埋点SDK 拿到 Application 对象之后,通过调用 Application的registerActivityLifecycleCallback(ActivityLifecycleCallbacks callback)方法注册Application.ActivityLifecycleCallbacks回调。这样埋点 SDK 就能对当前应用程序中所有的 Activity 的生命周期事件进行集中处理(监控)了。在注册的 Application.ActivityLifecycleCallbacks 的onActivityResumed(Activity activity)回调方法中,我们可以拿到当前正在显示的 Activity对象,然后调用 SDK 的相关接口触发页面浏览事件($AppViewScreen)即可。

2.3 案例

下面我们会详细介绍$AppViewScreen事件全埋点方案的实现步骤。
完整的项目源码可以参考以下网址:
https://github.com/wangzhzh/AutoTrackAppViewScreen
第1步:新建一个项目(Project)
在新建的项目中,会自动包含一个主 module,即:app。
第2步:创建 sdk module
新建一个 Android Library module,名称叫 sdk,这个模块就是我们的埋点 SDK模块。
第3步:添加依赖关系
app module需要依赖sdk module。可以通过修改app/build.gradle 文件,在其 dependencies节点中添加依赖关系:
apply plugin: 'com.android.application'

android {

compileSdkVersion 28
defaultConfig {
    applicationId "com.sensorsdata.analytics.android.app.appviewscreen"
    minSdkVersion 15
    targetSdkVersion 28
    versionCode 1
    versionName "1.0"
}
buildTypes {
    release {
        minifyEnabled false
         proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
    }
}

}

dependencies {

implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:appcompat-v7:28.0.0-rc02'
implementation 'com.android.support.constraint:constraint-layout:1.1.3'

implementation project(':sdk')
}

也可以通过 Project Structure 给模块添加依赖关系,在此不再详细描述。
第4步:编写埋点 SDK
在sdk module 中我们新建一个埋点 SDK 的主类,即SensorsDataAPI.java,完整的源码参考如下:
package com.sensorsdata.analytics.android.sdk;

import android.app.Application;
import android.support.annotation.Keep;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Log;

import org.json.JSONObject;

import java.util.Map;

带你读《Android全埋点解决方案》之二:$AppViewScreen全埋点方案


@Keep
public class SensorsDataAPI {
private final String TAG = this.getClass().getSimpleName();
public static final String SDK_VERSION = "1.0.0";
private static SensorsDataAPI INSTANCE;
private static final Object mLock = new Object();
private static Map<String, Object> mDeviceInfo;
private String mDeviceId;

@Keep
@SuppressWarnings("UnusedReturnValue")
public static SensorsDataAPI init(Application application) {
    synchronized (mLock) {
        if (null == INSTANCE) {
            INSTANCE = new SensorsDataAPI(application);
        }
        return INSTANCE;
    }
}

@Keep
public static SensorsDataAPI getInstance() {
    return INSTANCE;
}

private SensorsDataAPI(Application application) {
    mDeviceId = SensorsDataPrivate.getAndroidID(application.getApplicationContext());
    mDeviceInfo = SensorsDataPrivate.getDeviceInfo(application.getApplicationContext());
    SensorsDataPrivate.registerActivityLifecycleCallbacks(application);
}

![image.png](https://ucc.alicdn.com/pic/developer-ecology/656f122bf70b4d17bda922da776375a2.png)

带你读《Android全埋点解决方案》之二:$AppViewScreen全埋点方案

public void track(@NonNull String eventName, @Nullable JSONObject properties) {
    try {
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("event", eventName);
        jsonObject.put("device_id", mDeviceId);

        JSONObject sendProperties = new JSONObject(mDeviceInfo);

        if (properties != null) {
            SensorsDataPrivate.mergeJSONObject(properties, sendProperties);
        }

        jsonObject.put("properties", sendProperties);
        jsonObject.put("time", System.currentTimeMillis());

        Log.i(TAG, SensorsDataPrivate.formatJson(jsonObject.toString()));
        } catch (Exception e) {
            e.printStackTrace();
        }
}

}
目前这个主类比较简单,主要包含如下几个方法。
□init(Application application)
这是一个静态方法,是埋点SDK的初始化函数,有一个Application类型的参数。内部实现使用到了单例设计模式,然后调用私有构造函数初始化埋点 SDK。app module 就是调用这个方法来初始化我们的埋点SDK。
□getInstance()
它也是一个静态方法,app 通过该方法可以获取埋点 SDK 的实例对象。
□SensorsDataAPI(Application application)
私有的构造函数,也是埋点 SDK 真正的初始化逻辑。在其方法内部通过调用 SDK 的内部私有类SensorsDataPrivate中的方法来注册ActivityLifecycleCallbacks。
□track(@NonNull final String eventName, @Nullable JSONObject properties)
对外公开的 track 事件接口。通过调用该方法可以触发事件,第一个参数 eventName 代表事件名称,第二个参数properties代表事件属性。本书为了简化,触发事件仅仅通过Log.i打印了事件的JSON信息。
关于SensorsDataPrivate类中的getAndroidID(Context context)、getDeviceInfo(Context context)、mergeJSONObject(final JSONObject source, JSONObject dest)、formatJson(String jsonStr)方法实现可以参考如下源码:
package com.sensorsdata.analytics.android.sdk;

import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.app.ActionBar;
import android.app.Activity;
import android.app.Application;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.provider.Settings;
import android.support.annotation.Keep;
import android.support.v7.app.AppCompatActivity;
import android.text.TextUtils;
import android.util.DisplayMetrics;

import org.json.JSONException;
import org.json.JSONObject;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
带你读《Android全埋点解决方案》之二:$AppViewScreen全埋点方案
带你读《Android全埋点解决方案》之二:$AppViewScreen全埋点方案
带你读《Android全埋点解决方案》之二:$AppViewScreen全埋点方案
带你读《Android全埋点解决方案》之二:$AppViewScreen全埋点方案
带你读《Android全埋点解决方案》之二:$AppViewScreen全埋点方案
带你读《Android全埋点解决方案》之二:$AppViewScreen全埋点方案
带你读《Android全埋点解决方案》之二:$AppViewScreen全埋点方案

第5步:注册 ActivityLifecycleCallbacks回调
我们是通过调用 SDK 的内部私有类SensorsDataPrivate的registerActivityLifecycleCallbacks(Application application)方法来注册ActivityLifecycleCallbacks的。
带你读《Android全埋点解决方案》之二:$AppViewScreen全埋点方案

@TargetApi(14)
public static void registerActivityLifecycleCallbacks(Application application) {

application.registerActivityLifecycleCallbacks(new Application.Activity-LifecycleCallbacks() {
    @Override
    public void onActivityCreated(final Activity activity, Bundle bundle) {
    }
    @Override
    public void onActivityStarted(Activity activity) {

    }

    @Override
    public void onActivityResumed(final Activity activity) {
        trackAppViewScreen(activity);
     }

    @Override
    public void onActivityPaused(Activity activity) {
    }

    @Override
    public void onActivityStopped(Activity activity) {
    }

    @Override
    public void onActivitySaveInstanceState(Activity activity, Bundle bundle) {
    }

    @Override
    public void onActivityDestroyed(Activity activity) {
    }
});

}
需要我们注意的是,只有 API 14+ 才能注册ActivityLifecycleCallbacks回调。
在ActivityLifecycleCallbacks的onActivityResumed(final Activity activity)回调方法中,我们通过调用SensorsDataPrivate的trackAppViewScreen(Activity activity)方法来触发页面浏览事件($AppViewScreen)。
trackAppViewScreen(Activity activity)方法的内部实现逻辑比较简单,可以参考如下:
带你读《Android全埋点解决方案》之二:$AppViewScreen全埋点方案

@Keep
private static void trackAppViewScreen(Activity activity) {

try {
    JSONObject properties = new JSONObject();
    properties.put("$activity", activity.getClass().getCanonicalName());
    SensorsDataAPI.getInstance().track("$AppViewScreen", properties);
} catch (Exception e) {
    e.printStackTrace();
}

}
在此示例中,我们添加了一个$activity 属性,代表当前 Activity 的名称,我们使用包名+类名的形式表示。然后又定义了事件名称为“$AppViewScreen”,最后调用Sensors-DataAPI的 track 方法来触发页面浏览事件。
第6步:初始化埋点 SDK
需要在应用程序自定义的 Application类中初始化埋点 SDK,一般是建议在 onCreate()方法中初始化。
package com.sensorsdata.analytics.android.app;

import android.app.Application;

import com.sensorsdata.analytics.android.sdk.SensorsDataAPI;

public class MyApplication extends Application {

@Override
public void onCreate() {
    super.onCreate();
    initSensorsDataAPI(this);
}

带你读《Android全埋点解决方案》之二:$AppViewScreen全埋点方案

private void initSensorsDataAPI(Application application) {
    SensorsDataAPI.init(application);
}

}
第7步:声明自定义的 Application
以上面定义的MyApplication为例,需要在AndroidManifest.xml文件的application节点中声明MyApplication。
<?xml version="1.0" encoding="utf-8"?>
带你读《Android全埋点解决方案》之二:$AppViewScreen全埋点方案
带你读《Android全埋点解决方案》之二:$AppViewScreen全埋点方案

运行 demo并启动一个 Activity,可以看到如下打印的事件信息,参考图2-1。

带你读《Android全埋点解决方案》之二:$AppViewScreen全埋点方案

上面的事件名称叫“$AppViewScreen”,代表的是页面浏览事件,它有一个自定义属性,叫“$activity”,代表当前正在显示的 Activity 名称(包名+类名)。
至此,页面浏览事件($AppViewScreen)的全埋点方案就算完成了。

2.4 完善方案

在Android 6.0(API 23)发布的同时又引入了一种新的权限机制,即Runtime Permissions,又称运行时权限。
在一般情况下,我们如果要使用 Runtime Permissions主要分为四个步骤,下面我们以使用(申请)“android.permission.READ_CONTACTS”权限为例来介绍。
第1步:声明权限
需要在AndroidManifest.xml文件中使用uses-permission声明应用程序要使用的权限列表。
<?xml version="1.0" encoding="utf-8"?>

package="com.sensorsdata.analytics.android.app">

<uses-permission android:name="android.permission.READ_CONTACTS" />

<application
    android:name=".MyApplication"
    android:allowBackup="true"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    android:roundIcon="@mipmap/ic_launcher_round"
    android:supportsRtl="true"
    android:theme="@style/AppTheme">
    <activity android:name=".MainActivity">
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />

            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>
</application>


第2步:检查权限
如果应用程序需要使用 READ_CONTACTS 权限,则要在每次真正使用 READ_CONTACTS 权限之前,检测当前应用程序是否已经拥有该权限,这是因为用户可能随时会在Android 系统的设置中关掉授予当前应用程序的任何权限。检测权限可以使用ContextCompat的checkSelfPermission方法,简单示例如下:
if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS) ==
    PackageManager.PERMISSION_GRANTED) {
//拥有权限

} else {

//没有权限,需要申请权限

}
其中,PackageManager.PERMISSION_GRANTED代表当前应用程序已经拥有了该权限;反之,PackageManager.PERMISSION_DENIED 代表当前应用程序没有获得该权限,需要再次申请。
第3步:申请权限
可以通过调用ActivityCompat的requestPermissions方法来申请一个或者一组权限,简单示例如下:
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_CONTACTS},

                   PERMISSIONS_REQUEST_READ_CONTACTS);

调用ActivityCompat.requestPermissions方法之后,系统会弹出如图2-2的请求权限对话框(该对话框可能会随着 ROM的不同而略有差异):

带你读《Android全埋点解决方案》之二:$AppViewScreen全埋点方案

第4步:处理权限请求结果
用户选择之后的结果会回调当前 Activity的onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults)方法,我们可以根据 requestCode和grantResults参数来判断用户选择了“允许”还是“禁止”按钮。
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {

switch (requestCode) {
    case PERMISSIONS_REQUEST_READ_CONTACTS:
        if (grantResults.length > 0 &&
                grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            //用户点击允许
        } else {
            //用户点击禁止
        }
        break;
}
super.onRequestPermissionsResult(requestCode, permissions, grantResults);

}
讲到这里,你肯定开始疑惑了,这跟采集页面浏览事件有什么关系呢?
其实是有关系的!我们继续往下看。
通过测试可以发现,我们调用ActivityCompat.requestPermissions方法申请权限之后,不管用户选择了“允许”还是“禁止”按钮,系统都会先调用onRequestPermissionsResult回调方法,然后再调用当前 Activity 的 onResume 生命周期函数。而我们上面介绍的,就是通过 onResume生命周期函数来采集页面浏览事件的,这个现象会直接导致我们的埋点 SDK 再一次触发页面浏览事件。
对于这个问题,我们该如何解决呢?事实上,虽然目前也没有非常完美的解决方案,但是我们还是可以借助其他方法来尝试解决。毕竟,在一个完整的应用程序中,真正需要申请权限的页面并不是很多。所以,我们可以在这些申请权限的页面里进行一些特殊的“操作”来规避上面的问题。
我们可以考虑给埋点 SDK 新增一个功能,即用户可以设置想要过滤哪些 Activity 的页面浏览事件(即指定不采集哪些 Activity 的页面浏览事件),然后通过灵活使用这个接口,解决上面的问题。
下面我们详细地介绍一下具体的实现步骤。
第1步:在SensorsDataAPI中新增两个接口
带你读《Android全埋点解决方案》之二:$AppViewScreen全埋点方案

□ignoreAutoTrackActivity(Class<?> activity)
指定忽略采集哪个 Activity 的页面浏览事件。
□removeIgnoredActivity(Class<?> activity)
指定恢复采集哪个 Activity 的页面浏览事件。
以上两个接口,都是调用私有类SensorsDataPrivate中相对应的方法。
package com.sensorsdata.analytics.android.sdk;

......

带你读《Android全埋点解决方案》之二:$AppViewScreen全埋点方案

static {
    mIgnoredActivities = new ArrayList<>();
}
public static void ignoreAutoTrackActivity(Class<?> activity) {
    if (activity == null) {
        return;
    }

    mIgnoredActivities.add(activity.getClass().getCanonicalName());
}

public static void removeIgnoredActivity(Class<?> activity) {
    if (activity == null) {
        return;
    }

    if (mIgnoredActivities.contains(activity.getClass().getCanonicalName())) {
        mIgnoredActivities.remove(activity.getClass().getCanonicalName());
    }
}

......
}
内部实现机制比较简单,仅仅通过定义一个List来保存忽略采集页面浏览事件的 Activity 的名称(包名+类名)。
第2步:修改trackAppViewScreen(Activity activity)方法添加相应的判断逻辑
带你读《Android全埋点解决方案》之二:$AppViewScreen全埋点方案

@Keep
private static void trackAppViewScreen(Activity activity) {

try {
    if (activity == null) {
        return;
    }
    if (mIgnoredActivities.contains(activity.getClass().getCanonicalName())) {
        return;
    }
    JSONObject properties = new JSONObject();
    properties.put("$activity", activity.getClass().getCanonicalName());
    SensorsDataAPI.getInstance().track("$AppViewScreen", properties);
} catch (Exception e) {
    e.printStackTrace();
}

}
首先判断当前Activity是否已经被忽略,如果被忽略,则不触发页面浏览事件,否则将触发页面浏览事件。
第3步:修改申请权限的 Activity
在申请权限的 Activity中,在它的onRequestPermissionsResult回调中首先调用ignoreAutoTrackActivity方法来忽略当前 Activity 的页面浏览事件,然后在 onStop 生命周期函数中恢复采集当前 Activity 的页面浏览事件。
package com.sensorsdata.analytics.android.app;

import android.Manifest;
import android.content.pm.PackageManager;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

import com.sensorsdata.analytics.android.sdk.SensorsDataAPI;

public class MainActivity extends AppCompatActivity {

private final static int PERMISSIONS_REQUEST_READ_CONTACTS = 100;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    setTitle("Home");

    if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS) ==
            PackageManager.PERMISSION_GRANTED) {
        //拥有权限
    } else {
        //没有权限,需要申请全新啊
        ActivityCompat.requestPermissions(this, new String[]{Manifest.permission. READ_CONTACTS},
                PERMISSIONS_REQUEST_READ_CONTACTS);
    }
}

@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
    SensorsDataAPI.getInstance().ignoreAutoTrackActivity(MainActivity.class);
    switch (requestCode) {
        case PERMISSIONS_REQUEST_READ_CONTACTS:
            if (grantResults.length > 0 &&

grantResults[0] == PackageManager.PERMISSION_GRANTED) {

                // 用户点击允许
            } else {
                // 用户点击禁止
            }
            break;
    }

    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}

@Override
protected void onStop() {
    super.onStop();
    SensorsDataAPI.getInstance().removeIgnoredActivity(MainActivity.class);
}

}
这样处理之后,就可以解决申请权限再次触发页面浏览事件的问题了。

2.5 扩展采集能力

对于Activity的页面浏览事件,仅仅采集当前 Activity 的名称(包名 + 类名)是远远不够的,还需要采集当前 Activity 的 title(标题)才能满足实际的分析需求。
但是一个 Activity 的 title 的来源是非常复杂的,因为可以通过不同的方式来设置一个 Activity 的 title,甚至可以使用自定义的 View 来设置 title。比如说,可以在Android-Manifest.xml文件中声明 activity 时通过 android:label属性来设置,还可以通过 activity.setTitle()来设置,也可以通过 ActionBar、ToolBar 来设置。所以,在获取Activity 的 title 时,需要兼容不同的设置title的方式,同时更需要考虑其优先级顺序。
我们目前写了一个比较简单的方法来获取一个 Activity 的 title,内容参考如下:
带你读《Android全埋点解决方案》之二:$AppViewScreen全埋点方案

public static String getActivityTitle(Activity activity) {

String activityTitle = null;

if (activity == null) {
    return null;
}

try {
    activityTitle = activity.getTitle().toString();

    if (Build.VERSION.SDK_INT >= 11) {
        String toolbarTitle = getToolbarTitle(activity);
        if (!TextUtils.isEmpty(toolbarTitle)) {
            activityTitle = toolbarTitle;
        }
    }

    if (TextUtils.isEmpty(activityTitle)) {
        PackageManager packageManager = activity.getPackageManager();
        if (packageManager != null) {
            ActivityInfo activityInfo = packageManager.getActivityInfo(activity.getComponentName(), 0);
            if (activityInfo != null) {
                activityTitle = activityInfo.loadLabel(packageManager).toString();
            }
        }
    }
} catch (Exception e) {
    e.printStackTrace();
}
return activityTitle;

}
我们首先通过activity.getTitle() 获取当前 Activity 的 title,因为用户有可能会使用 ActionBar 或 ToolBar,所以我们还需要获取 ActionBar 或 ToolBar 设置的 title,如果能获取到,就以这个为准(即覆盖通过activity.getTitle()获取的 title)。如果以上两个步骤都没有获取到 title,那我们就要尝试获取 android:label 属性的值。
获取ActionBar或ToolBar的title逻辑如下:
@TargetApi(11)
private static String getToolbarTitle(Activity activity) {

try {
    ActionBar actionBar = activity.getActionBar();
    if (actionBar != null) {
        if (!TextUtils.isEmpty(actionBar.getTitle())) {
            return actionBar.getTitle().toString();
        }
    } else {
        if (activity instanceof AppCompatActivity) {
            AppCompatActivity appCompatActivity = (AppCompatActivity) activity;
            android.support.v7.app.ActionBar supportActionBar = appCompat-Activity.getSupportActionBar();
            if (supportActionBar != null) {
                if (!TextUtils.isEmpty(supportActionBar.getTitle())) {
                    return supportActionBar.getTitle().toString();
                }
            }
        }
    }
} catch (Exception e) {
    e.printStackTrace();
}
return null;

}
修改trackAppViewScreen(Activity activity)方法,添加设置$title 属性的逻辑:
带你读《Android全埋点解决方案》之二:$AppViewScreen全埋点方案

@Keep
private static void trackAppViewScreen(Activity activity) {

try {
    if (activity == null) {
        return;
    }
    if (mIgnoredActivities.contains(activity.getClass().hashCode())) {
        return;
    }
    JSONObject properties = new JSONObject();
    properties.put("$activity", activity.getClass().getCanonicalName());
    properties.put("$title", getActivityTitle(activity));
    SensorsDataAPI.getInstance().track("$AppViewScreen", properties);
} catch (Exception e) {
    e.printStackTrace();
}

}
运行 demo,可以看到打印的如下事件信息,参考图2-3。

带你读《Android全埋点解决方案》之二:$AppViewScreen全埋点方案

至此,一个相对完善的用来采集页面浏览事件的全埋点方案就算完成了。

上一篇:人工智能医疗取得突破性进展:AI阅片在临床铺开


下一篇:中日韩消费者对智能家居认知数据调研