Android 全埋点解决方案 (8):$AppViewScreen 全埋点方案 2.4

阅读数:6 2019 年 11 月 30 日 22:55

Android全埋点解决方案(8):$AppViewScreen全埋点方案 2.4

(完善方案)

内容简介
这是一本实战为导向的、翔实的 Android 全埋点技术与解决方案手册,是国内知名大数据公司神策数据在该领域多年实践经验的总结。由神策数据合肥研发中心负责人亲自执笔,他在 Android 领域有近 10 年研发经验,开发和维护着知名的商用开源 Android & iOS 数据埋点 SDK。
本书详细阐述了 Android 全埋点的 8 种解决方案,涵盖各种场景,从 0 到 1 详解技术原理和实现步骤,并且提供完整的源代码,各级研发工程师均可借此实现全埋点数据采集,为市场解开全埋点的神秘面纱。
8 种 Android 全埋点解决方案包括:
AppClick 全埋点方案 1:代理 View.OnClickListener、
AppClick 全埋点方案 2:代理 Window.Callback
AppClick 全埋点方案 3:代理 View.AccessibilityDelegate
AppClick 全埋点方案 4:透明层
AppClick 全埋点方案 5:AspectJ
AppClick 全埋点方案 6:ASM
AppClick 全埋点方案 7:Javassist
AppClick 全埋点方案 8:AST

在 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"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
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>
</manifest>

第 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全埋点解决方案(8):$AppViewScreen全埋点方案 2.4

图 2-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 中新增两个接口

复制代码
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 {
......
{1}
/**
* 指定不采集哪个 Activity 的页面浏览事件
*
* @param activity Activity
*/
public void ignoreAutoTrackActivity(Class<?> activity) {
SensorsDataPrivate.ignoreAutoTrackActivity(activity);
}
{1}
/**
* 恢复采集某个 Activity 的页面浏览事件
*
* @param activity Activity
*/
public void removeIgnoredActivity(Class<?> activity) {
SensorsDataPrivate.removeIgnoredActivity(activity);
}
{1}
......
}
{1}
  • ignoreAutoTrackActivity(Class<?> activity)

指定忽略采集哪个 Activity 的页面浏览事件。

  • removeIgnoredActivity(Class<?> activity)

指定恢复采集哪个 Activity 的页面浏览事件。

以上两个接口,都是调用私有类 SensorsDataPrivate 中相对应的方法。

复制代码
package com.sensorsdata.analytics.android.sdk;
......
/*public*/ class SensorsDataPrivate {
private static List<String> mIgnoredActivities;
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) 方法添加相应的判断逻辑

复制代码
/**
* Track 页面浏览事件
*
* @param activity Activity
*/
@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);
}
}

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

Android全埋点解决方案(8):$AppViewScreen全埋点方案 2.4

购书地址 https://item.jd.com/12574672.html?dist=jd

评论

发布