需求意在统计应用的使用时长和开始结束时间,最终生成一个文件可以直观看出什么时候进入了哪个应用、什么时候退出,如图:
每行记录了应用的进入或退出,以逗号分割。分别记录了事件开始时间,应用包名,进入或退出(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)时,判断新的包名与旧的是否相同,如果不同说明进行了应用切换,记录退出了上个应用和进入了新的应用。