android10 系统定制:增加应用使用数据埋点,应用使用时长统计

需求意在统计应用的使用时长和开始结束时间,最终生成一个文件可以直观看出什么时候进入了哪个应用、什么时候退出,如图:

每行记录了应用的进入或退出,以逗号分割。分别记录了事件开始时间,应用包名,进入或退出(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)时,判断新的包名与旧的是否相同,如果不同说明进行了应用切换,记录退出了上个应用和进入了新的应用。

相关推荐
小书房1 小时前
Kotlin的by
android·开发语言·kotlin·委托·by
jinanwuhuaguo1 小时前
(第二十八篇)OpenClaw成本与感知的奇点——从“Token封建制”到“全民养虾”的本体论地基
android·人工智能·kotlin·拓扑学·openclaw
xxjj998a2 小时前
Laravel4.x核心特性全解析
android·mysql·laravel
JoshRen2 小时前
2026教程:在Android Termux中集成Gemini 3镜像站实现移动端文档自动处理与摘要生成(附国内免费方案)
android
诸神黄昏EX3 小时前
Android Google KEY
android
一起搞IT吧3 小时前
Android性能系列专题理论之十一:block IO问题分析思路
android·嵌入式硬件·智能手机·性能优化
小妖6664 小时前
怎么用 tauri 创建编译 android 应用程序
android·tauri
鸟儿不吃草5 小时前
安卓实现左右布局聊天界面
android·开发语言·python
xxjj998a7 小时前
Laravel 1.x:PHP框架的原始魅力
android·php·laravel
formula100007 小时前
在iOS/安卓上远程连接任何 Agent!Claude、Codex、Copilot、Gemini、OpenCode 等
android·copilot