《Android全埋点解决方案》 —3.2 案例

网友投稿 935 2022-05-30

3.2 案例

针对上面介绍的原理,接下来我们将详细介绍如何实现$AppStart和$AppEnd 事件的全埋点方案。

完整的项目源码可以参考:https://github.com/wangzhzh/AutoTrackAppStartAppEnd。

第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.startend"

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')

}

第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;

/**

* Created by 王灼洲 on 2018/7/22

*/

@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 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);

SensorsDataPrivate.registerActivityStateObserver(application);

}

/**

* track 事件

*

* @param eventName  String 事件名称

* @param properties JSONObject 事件自定义属性

*/

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()

这也是一个静态方法,通过该方法可以获取埋点 SDK 的实例对象。

SensorsDataAPI(Application application)

私有的构造函数,也是埋点 SDK 真正的初始化逻辑。在其方法内部通过调用 SDK 的内部私有类SensorsDataPrivate中的方法来注册ActivityLifecycleCallbacks,并给 Content-Provider 注册一个ContentObserver。

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)等方法实现可以参考工程的源码。

第5步:注册 ActivityLifecycleCallbacks回调

我们是通过调用埋点 SDK 的内部私有类SensorsDataPrivate的registerActivityLifecycleCallbacks(Application application)方法来注册ActivityLifecycleCallbacks的。

/**

* 注册 Application.ActivityLifecycleCallbacks

*

* @param application Application

*/

@TargetApi(14)

public static void registerActivityLifecycleCallbacks(Application application) {

mDatabaseHelper = new DatabaseHelper(application.getApplicationContext(), application.getPackageName());

countDownTimer = new CountDownTimer(SESSION_INTERVAL_TIME, 10 * 1000) {

@Override

public void onTick(long l) {

}

@Override

public void onFinish() {

trackAppEnd(mCurrentActivity.get());

}

};

application.registerActivityLifecycleCallbacks(new Application.ActivityLifecycle-Callbacks() {

@Override

public void onActivityCreated(Activity activity, Bundle bundle) {

}

@Override

public void onActivityStarted(Activity activity) {

mDatabaseHelper.commitAppStart(true);

double timeDiff = System.currentTimeMillis() - mDatabaseHelper.getAppPausedTime();

if (timeDiff > 30 * 1000) {

if (!mDatabaseHelper.getAppEndEventState()) {

trackAppEnd(activity);

}

}

if (mDatabaseHelper.getAppEndEventState()) {

mDatabaseHelper.commitAppEndEventState(false);

trackAppStart(activity);

}

}

@Override

public void onActivityResumed(Activity activity) {

trackAppViewScreen(activity);

}

@Override

public void onActivityPaused(Activity activity) {

mCurrentActivity = new WeakReference<>(activity);

countDownTimer.start();

mDatabaseHelper.commitAppPausedTime(System.currentTimeMillis());

}

@Override

public void onActivityStopped(Activity activity) {

}

@Override

public void onActivitySaveInstanceState(Activity activity, Bundle bundle) {

}

@Override

public void onActivityDestroyed(Activity activity) {

}

});

}

首先初始化一个SensorsDatabaseHelper对象,这个主要是用来操作 ContentProvider 的,然后再初始化一个30s的计时器 CountDownTimer对象,当计时器 finish 的时候,会触发$AppEnd 事件。最后注册Application.ActivityLifecycleCallbacks回调。

在Application.ActivityLifecycleCallbacks 的onActivityStarted(Activity activity)回调方法中,首先修改 AppStart 的标记位,这样之前注册的 ContentObserver 就能收到通知并取消掉 CountDownTimer计时器。然后判断一下当前页面与上个页面退出时间的间隔是否超出了 30s,如果超出了 30s,并且没有触发过$AppEnd 事件(应用程序发生崩溃或者应用程序被强杀等场景),则补发$AppEnd 事件。如果触发了$AppEnd 事件,说明是一个新的 Session 开始了,需要触发$AppStart 事件。

在onActivityResumed(Activity activity)回调方法中,会直接触发$AppViewScreen 页面浏览事件。

在onActivityPaused(Activity activity)回调方法中,启动 CountDownTimer计时器,并且保存当前页面退出时的时间戳。

第6步:定义SensorsDatabaseHelper

package com.sensorsdata.analytics.android.sdk;

import android.content.ContentResolver;

import android.content.ContentValues;

import android.content.Context;

import android.database.Cursor;

import android.net.Uri;

/*public*/ class SensorsDatabaseHelper {

private static final String SensorsDataContentProvider = ".SensorsData-ContentProvider/";

private ContentResolver mContentResolver;

《Android全埋点解决方案》 —3.2 案例

private Uri mAppStart;

private Uri mAppEndState;

private Uri mAppPausedTime;

public static final String APP_STARTED = "$app_started";

public static final String APP_END_STATE = "$app_end_state";

public static final String APP_PAUSED_TIME = "$app_paused_time";

SensorsDatabaseHelper(Context context, String packageName) {

mContentResolver = context.getContentResolver();

mAppStart = Uri.parse("content://" + packageName + SensorsDataContentProvider + SensorsDataTable.APP_STARTED.getName());

mAppEndState = Uri.parse("content://" + packageName + SensorsDataContentProvider + SensorsDataTable.APP_END_STATE.getName());

mAppPausedTime = Uri.parse("content://" + packageName + SensorsDataContentProvider + SensorsDataTable.APP_PAUSED_TIME.getName());

}

/**

* Add the AppStart state to the SharedPreferences

*

* @param appStart the ActivityState

*/

public void commitAppStart(boolean appStart) {

ContentValues contentValues = new ContentValues();

contentValues.put(APP_STARTED, appStart);

mContentResolver.insert(mAppStart, contentValues);

}

/**

* Add the Activity paused time to the SharedPreferences

*

* @param pausedTime Activity paused time

*/

public void commitAppPausedTime(long pausedTime) {

ContentValues contentValues = new ContentValues();

contentValues.put(APP_PAUSED_TIME, pausedTime);

mContentResolver.insert(mAppPausedTime, contentValues);

}

/**

* Return the time of Activity paused

*

* @return Activity paused time

*/

public long getAppPausedTime() {

long pausedTime = 0;

Cursor cursor = mContentResolver.query(mAppPausedTime, new String[]{APP_

PAUSED_TIME}, null, null, null);

if (cursor != null && cursor.getCount() > 0) {

while (cursor.moveToNext()) {

pausedTime = cursor.getLong(0);

}

}

if (cursor != null) {

cursor.close();

}

return pausedTime;

}

/**

* Add the Activity End to the SharedPreferences

*

* @param appEndState the Activity end state

*/

public void commitAppEndEventState(boolean appEndState) {

ContentValues contentValues = new ContentValues();

contentValues.put(APP_END_STATE, appEndState);

mContentResolver.insert(mAppEndState, contentValues);

}

/**

* Return the state of $AppEnd

*

* @return Activity End state

*/

public boolean getAppEndEventState() {

boolean state = true;

Cursor cursor = mContentResolver.query(mAppEndState, new String[]{APP_

END_STATE}, null, null, null);

if (cursor != null && cursor.getCount() > 0) {

while (cursor.moveToNext()) {

state = cursor.getInt(0) > 0;

}

}

if (cursor != null) {

cursor.close();

}

return state;

}

public Uri getAppStartUri() {

return mAppStart;

}

}

这个工具类主要是用来操作 ContentProvider 用来保存相关的数据和标记位。

第7步:定义SensorsDataContentProvider

package com.sensorsdata.analytics.android.sdk;

import android.content.ContentProvider;

import android.content.ContentResolver;

import android.content.ContentValues;

import android.content.Context;

import android.content.SharedPreferences;

import android.content.UriMatcher;

import android.database.Cursor;

import android.database.MatrixCursor;

import android.net.Uri;

import android.support.annotation.NonNull;

import android.support.annotation.Nullable;

public class SensorsDataContentProvider extends ContentProvider {

private final static int APP_START = 1;

private final static int APP_END_STATE = 2;

private final static int APP_PAUSED_TIME = 3;

private static SharedPreferences sharedPreferences;

private static SharedPreferences.Editor mEditor;

private static UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);

private ContentResolver mContentResolver;

@Override

public boolean onCreate() {

if (getContext() != null) {

String packName = getContext().getPackageName();

uriMatcher.addURI(packName + ".SensorsDataContentProvider", Sensors-DataTable.APP_STARTED.getName(), APP_START);

uriMatcher.addURI(packName + ".SensorsDataContentProvider", Sensors-DataTable.APP_END_STATE.getName(), APP_END_STATE);

uriMatcher.addURI(packName + ".SensorsDataContentProvider", Sensors-DataTable.APP_PAUSED_TIME.getName(), APP_PAUSED_TIME);

sharedPreferences = getContext().getSharedPreferences("com.sensorsdata. analytics.android.sdk.SensorsDataAPI", Context.MODE_PRIVATE);

mEditor = sharedPreferences.edit();

mEditor.apply();

mContentResolver = getContext().getContentResolver();

}

return false;

}

@Nullable

@Override

public Uri insert(@NonNull Uri uri, @Nullable ContentValues contentValues) {

if (contentValues == null) {

return uri;

}

int code = uriMatcher.match(uri);

switch (code) {

case APP_START:

boolean appStart = contentValues.getAsBoolean(SensorsDatabaseHelper.APP_STARTED);

mEditor.putBoolean(SensorsDatabaseHelper.APP_STARTED, appStart);

mContentResolver.notifyChange(uri, null);

break;

case APP_END_STATE:

boolean appEnd = contentValues.getAsBoolean(SensorsDatabaseHelper.APP_END_STATE);

mEditor.putBoolean(SensorsDatabaseHelper.APP_END_STATE, appEnd);

break;

case APP_PAUSED_TIME:

long pausedTime = contentValues.getAsLong(SensorsDatabaseHelper.APP_PAUSED_TIME);

mEditor.putLong(SensorsDatabaseHelper.APP_PAUSED_TIME, pausedTime);

break;

}

mEditor.commit();

return uri;

}

@Nullable

@Override

public Cursor query(@NonNull Uri uri, @Nullable String[] strings, @Nullable String s, @Nullable String[] strings1, @Nullable String s1) {

int code = uriMatcher.match(uri);

MatrixCursor matrixCursor = null;

switch (code) {

case APP_START:

int appStart = sharedPreferences.getBoolean(SensorsDatabaseHelper.APP_STARTED, true) ? 1 : 0;

matrixCursor = new MatrixCursor(new String[]{SensorsDatabase-Helper.APP_STARTED});

matrixCursor.addRow(new Object[]{appStart});

break;

case APP_END_STATE:

int appEnd = sharedPreferences.getBoolean(SensorsDatabaseHelper.APP_END_STATE, true) ? 1 : 0;

matrixCursor = new MatrixCursor(new String[]{SensorsDatabase-Helper.APP_END_STATE});

matrixCursor.addRow(new Object[]{appEnd});

break;

case APP_PAUSED_TIME:

long pausedTime = sharedPreferences.getLong(SensorsDatabase-Helper.APP_PAUSED_TIME, 0);

matrixCursor = new MatrixCursor(new String[]{SensorsDatabase-Helper.APP_PAUSED_TIME});

matrixCursor.addRow(new Object[]{pausedTime});

break;

}

return matrixCursor;

}

@Nullable

@Override

public String getType(@NonNull Uri uri) {

return null;

}

@Override

public int delete(@NonNull Uri uri, @Nullable String s, @Nullable String[] strings) {

return 0;

}

@Override

public int update(@NonNull Uri uri, @Nullable ContentValues contentValues, @Nullable String s, @Nullable String[] strings) {

return 0;

}

}

实现了一个ContentProvider,通过操作SharedPreferences来保存数据,可以解决多进程间共享数据的问题,同时也能做到快速读写,提升效率。

SensorsDataTable的定义如下:

package com.sensorsdata.analytics.android.sdk;

/*public*/ enum SensorsDataTable {

APP_STARTED("app_started"),

APP_PAUSED_TIME("app_paused_time"),

APP_END_STATE("app_end_state");

SensorsDataTable(String name) {

this.name = name;

}

public String getName() {

return name;

}

private String name;

}

第8步:初始化埋点 SDK

需要在应用程序自定义的 Application (比如叫 MyApplication)类中初始化 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);

}

/**

* 初始化埋点 SDK

*

* @param application Application

*/

private void initSensorsDataAPI(Application application) {

SensorsDataAPI.init(application);

}

}

第9步:声明自定义的 Application

以上面定义的 MyApplication 为例,需要在AndroidManifest.xml文件的 application 节点中声明 MyApplication。

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

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">

至此,$AppStart 和$AppEnd 事件的全埋点方案就算完成了。

Android

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:Reactive 模式优势与实战
下一篇:如何整合hive和hbase
相关文章