Android UsageStatsService:要点解析

本文详细介绍了Android系统中UsageStatsService的作用、使用方法及其工作原理。包括如何获取APP使用数据、服务内部实现细节及数据持久化过程。

1、UsageStatsService作用是什么?

   这是一个Android私有service,主要作用是收集用户使用每一个APP的频率、使用时常;


2、如何通过UsageStatsService获取用户使用APP的数据?

(1)必须要具备系统权限;(APP内置在/system/app下)

(2)必须要在manifest中申明权限:PACKAGE_USAGE_STATS;例如:

<uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" />


(3)调用UsageStatsService.getAllPkgUsageStats or UsageStatsService.getPkgUsageStats 接口获取用户使用APP频率:

 <span style="white-space:pre">	</span>    //相当于:IBinder oRemoteService = ServiceManager.getService("usagestats");
            Class<?> cServiceManager = Class.forName("android.os.ServiceManager");
            Method mGetService = cServiceManager.getMethod("getService", java.lang.String.class);
            Object oRemoteService = mGetService.invoke(null, "usagestats");

            // 相当于:IUsageStats mUsageStatsService = IUsageStats.Stub.asInterface(oRemoteService)
            Class<?> cStub = Class.forName("com.android.internal.app.IUsageStats$Stub");
            Method mUsageStatsService = cStub.getMethod("asInterface", android.os.IBinder.class);
            Object oIUsageStats = mUsageStatsService.invoke(null, oRemoteService);

            // 相当于:PkgUsageStats[] oPkgUsageStatsArray =mUsageStatsService.getAllPkgUsageStats();
            Class<?> cIUsageStatus = Class.forName("com.android.internal.app.IUsageStats");
            Method mGetAllPkgUsageStats = cIUsageStatus.getMethod("getAllPkgUsageStats", (Class[]) null);
            Object[] oPkgUsageStatsArray = (Object[]) mGetAllPkgUsageStats.invoke(oIUsageStats, (Object[]) null);

            //相当于
            //for (PkgUsageStats pkgUsageStats: oPkgUsageStatsArray)
            //{
            //  当前APP的包名:
            //  packageName = pkgUsageStats.packageName
            //  当前APP的启动次数
            //  launchCount = pkgUsageStats.launchCount
            //  当前APP的累计使用时间:
            //  usageTime = pkgUsageStats.usageTime
            //  当前APP的每个Activity的最后启动时间
            //  componentResumeTimes = pkgUsageStats.componentResumeTimes
            //}
            Class<?> cPkgUsageStats = Class.forName("com.android.internal.os.PkgUsageStats");
            for (Object pkgUsageStats : oPkgUsageStatsArray) {
                String packageName = (String) cPkgUsageStats.getDeclaredField("packageName").get(pkgUsageStats);
                int launchCount = cPkgUsageStats.getDeclaredField("launchCount").getInt(pkgUsageStats);
                long usageTime = cPkgUsageStats.getDeclaredField("usageTime").getLong(pkgUsageStats);
                Map<String, Long > componentResumeMap = (Map<String, Long>) cPkgUsageStats.getDeclaredField("componentResumeTimes").get(pkgUsageStats);
            }

3、UsageStatsService工作原理是什么?
(3-1)首先是service的初始化,主要分为两步;
第一步是从/data/system/usagestats/usage-history.xml文件中读取每个APP中每个Activity最后启动的时间;
//初始化/data/system/usagestats/usage-history.xml
 mHistoryFile = new AtomicFile(new File(mDir, FILE_HISTORY));
 //解析xml文件,将xml解析成为数据,并存储
 readStatsFromFile();
第二步是从“/data/system/usagestats/usage-当前日志”文件中解析今天的使用记录的数据:
//指定文件前缀为usage,根据日志生成文件后缀,例如usage-20140817
 mFileLeaf = getCurrentDateStr(FILE_PREFIX);
 //解析文件,为文件流的格式,直接读取即可
 readStatsFromFile();
其中“/data/system/usagestats/usage-当前日志”文件格式为2精制的序列化流,可直接从中读取相应的对象即可;如果对应文件不存在,则创建它;

(3-2)在初始化service完成后,需要组数据存储:
从usage-history.xml文件中解析出来的数据,放在:
   // key为包名,value为Map<String, Long>,记录该包下的Activity名以及该Activity最后启动时间
    final private Map<String, Map<String, Long>> mLastResumeTimes;

从“/data/system/usagestats/usage-当前日志”解析出来的数据,放在:
    // String为包名,PkgUsageStatsExtended存储相应的启动信息
    final private Map<String, PkgUsageStatsExtended> mStats;

可以发现此处封装了一个内部类PkgUsageStatsExtended做数据存储,其实PkgUsageStatsExtended还封装了许多操作,后面会逐步涉及,目前PkgUsageStatsExtended主要存储了这些信息:
   private class PkgUsageStatsExtended {
        //每个Activity最后启动时间
        final HashMap<String, TimeStats> mLaunchTimes
                = new HashMap<String, TimeStats>();
        //当前APP启动次数
        int mLaunchCount;
        //当前APP使用时间
        long mUsageTime;
        //当前APP最后一次调用onPause时间
        long mPausedTime;
        //当前APP最后一个调用onResume时间
        long mResumedTime;
        
        //更新mResumedTime为当前时间
        void updateResume(String comp, boolean launched) {
            if (launched) {
                mLaunchCount ++;
            }
            mResumedTime = SystemClock.elapsedRealtime();
        }
        
        //更新mPausedTime为当前时间,并且使用时常=mPausedTime - mResumedTime
        void updatePause() {
            mPausedTime =  SystemClock.elapsedRealtime();
            mUsageTime += (mPausedTime - mResumedTime);
        }
        
        //记录activity次数
        void addLaunchCount(String comp) {
            TimeStats times = mLaunchTimes.get(comp);
            if (times == null) {
                times = new TimeStats();
                mLaunchTimes.put(comp, times);
            }
            times.incCount();
        }
        
        //记录activity启动时间
        void addLaunchTime(String comp, int millis) {
            TimeStats times = mLaunchTimes.get(comp);
            if (times == null) {
                times = new TimeStats();
                mLaunchTimes.put(comp, times);
            }
            times.add(millis);
        }
        /*more code
        */
        
    }

(3-3)当应用启动一个Activity时,UsageStatsService会发生什么行为?
ActivityManagerService会调用UsageStatsService.noteResumeComponent方法,在该方法中会有以下操作:
public void noteResumeComponent(ComponentName componentName) {
        enforceCallingPermission();
        String pkgName;
        synchronized (mStatsLock) {
        		/*some code
        		*/
            final boolean samePackage = pkgName.equals(mLastResumedPkg);
            //1、mIsResumed会在onResume中变为true,在onPause中变为false
            if (mIsResumed) {
                if (mLastResumedPkg != null) {
                    //2、这里是为了避免没有调用onPause的情况出现,理论上不存在
                    PkgUsageStatsExtended pus = mStats.get(mLastResumedPkg);
                    if (pus != null) {
                    		//3、增加保护,调用一次updatePause
                        pus.updatePause();
                    }
                }
            }
            
            final boolean sameComp = samePackage
                    && componentName.getClassName().equals(mLastResumedComp);
                    
            //5、内部数据更新,记录最后一次启动的Activity
            mIsResumed = true;
            mLastResumedPkg = pkgName;
            mLastResumedComp = componentName.getClassName();
            
            PkgUsageStatsExtended pus = mStats.get(pkgName);
            if (pus == null) {
                pus = new PkgUsageStatsExtended();
                mStats.put(pkgName, pus);
            }
            //6、更新Activity启动时间,如果用户是从一个App启动进入另外一个APP,那么需要App标识启动次数+1
            pus.updateResume(mLastResumedComp, !samePackage);
            if (!sameComp) {
            		//7、同上,Activity启动次数+1
                pus.addLaunchCount(mLastResumedComp);
            }
						
            Map<String, Long> componentResumeTimes = mLastResumeTimes.get(pkgName);
            if (componentResumeTimes == null) {
                componentResumeTimes = new HashMap<String, Long>();
                mLastResumeTimes.put(pkgName, componentResumeTimes);
            }
            //8、更新componentResumeTimes
            componentResumeTimes.put(mLastResumedComp, System.currentTimeMillis());
        }
    }

(3-4)当退出一个Activity,UsageStatsService会发生什么行为?
ActivityManagerService会调用UsageStatsService.notePauseComponent方法,UsageStatsService会更新当前展示Activity的OnPause时间:
public void notePauseComponent(ComponentName componentName) {
				/*some code
				*/
        synchronized (mStatsLock) {
            /*some code
            */
            //1、mIsResumed会在onResume中变为true,在onPause中变为false
            mIsResumed = false;

            PkgUsageStatsExtended pus = mStats.get(pkgName);
            if (pus == null) {
                // Weird some error here
                Slog.i(TAG, "No package stats for pkg:"+pkgName);
                return;
            }
            //2、更新onPause的时间
            pus.updatePause();
        }
      //3、视情况而定确认是否需要将内存数据保存成文件
    writeStatsToFile(false, false);      
}

(3-5)现在,我们已经明白APP启动数据如何在内存中流转了,那么什么时候系统将数据持久化成文件?
UsageStatsService通过writeStatsToFile方法将数据持久化成文件,函数原型如下:
    /**
     * 在特定条件下,将mStats或者mLastResumeTimes写入到文件中,由用户操作触发
     * @params force  强制将mStats实例化到文件中
     *               
     * @params forceWriteHistoryStats 强制将mLastResumeTimes实例化到文件中
     */
    private void writeStatsToFile(final boolean force, final boolean forceWriteHistoryStats) {
    }

具体触发writeStatsToFile的时机有:
1、用户关机,触发writeStatsToFile(true, true);
2、用户打开一个新应用,触发writeStatsToFile(false,false);
3、在notePauseComponent最后,调用writeStatsToFile(false,false);

writeStatsToFile触发写文件操作的条件有:
1、关机强制触发;
2、当前日期与最后一次写文件日期不同;
3、除用户关机外,两次写文件间隔必须在30分钟以上;

(3-6)卸载APP后,已经记录的数据会被清除;



<think>我们正在分析一段日志,用户询问这段日志是否与OTG不识别有关。日志内容如下: User[0] SELECT * FROM 0 WHERE beginTime >= 1760434216618 AND endTime < 1760520616618 行 110876: 10-15 17:30:16.626493 3250 5186 D UsageStatsService: Got 131 results from disk 行 110877: 10-15 17:30:16.626530 3250 5186 D UsageStatsService: Current stats beginTime=1760515444089 endTime=1760520614927 首先,我们需要理解日志的含义: - 第一行是一个SQL查询语句,从某个表(可能是使用统计数据库)中查询特定时间范围内的数据。时间戳(beginTime和endTime)是毫秒级Unix时间戳。 - 后续两行是UsageStatsService(使用统计服务)的调试日志,表明它从磁盘获取了131条结果,并记录了当前统计数据的开始时间和结束时间。 根据之前的讨论,UsageStatsService负责记录应用的使用情况,而不直接处理硬件事件(如OTG识别)。因此,这段日志本身并没有提到任何与USB、OTG或硬件相关的内容。 那么,这段日志是否与OTG不识别有关呢? - **直接关联性低**:日志中未出现USB、OTG、FunctionFS、UsbService等关键词,也没有错误提示(如设备无法识别)。UsageStatsService的日志通常只与应用使用统计相关。 - **间接关联可能**:如果OTG插入后导致某个应用(如文件管理器)被启动或使用,UsageStatsService可能会记录该应用的活动。但这段日志只展示了查询操作和结果数量,没有具体应用信息,无法直接推断OTG状态。 结论:这段日志**与OTG不识别问题无直接关联**,它仅反映UsageStatsService在正常运行(查询并加载应用使用统计数据)。若需排查OTG问题,建议检查UsbService、内核USB驱动或ADB相关日志(如`adb logcat | grep -i 'usb'`)。 §§相关问题§§ 1. UsageStatsService的日志中哪些关键信息可以间接反映OTG设备的使用? 2. 如何通过日志区分UsageStatsService的正常操作和异常情况? 3. OTG设备无法识别时,Android系统的哪些核心日志模块需要优先检查? 4. UsageStatsService的数据库查询时间范围(如beginTime和endTime)如何与系统事件时间关联? 5. UsageStatsService记录的"results from disk"具体包含哪些数据类型? </think>### 日志解析与OTG关联性分析 您提供的日志片段属于Android系统UsageStatsService的正常操作记录,**与OTG识别问题无直接关联**。以下是关键解析: 1. **SQL查询分析** `SELECT * FROM 0 WHERE beginTime >= 1760434216618 AND endTime < 1760520616618` - 这是UsageStatsService在查询应用使用统计数据库(表`0`代表设备事件统计表) - 时间范围: `1760434216618` → UTC 2025-10-15 16:30:16.618 `1760520616618` → UTC 2025-10-16 16:30:16.618 - 目的:检索24小时内应用使用事件记录 2. **UsageStatsService日志** - `Got 131 results from disk`:从磁盘加载131条应用使用记录(正常操作) - `Current stats beginTime=1760515444089 endTime=1760520614927`: 当前统计周期为2025-10-16 15:44:04.089至16:30:14.927(约46分钟) - 日志级别`D`(Debug)表明是常规调试信息 --- ### 与OTG不识别问题的关联性 - **无关证据** - ❌ 无USB/OTG关键词(如`UsbService`, `OTG`, `FunctionFS`等) - ❌ 无硬件事件(`USB_ATTACHED`, `USB_CONFIGURED`等广播) - ❌ 无错误提示(如`permission denied`, `unsupported device`等) - **间接关联场景(仅当同时发生)** 若OTG插入**恰好触发应用使用行为**,则可能被记录在此统计中: ```mermaid graph LR OTG插入 --> 启动文件管理器 --> 被UsageStats记录 OTG故障 --> 无应用响应 --> 无相关日志 ``` 但当前日志**未显示任何应用活动记录**,仅体现服务自身数据加载操作。 --- ### 排查建议 若存在OTG识别问题,应检查以下日志: 1. **内核层**:`dmesg | grep dwc3`(USB控制器驱动) 2. **系统层**:`logcat | grep -Ei 'usb|otg'` 3. **广播事件**:`logcat | grep 'ACTION_USB'` 4. **权限日志**:`logcat | grep 'UsbPermission'`
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值