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)时,判断新的包名与旧的是否相同,如果不同说明进行了应用切换,记录退出了上个应用和进入了新的应用。

相关推荐
阿巴斯甜14 小时前
Android 报错:Zip file '/Users/lyy/develop/repoAndroidLapp/l-app-android-ble/app/bu
android
Kapaseker14 小时前
实战 Compose 中的 IntrinsicSize
android·kotlin
xq952715 小时前
Andorid Google 登录接入文档
android
黄林晴16 小时前
告别 Modifier 地狱,Compose 样式系统要变天了
android·android jetpack
冬奇Lab1 天前
Android触摸事件分发、手势识别与输入优化实战
android·源码阅读
城东米粉儿1 天前
Android MediaPlayer 笔记
android
Jony_1 天前
Android 启动优化方案
android
阿巴斯甜1 天前
Android studio 报错:Cause: error=86, Bad CPU type in executable
android
张小潇1 天前
AOSP15 Input专题InputReader源码分析
android
_小马快跑_2 天前
Kotlin | 协程调度器选择:何时用CoroutineScope配置,何时用launch指定?
android