全埋点
全埋点也叫无埋点,自动埋点。是指预先自动收集用户的所有行为数据。然后就可以根据收集的数据从中筛选出所需的行为数据进行分析。
采集的事件类型
全埋点采集的事件主要包括以下4种类型:
AppStart事件
指应用程序的启动,它包括冷启动和热启动。
AppEnd事件
指应用程序退出,它包括了应用程序的正常退出、Home键进入后台、应用程序被强杀以及应用程序崩溃。
AppViewScreen事件
指应用程序页面浏览,对于Android应用程序来说就是指Activity或Fragment的切换。
AppClick事件
指应用程序控件的点击事件,也就是View的点击事件。
AppViewScreen全埋点方案
页面浏览事件就是指切换不同的Activity或Fragment,对于一个Activity来说它的onResume()
方法执行,就代表该页面已经显示出来了,即该页面被浏览。所以我们只需要自动在onResume()
方法中实现处理AppViewScreen事件相关的代码,即可解决AppViewScreen事件的全埋点。
Application.ActivityLifecycleCallbacks
ActivityLifecycleCallbacks
是Application
的一个内部接口,是从API14(Android4.0)开始提供的。Application
类通过此接口提供了一系列的回调方法,用于让开发者可以对Activity
的所有生命周期事件进行集中的处理。我们可以通过** Application**提供的registerActivityLifecycleCallbacks
方法来注册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()
方法为例,如果我们注册了该接口,Android系统会先回调ActivityLifecycleCallbacks的onActivityResumed(Activity activity)
方法,然后再执行Activity本身的onResume()
方法。(注意:一个Application是可以注册多个ActivityLifecycleCallbacks回调的)
除了使用该方法,我们可能还会想到定义一个BaseActivity,然后让其他的Activity继承这个BaseActivity。但是这样会有一个问题。如果我们在应用中集成了一些第三方库,并且我们用到了三方库中的Activity,此时我们是无法监控该页面的浏览事件的。因为我们无法让它去继承我们自定义的BaseActivity。所以我们采用Application.ActivityLifecycleCallbacks,不过该方案需要API14+,不过现在主流的机型都已经满足了。
代码实战
1.定义一个工具类SensorsDataAPI,用来采集相应的事件。
//全埋点工具类
class SensorsDataAPI private constructor(application: Application) {
//设备ID
private var mDeviceId: String = SensorsDataPrivate.getAndroidID(application.applicationContext)
//设备信息集合,eg:厂商,版本,分辨率等
private var mDeviceInfo: Map = SensorsDataPrivate.getDeviceInfo(application.applicationContext)
companion object {
//事件采集工具的版本号
const val SDK_VERSION = "1.0.0"
@Volatile
var instance: SensorsDataAPI? = null
private val TAG = this.javaClass.simpleName
/**
* 初始化埋点
* @param application:Application
*/
fun init(application: Application) = instance ?: synchronized(this) {
//双检查带参数单例
instance ?: SensorsDataAPI(application).also { instance = it }
}
}
init {
//注册registerActivityLifecycleCallbacks
SensorsDataPrivate.registerActivityLifecycleCallbacks(application)
}
/**
* 记录事件
* @param eventName:事件名字
* @param properties:自定义采集的属性
*/
fun track(@NonNull eventName: String, @Nullable properties: JSONObject?) {
try {
val jsonObject = JSONObject()
jsonObject.put("event", eventName)
jsonObject.put("device_id", mDeviceId)
val sendProperties = JSONObject(mDeviceInfo)
if (properties != null) {
SensorsDataPrivate.mergeJSONObject(properties, sendProperties)
}
jsonObject.put("properties", sendProperties)
jsonObject.put("time", System.currentTimeMillis())
Log.e(TAG, jsonObject.toString())
} catch (e: Exception) {
e.printStackTrace()
}
}
/**
* 不采集设置的页面的浏览事件
* @param activity :指定的Activity
*/
fun ignoreAutoTrackActivity(activity: Class<*>) {
SensorsDataPrivate.ignoreAutoTrackActivity(activity)
}
/**
* 恢复指定页面的浏览事件的采集
* @param activity
*/
fun removeIgnoredActivity(activity: Class<*>) {
SensorsDataPrivate.removeIgnoredActivity(activity)
}
}
SensorsDataPrivate
class SensorsDataPrivate {
companion object {
private val mDateFormat = SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", Locale.CHINA)
private val mIgnoredActivities = ArrayList()
/**
* 记录页面浏览事件
* @param activity
*/
fun trackAppViewScreen(activity: Activity) {
try {
if (mIgnoredActivities.contains(activity.javaClass.canonicalName)) {
return
}
val properties = JSONObject()
properties.put("activity", activity.javaClass.canonicalName)
SensorsDataAPI.instance?.track("AppViewScreen", properties)
} catch (e: Exception) {
e.printStackTrace()
}
}
/**
* 获取androidID
* @param context
* @return String
*/
@SuppressLint("HardwareIds")
fun getAndroidID(context: Context): String {
var androidID = ""
try {
androidID = Settings.Secure.getString(
context.contentResolver,
Settings.Secure.ANDROID_ID
)
} catch (e: Exception) {
e.printStackTrace()
}
return androidID
}
/**
* 获取设备相关信息
* @param context
* @return Map
*/
fun getDeviceInfo(context: Context): Map {
val deviceInfo = HashMap()
deviceInfo["lib"] = "Android"
deviceInfo["lib_version"] = SensorsDataAPI.SDK_VERSION
deviceInfo["os"] = "Android"
deviceInfo["os_version"] = Build.VERSION.RELEASE ?: "UNKNOWN"
deviceInfo["manufacturer"] = Build.MANUFACTURER ?: "UNKNOWN"
deviceInfo["model"] = if (TextUtils.isEmpty(Build.MODEL)) "UNKNOWN" else Build.MODEL.trim()
try {
val packageInfo = context.packageManager.getPackageInfo(context.packageName, 0)
deviceInfo["app_version"] = packageInfo.versionName
deviceInfo["app_name"] = context.resources.getString(packageInfo.applicationInfo.labelRes)
} catch (e: Exception) {
e.printStackTrace()
}
val displayMetrics = context.resources.displayMetrics
deviceInfo["screen_width"] = displayMetrics.widthPixels
deviceInfo["screen_height"] = displayMetrics.heightPixels
//返回值有序只读的map
return Collections.unmodifiableMap(deviceInfo)
}
/**
* 注册Application.registerActivityLifecycleCallbacks
* @param application
*/
fun registerActivityLifecycleCallbacks(application: Application) {
application.registerActivityLifecycleCallbacks(object : Application.ActivityLifecycleCallbacks {
override fun onActivityPaused(activity: Activity?) { }
override fun onActivityResumed(activity: Activity?) {
if (activity != null) {
trackAppViewScreen(activity)
}
}
override fun onActivityStarted(activity: Activity?) { }
override fun onActivityDestroyed(activity: Activity?) {}
override fun onActivitySaveInstanceState(activity: Activity?, outState: Bundle?) { }
override fun onActivityStopped(activity: Activity?) { }
override fun onActivityCreated(activity: Activity?, savedInstanceState: Bundle?) { }
})
}
/**
* 将自定义属性合并到公共属性中
* @param source 自定义的
* @param dest : 公共的
*/
fun mergeJSONObject(source: JSONObject, dest: JSONObject) {
val propertiesIterator = source.keys()
while (propertiesIterator.hasNext()) {
val key = propertiesIterator.next()
val value = source.get(key)
if (value is Date) {
synchronized(mDateFormat) {
dest.put(key, mDateFormat.format(value))
}
} else {
dest.put(key, value)
}
}
}
/**
* 忽略记录指定的Activity
* @param activity
*/
fun ignoreAutoTrackActivity(activity: Class<*>) {
mIgnoredActivities.add(activity.canonicalName)
}
/**
* 移除忽略的Activity
* @param activity
*/
fun removeIgnoredActivity(activity: Class<*>) {
if (mIgnoredActivities.contains(activity.canonicalName)) {
mIgnoredActivities.remove(activity.canonicalName)
}
}
}
}
2.在Application中初始化埋点工具类。
SensorsDataAPI.init(this)
注意:别忘记在
AndroidManifest.xml
中指定Application。
通过上面的代码设置,我们在启动页面的时候就会自动采集设置的数据,这里只是以日志的形式把采集的数据打印出来了。
Log.e(TAG, jsonObject.toString())
{
"event": "AppViewScreen",
"device_id": "9e3077550b446ff0",
"properties": {
"app_name": "My Application",
"screen_width": 1080,
"screen_height": 1794,
"lib": "Android",
"os": "Android",
"app_version": "1.0",
"os_version": "9",
"model": "Android SDK built for x86",
"lib_version": "1.0.0",
"manufacturer": "Google",
"activity": "com.example.myapplication.MainActivity"
},
"time": 1563692439349
}
在SensorsDataAPI中还有两个方法:ignoreAutoTrackActivity
和removeIgnoredActivity
在上面的示例中没有用到。通过这两个方法,可以忽略和恢复事件的采集。
比如:Android6.0+有些权限需要动态申请,不管用户选择了允许还是禁止,系统都会再次回调当前Activity的onResume()
方法。这样就会导致再一次触发页面浏览事件,所以,在需要申请权限的页面需要把当前的Activity忽略掉,等到权限申请结束后再恢复采集。
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) {
SensorsDataAPI.instance?.ignoreAutoTrackActivity(MainActivity::class.java)
when (requestCode) {
//权限申请结果处理
}
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
}
override fun onStop() {
super.onStop()
SensorsDataAPI.instance?.removeIgnoredActivity(MainActivity::class.java)
}
页面浏览事件相对于其他事件的埋点是比较容易的,主要是通过Application的ActivityLifecycleCallbacks
来监控Activity的生命周期相关方法的回调,从而来实现埋点的逻辑。