需求意在统计应用的使用时长和开始结束时间,最终生成一个文件可以直观看出什么时候进入了哪个应用、什么时候退出,如图:
每行记录了应用的进入或退出,以逗号分割。分别记录了事件开始时间,应用包名,进入或退出(1或2),应用名称。
根据上面的数据记录可以看出:2024-08-12 09:52:54进入了设置,09:52:57退出设置回到了桌面,09:53:11进入了包名为com.example.intelligentsearch888名称为client1的应用... ...
基本思路:当系统窗体焦点发生变化时,获取应用信息,如果进入了新的应用(同一个应用内不做记录,减少不必要的数据)则写入记录文件。
具体实现:
1.添加数据记录帮助类:
在frameworks/base/services/core/java/com/下添加自定义目录custom/buriedpoint,添加工具文件BuriedPointManager.java:
package com.custom.buriedpoint;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.provider.Settings;
import android.text.TextUtils;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* @author : ljl
* @description:
* @date :2024/8/9 下午5:35
*/
public class BuriedPointManager {
/**
* Enter application buried point type
*/
private static final int TYPE_BURIED_POINT_DATA_ENTER = 1;
/**
* Exit application buried point type
*/
private static final int TYPE_BURIED_POINT_DATA_EXIT = 2;
/**
* Embedded point file path
*/
private static final String BURIED_POINT_FILE_PATH = "/data/system/UsageStats.txt";
/**
* Time conversion format
*
* @param createTime
* @return
*/
private String getSimpleDateTime(long createTime) {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
try {
long issueTime = new Date(createTime).getTime();
String timeStamp = dateFormat.format(new Date(issueTime));
return timeStamp;
} catch (NumberFormatException e) {
e.printStackTrace();
}
return "";
}
/**
* Write buried point file
*
* @param appName
* @param packageName
* @param type
*/
private void saveBuriedPointFile(String appName, String packageName, int type) {
File file = new File(BURIED_POINT_FILE_PATH);
if (!file.exists()) {
try {
file.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}
StringBuilder sb = new StringBuilder();
sb.append(getSimpleDateTime(System.currentTimeMillis()));
sb.append(",");
sb.append(packageName);
sb.append(",");
sb.append(type);
sb.append(",");
sb.append(appName);
android.util.Log.i("fzs-buried", "save content == " + sb.toString());
FileWriter writer = null;
try {
writer = new FileWriter(BURIED_POINT_FILE_PATH, true);
writer.write(sb.toString());
writer.write("\n");
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (writer != null) {
writer.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
public void onNewFocusChange(String newPackageName, String newAppName, boolean isFocus) {
saveBuriedPointFile(newPackageName, newAppName,
isFocus ? TYPE_BURIED_POINT_DATA_ENTER : TYPE_BURIED_POINT_DATA_EXIT);
}
}
代码比较简单,主要定义了日志文件的输出路径,进入或退出应用的常量和写入到文件的方法。
2.埋点数据的写入
系统窗口焦点的变化可以在WindowManagerService中获取到,所以主要在WMS中进行数据写入:
final class H extends android.os.Handler {
private BuriedPointManager buriedPointManager;
private String lastPackageName=""; //记录上个应用包名
private String lastAppName=""; //记录上个应用名称
...
public void handleMessage(Message msg) {
switch (msg.what) {
case REPORT_FOCUS_CHANGE: {
if (lastFocus == newFocus) {
// Focus is not changing, so nothing to do.
return;
}
if (buriedPointManager == null) {//实例化工具类
buriedPointManager = new BuriedPointManager();
}
PackageManager packageManager = mContext.getPackageManager();
if (lastFocus != null){
try {
ApplicationInfo applicationInfoLastFocus = packageManager.getApplicationInfo(lastFocus.getOwningPackage(),
PackageManager.GET_META_DATA);
if (applicationInfoLastFocus!=null){
//获取lastFocus 对应的包名和应用名称,记录到全局变量
lastPackageName = packageManager.getApplicationLabel(applicationInfoLastFocus).toString();
lastAppName = lastFocus.getOwningPackage();
}
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
}
if (newFocus != null){
try {
ApplicationInfo applicationInfoNewFocus = packageManager.getApplicationInfo(newFocus.getOwningPackage(),
PackageManager.GET_META_DATA);
if (applicationInfoNewFocus!=null){
String newPackageName = packageManager.getApplicationLabel(applicationInfoNewFocus).toString();
if (!TextUtils.equals(newPackageName, lastPackageName)) {//发生了应用切换
if (!TextUtils.isEmpty(lastPackageName)) {
//记录退出了上个应用
buriedPointManager.onNewFocusChange(lastPackageName,lastAppName,false);
}
if (!TextUtils.isEmpty(newPackageName)) {
// 记录进入了新的应用
buriedPointManager.onNewFocusChange(newPackageName,newFocus.getOwningPackage(),newFocus.isFocused());
}
}
}
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
}
synchronized (mGlobalLock) {
displayContent.mLastFocus = newFocus;
if (DEBUG_FOCUS_LIGHT) Slog.i(TAG_WM, "Focus moving from " + lastFocus +
" to " + newFocus + " displayId=" + displayContent.getDisplayId());
if (newFocus != null && lastFocus != null && !newFocus.isDisplayedLw()) {
if (DEBUG_FOCUS_LIGHT) Slog.i(TAG_WM, "Delaying loss of focus...");
displayContent.mLosingFocus.add(lastFocus);
lastFocus = null;
}
}
...
break;
}
...
}
...
}
系统窗口焦点发生变化会调用Handler的REPORT_FOCUS_CHANGE,里面有lastFocus和newFocus记录了新旧Window的状态。
因为执行到这里lastFocus可能为null只存在newFocus,因此在Handler定义了全局变量lastPackageName和lastAppName来记录上次的应用包名及名称。根据lastFocus获取到上个窗体的应用包名及名称。当新的窗体到来(newFocus!=null)时,判断新的包名与旧的是否相同,如果不同说明进行了应用切换,记录退出了上个应用和进入了新的应用。