天天看點

帶你讀《Android全埋點解決方案》之二:$AppViewScreen全埋點方案第2章

點選檢視第一章 點選檢視第三章

第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全埋點方案第2章

@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全埋點方案第2章
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)方法實作可以參考如下源碼:

import android.annotation.SuppressLint;

import android.annotation.TargetApi;

import android.app.ActionBar;

import android.app.Activity;

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.v7.app.AppCompatActivity;

import android.text.TextUtils;

import android.util.DisplayMetrics;

import org.json.JSONException;

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;

帶你讀《Android全埋點解決方案》之二:$AppViewScreen全埋點方案第2章
帶你讀《Android全埋點解決方案》之二:$AppViewScreen全埋點方案第2章
帶你讀《Android全埋點解決方案》之二:$AppViewScreen全埋點方案第2章
帶你讀《Android全埋點解決方案》之二:$AppViewScreen全埋點方案第2章
帶你讀《Android全埋點解決方案》之二:$AppViewScreen全埋點方案第2章
帶你讀《Android全埋點解決方案》之二:$AppViewScreen全埋點方案第2章
帶你讀《Android全埋點解決方案》之二:$AppViewScreen全埋點方案第2章

第5步:注冊 ActivityLifecycleCallbacks回調

我們是通過調用 SDK 的内部私有類SensorsDataPrivate的registerActivityLifecycleCallbacks(Application application)方法來注冊ActivityLifecycleCallbacks的。

帶你讀《Android全埋點解決方案》之二:$AppViewScreen全埋點方案第2章

@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全埋點方案第2章

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 com.sensorsdata.analytics.android.sdk.SensorsDataAPI;

public class MyApplication extends Application {

@Override
public void onCreate() {
    super.onCreate();
    initSensorsDataAPI(this);
}
           
帶你讀《Android全埋點解決方案》之二:$AppViewScreen全埋點方案第2章
private void initSensorsDataAPI(Application application) {
    SensorsDataAPI.init(application);
}           

第7步:聲明自定義的 Application

以上面定義的MyApplication為例,需要在AndroidManifest.xml檔案的application節點中聲明MyApplication。

<?xml version="1.0" encoding="utf-8"?>

帶你讀《Android全埋點解決方案》之二:$AppViewScreen全埋點方案第2章
帶你讀《Android全埋點解決方案》之二:$AppViewScreen全埋點方案第2章

運作 demo并啟動一個 Activity,可以看到如下列印的事件資訊,參考圖2-1。

帶你讀《Android全埋點解決方案》之二:$AppViewScreen全埋點方案第2章

上面的事件名稱叫“$AppViewScreen”,代表的是頁面浏覽事件,它有一個自定義屬性,叫“$activity”,代表目前正在顯示的 Activity 名稱(包名+類名)。

至此,頁面浏覽事件($AppViewScreen)的全埋點方案就算完成了。

2.4 完善方案

在Android 6.0(API 23)釋出的同時又引入了一種新的權限機制,即Runtime Permissions,又稱運作時權限。

在一般情況下,我們如果要使用 Runtime Permissions主要分為四個步驟,下面我們以使用(申請)“android.permission.READ_CONTACTS”權限為例來介紹。

第1步:聲明權限

需要在AndroidManifest.xml檔案中使用uses-permission聲明應用程式要使用的權限清單。

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全埋點方案第2章

第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全埋點方案第2章

□ignoreAutoTrackActivity(Class<?> activity)

指定忽略采集哪個 Activity 的頁面浏覽事件。

□removeIgnoredActivity(Class<?> activity)

指定恢複采集哪個 Activity 的頁面浏覽事件。

以上兩個接口,都是調用私有類SensorsDataPrivate中相對應的方法。

......

帶你讀《Android全埋點解決方案》之二:$AppViewScreen全埋點方案第2章
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全埋點方案第2章
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 的頁面浏覽事件。

import android.Manifest;

import android.support.v4.app.ActivityCompat;

import android.support.v4.content.ContextCompat;

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全埋點方案第2章

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全埋點方案第2章
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全埋點方案第2章

至此,一個相對完善的用來采集頁面浏覽事件的全埋點方案就算完成了。

繼續閱讀