$AppViewScreen全埋点方案
2019-05-01 本文已影响71人
Peakmain
参考书籍:《Android全埋点解决方案》
背景
$AppViewScreen事件,即页面浏览事件。在Android中页面浏览实际指的就是切换不同的Activity或者Fragment
-
Activity的生命周期
文章:https://www.jianshu.com/p/48cb882b8717
生命周期图
image.png
我们会发现,一个activity生命周期中onResume执行了代表该页面被显示出来了,也就是该页面被浏览了,所以事件onResume里触发$AppViewScreen -
关键技术
Application.ActivityLifecycleCallbacks
原理:调用Application中的registerActivityLifecycleCallbacks方法,在onActivityResumed(Activity activity)方法中拿到正在显示的activity对象
权限6.0
使用
if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_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) {
AppViewScreenDataAPI.getInstance().ignoreAutoTrackActivity(MainActivity.class);
switch (requestCode) {
case PERMISSIONS_REQUEST_READ_CONTACTS:
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// 用户点击允许
} else {
// 用户点击禁止
}
break;
default:
break;
}
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
- 源码分析
ActivityCompat.requestPermissions源码分析
public static void requestPermissions(final @NonNull Activity activity,
final @NonNull String[] permissions, final @IntRange(from = 0) int requestCode) {
//代码省略
if (Build.VERSION.SDK_INT >= 23) {
//实际会调用 onRequestPermissionsResult和Activity的startActivityForResult方法,随即会走activity的OnResume方法
activity.requestPermissions(permissions, requestCode);
} else if (activity instanceof OnRequestPermissionsResultCallback) {
//代码省略
}
}
public final void requestPermissions(@NonNull String[] permissions, int requestCode) {
if (mHasCurrentPermissionsRequest) {
onRequestPermissionsResult(requestCode, new String[0], new int[0]);
return;
}
Intent intent = getPackageManager().buildRequestPermissionsIntent(permissions);
startActivityForResult(REQUEST_PERMISSIONS_WHO_PREFIX, intent, requestCode, null);
mHasCurrentPermissionsRequest = true;
}
用户无论点击同意还是拒绝实际都会调用 onRequestPermissionsResult和Activity的startActivityForResult方法,随即会走activity的OnResume方法,所以在埋点的时候需要避免多次调用onResume方法
- 思路
onRequestPermissionsResult的时候忽略某个activity,onStop的时候恢复activity
实现方案
-
效果
image.png
-
AppViewScreenDataAPI
初始化数据,获取设备id,设备信息
private final String TAG = this.getClass().getSimpleName();
public static final String SDK_VERSION = "1.0.0";
private static AppViewScreenDataAPI INSTANCE;
private static final Object mLock = new Object();
private static Map<String, Object> mDeviceInfo;
private String mDeviceId;
private AppViewScreenDataAPI(Application application) {
mDeviceId = AppViewScreenDataPrivate.getAndroidID(application.getApplicationContext());
mDeviceInfo = AppViewScreenDataPrivate.getDeviceInfo(application.getApplicationContext());
AppViewScreenDataPrivate.registerActivityLifecycleCallbacks(application);
}
@Keep
public static AppViewScreenDataAPI init(Application application) {
if (INSTANCE == null) {
synchronized (mLock) {
if (null == INSTANCE) {
INSTANCE = new AppViewScreenDataAPI(application);
}
}
}
return INSTANCE;
}
@Keep
public static AppViewScreenDataAPI getInstance() {
return INSTANCE;
}
指定不采集哪个 Activity 的页面浏览事件
public void ignoreAutoTrackActivity(Class<?> activity) {
AppViewScreenDataPrivate.ignoreAutoTrackActivity(activity);
}
恢复采集某个 Activity 的页面浏览事件
public void removeIgnoredActivity(Class<?> activity) {
AppViewScreenDataPrivate.removeIgnoredActivity(activity);
}
事件
/**
* 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) {
AppViewScreenDataPrivate.mergeJSONObject(properties, sendProperties);
}
jsonObject.put("properties", sendProperties);
jsonObject.put("time", System.currentTimeMillis());
//打印数据
Log.i(TAG, AppViewScreenDataPrivate.formatJson(jsonObject.toString()));
} catch (Exception e) {
e.printStackTrace();
}
}
- AppViewScreenDataPrivate具体实现类
初始化
private static List<String> mIgnoredActivities;
static {
mIgnoredActivities = new ArrayList<>();
}
private static final SimpleDateFormat mDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"
+ ".SSS", Locale.CHINA);
指定不采集哪个 Activity 的页面浏览事件
public static void ignoreAutoTrackActivity(Class<?> activity) {
if (activity == null) {
return;
}
mIgnoredActivities.add(activity.getClass().getCanonicalName());
}
恢复采集某个 Activity 的页面浏览事件
public static void removeIgnoredActivity(Class<?> activity) {
if (activity == null) {
return;
}
if (mIgnoredActivities.contains(activity.getClass().getCanonicalName())) {
mIgnoredActivities.remove(activity.getClass().getCanonicalName());
}
}
获取 Android ID
public static String getAndroidID(Context mContext) {
String androidID = "";
try {
androidID = Settings.Secure.getString(mContext.getContentResolver(), Settings.Secure.ANDROID_ID);
} catch (Exception e) {
e.printStackTrace();
}
return androidID;
}
获取设备信息
public static Map<String, Object> getDeviceInfo(Context context) {
final Map<String, Object> deviceInfo = new HashMap<>();
deviceInfo.put("$lib", "Android");
deviceInfo.put("$lib_version", AppViewScreenDataAPI.SDK_VERSION);
deviceInfo.put("$os", "Android");
deviceInfo.put("$os_version",
Build.VERSION.RELEASE == null ? "UNKNOWN" : Build.VERSION.RELEASE);
deviceInfo
.put("$manufacturer", Build.MANUFACTURER == null ? "UNKNOWN" : Build.MANUFACTURER);
if (TextUtils.isEmpty(Build.MODEL)) {
deviceInfo.put("$model", "UNKNOWN");
} else {
deviceInfo.put("$model", Build.MODEL.trim());
}
try {
final PackageManager manager = context.getPackageManager();
final PackageInfo packageInfo = manager.getPackageInfo(context.getPackageName(), 0);
deviceInfo.put("$app_version", packageInfo.versionName);
int labelRes = packageInfo.applicationInfo.labelRes;
deviceInfo.put("$app_name", context.getResources().getString(labelRes));
} catch (final Exception e) {
e.printStackTrace();
}
final DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
deviceInfo.put("$screen_height", displayMetrics.heightPixels);
deviceInfo.put("$screen_width", displayMetrics.widthPixels);
return Collections.unmodifiableMap(deviceInfo);
}
获取activity的标题
private static String getToolbarTitle(Activity activity) {
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 = appCompatActivity.getSupportActionBar();
if (supportActionBar != null) {
if (!TextUtils.isEmpty(supportActionBar.getTitle())) {
return supportActionBar.getTitle().toString();
}
}
}
}
return null;
}
/**
* 获取 Activity 的 title
*/
@SuppressWarnings("all")
private static String getActivityTitle(Activity activity) {
String activityTitle = "";
if (activity == null) {
return null;
}
try {
activityTitle = activity.getTitle().toString();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
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;
}
注册 Application.ActivityLifecycleCallbacks
/**
* 注册 Application.ActivityLifecycleCallbacks
*/
public static void registerActivityLifecycleCallbacks(Application application) {
application.registerActivityLifecycleCallbacks(new Application.ActivityLifecycleCallbacks() {
@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
}
@Override
public void onActivityStarted(Activity activity) {
}
@Override
public void onActivityResumed(Activity activity) {
trackAppViewScreen(activity);
}
@Override
public void onActivityPaused(Activity activity) {
}
@Override
public void onActivityStopped(Activity activity) {
}
@Override
public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
}
@Override
public void onActivityDestroyed(Activity activity) {
}
});
}
Track 页面浏览事件
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());
properties.put("$title", getActivityTitle(activity));
AppViewScreenDataAPI.getInstance().track("$AppViewScreen", properties);
} catch (JSONException e) {
e.printStackTrace();
}
}
拼接起来
private static void addIndentBlank(StringBuilder sb, int indent) {
try {
for (int i = 0; i < indent; i++) {
sb.append('\t');
}
} catch (Exception e) {
e.printStackTrace();
}
}
public static String formatJson(String jsonStr) {
try {
if (null == jsonStr || "".equals(jsonStr)) {
return "";
}
StringBuilder sb = new StringBuilder();
char last;
char current = '\0';
int indent = 0;
boolean isInQuotationMarks = false;
for (int i = 0; i < jsonStr.length(); i++) {
last = current;
current = jsonStr.charAt(i);
switch (current) {
case '"':
if (last != '\\') {
isInQuotationMarks = !isInQuotationMarks;
}
sb.append(current);
break;
case '{':
case '[':
sb.append(current);
if (!isInQuotationMarks) {
sb.append('\n');
indent++;
addIndentBlank(sb, indent);
}
break;
case '}':
case ']':
if (!isInQuotationMarks) {
sb.append('\n');
indent--;
addIndentBlank(sb, indent);
}
sb.append(current);
break;
case ',':
sb.append(current);
if (last != '\\' && !isInQuotationMarks) {
sb.append('\n');
addIndentBlank(sb, indent);
}
break;
default:
sb.append(current);
}
}
return sb.toString();
} catch (Exception e) {
e.printStackTrace();
return "";
}
}