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

相关推荐
千里马学框架3 天前
一起学 Android 14:ShellTransition 屏幕旋转过程深度剖析
android·智能手机·性能优化·framework·性能·屏幕旋转·rotation
美狐美颜SDK开放平台3 天前
开发直播APP时如何接入视频美颜SDK?开发流程与注意事项
android·人工智能·计算机视觉·音视频·直播美颜sdk
AFinalStone3 天前
Android7 SystemUI源码解析(七)Keyguard锁屏模块深度解析
android·systemui
致远ccc3 天前
Google Play 上架前如何测试 App?多国家 Android 环境测试
android·app测试·googleplay·多国家应用测试
ttyyttemo3 天前
Kotlin 协程中的 Job 结构化并发与取消
android
sun0077003 天前
tbox 4g/5g切换,导致wan ip 改变,导致车机旧网络不可用。需要重启车机才行
android
其实防守也摸鱼3 天前
内网穿透与反向代理:原理、工具与实战指南
android·大数据·运维·安全·网络安全·自动化·渗透
AFinalStone3 天前
Android7 SystemUI 源码解析(四)NavigationBar 导航栏与 SystemBars
android·systemui
JMchen3 天前
属性动画原理与高级动画实现
android·kotlin·canvas
AFinalStone3 天前
Android7 SystemUI 源码解析(二)启动流程深度解析
android·systemui