UsageStatsService是一个系统服务,其主要通过AMS等,来监测并记录各个应用的使用数据,如上次调用com.android.settings的时间等。
UsageStatsService,a service that collects, aggregates, and persists application usage data. This data can be queried by apps that have been granted permission by AppOps.
代码位置:frameworks/base/services/usage/java/com/android/server/usage/
UsageStatsService创建时,其在onStart()方法中会调用如下方法提供服务,
publishLocalService(UsageStatsManagerInternal.class, new LocalService()); // AMS会调用
publishBinderService(Context.USAGE_STATS_SERVICE, new BinderService()); // 给其他Service和APP调用
其中重点关注LocalService,ActivityManagerService有一个成员变量mUsageStatsService,其会统计4个UsageStatsService自定义的事件(MOVE_TO_FOREGROUND,MOVE_TO_BACKGROUND,CONFIGURATION_CHANGE,SYSTEM_INTERACTION)。mUsageStatsService的赋值在SystemServer#startCoreServices()方法中,如下:
mActivityManagerService.setUsageStatsManager(LocalServices.getService(UsageStatsManagerInternal.class));
数据的事件类型有7种,全部定义在UsageEvents.java中,如下:
数值
|
事件
|
解释
|
调用方
|
备注
|
---|---|---|---|---|
1 | MOVE_TO_FOREGROUND | An event type denoting that a component moved to the foreground | ActivityManagerService | 例如当Activity被置于前台显示时,会记录此事件及时间,包括Activity名字 |
2 | MOVE_TO_BACKGROUND | An event type denoting that a component moved to the background | ActivityManagerService | 例如当Activity被置于后台时,会记录此事件及时间,包括Activity名字 |
3 | END_OF_DAY | An event type denoting that a component was in the foreground when the stats * rolled-over. This is effectively treated as a {@link #MOVE_TO_BACKGROUND} | UsageStatsService |
每一次记录事件时间时,都会判定,如果此次记录时系统时间越过一天,此时会记录此事件,它的作用是这次的时间会做为今天的最后一个记录时间。 那么下次再记录事件时,将会当作新的一天来记录,新建一个新的XML文件 |
4 | CONTINUE_PREVIOUS_DAY | An event type denoting that a component was in the foreground the previous day. * This is effectively treated as a {@link #MOVE_TO_FOREGROUND} | UsageStatsService | - |
5 | CONFIGURATION_CHANGE | An event type denoting that the device configuration has changed | ActivityManagerService | 记录下系统配置变化的数据,时间等 |
6 | SYSTEM_INTERACTION | An event type denoting that a package was interacted with in some way by the system | ActivityManagerService | 较多事件是此类型,具体解释可见下方2 |
7 | USER_INTERACTION | An event type denoting that a package was interacted with in some way by the user | NotificationManagerService | 当一条通知显示给用户看时,会记录此事件,包括时间,package等 |
重点介绍以下3个事件,
1. 事件【MOVE_TO_FOREGROUND与MOVE_TO_BACKGROUND】
Activity在前台显示时,ActivityStackSupervisor.java中方法reportResumedActivityLocked(),其会调用ActivityManagerService#updateUsageStats(),resumedf参数为true,
Activity在后台时,ActivityStack.java中方法startPausingLocked()或removeHistoryRecordsForAppLocked(),其会调用ActivityManagerService#updateUsageStats(),resumedf参数为false,
void updateUsageStats(ActivityRecord component, boolean resumed) {
- ......
- if (resumed) {
- if (mUsageStatsService != null) {
- mUsageStatsService.reportEvent(component.realActivity, component.userId,
- UsageEvents.Event.MOVE_TO_FOREGROUND);
- }
- ......
- } else {
- if (mUsageStatsService != null) {
- mUsageStatsService.reportEvent(component.realActivity, component.userId,
- UsageEvents.Event.MOVE_TO_BACKGROUND);
- }
- ......
- }
- }
在updateUsageStats()中会调用UsageStatsService的reportEvent方法,来记录下MOVE_TO_FOREGROUND或MOVE_TO_FOREGROUND事件,以及Activity等,这些数据会通过UsageStatsService被保存。
2. 事件【SYSTEM_INTERACTION】
统计此事件的代码调用顺序是:AcitivityManagerService#applyOomAdjLocked() -> ActivityManagerService#maybeUpdateUsageStatsLocked() -> mUsageStatsService.reportEvent(packages[i], app.userId, UsageEvents.Event.SYSTEM_INTERACTION);
代码:frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java#maybeUpdateUsageStatsLocked
其中在方法maybeUpdateUsageStatsLocked中,判断是否向UsageStatsService发送此事件统计的依据之一是变量isInteraction(通过对比app.curProcState)。
举个例子:
百度小米输入法(com.baidu.input_mi)的进程状态由PROCESS_STATE_NONEXISTENT(-1)变更为PROCESS_STATE_BOUND_FOREGROUND_SERVICE(3)时,AMS会向UsageStatsService发送此事件记录。debug log如下:
Checking proc [[com.baidu.input_mi]] state changes: old = -1, new = 3
report SYSTEM_INTERACTION event, package = com.baidu.input_mi
另外,重点注意以下2点:
A. UsageStatsService中,SYSTEM_INTERACTION事件在数据存储时,其event type会被记为0。代码依据:
frameworks/base/services/usage/java/com/android/server/usage/IntervalStats.java#108
B. SYSTEM_INTERACTION事件的上次使用时间,在数据存储时,会被记为0,通过UTC时间转换后,是1970年1月1日
- if (eventType != UsageEvents.Event.SYSTEM_INTERACTION) {
- usageStats.mLastTimeUsed = timeStamp;
- }
frameworks/base/services/usage/java/com/android/server/usage/IntervalStats.java#112
以上2点是应用使用统计服务的by design逻辑
UsageStatsService的数据存储在哪里?有一个类在管理UsageStatsDatabase,通过它的源码即可发现,真正的数据持久化是存储在XML中,XML位置:/data/system/usagestats/。XML的所有操作,例如读,写等,都被封装在类UsageStatsXmlV1中,由UsageStatsDatabase调用。
以下介绍3个方面,以及重点介绍下【时间跳变时UsageStatsService是如何更新已记录的时间】
1. 缓存与文件存储
UsageStatsService每次在启动时,都会先按照user生成各个UserUsageStatsService,其中每个对象都会先去各自的文件路径下读取数据到内存中。代码如下:
- for (int i = 0; i < mCurrentStats.length; i++) {
- mCurrentStats[i] = mDatabase.getLatestUsageStats(i);
- ......
此后每次外界reportEvent,都会先更新内存中的数据,相当于缓存。那什么时候内存中的数据会更新至文件中呢?主要有以下几种情况:
情况 | |
---|---|
内存中数据更新至文件中的时机 |
1. 手机关机,具体见:UsageStatsService.java#shutdown |
2. 系统时间跳变(如人为修改系统时间或时间随网络校准) | |
3. 一天结束时,因为daily下面xml文件存储一天的数据,此时需下次新建文件 | |
4. ...... |
数据在内存中保存在mCurrentStats变量中
2. 数据目录按daily,monthly,weekly,yearly四个文件夹存储,每个文件夹中包含若干个XML文件
如:
3. 每一个XML中的数据
打开一个XML,即可看到,存储的数据包括三类,package上次访问和使用的时间,configurations,event-log。举例如下:
<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<usagestats version="1" endTime="93054">
<packages>
<package lastTimeActive="92995" package="com.android.settings" timeActive="87841" lastEvent="2" />
<package lastTimeActive="93054" package="com.miui.home" timeActive="5076" lastEvent="1" />
</packages>
<configurations>
<config lastTimeActive="0" timeActive="0" count="1" active="true" fs="1065353216" locales="zh-CN" touch="3" key="1" keyHid="1" hardKeyHid="2" nav="1" navHid="2" ori="1" scrLay="268435810" ui="17" width="360" height="620" sw="360" density="480" />
</configurations>
<event-log>
<event time="0" package="com.android.settings" class="com.android.settings.UsageStatsActivity" type="2" />
<event time="61" package="com.miui.home" class="com.miui.home.launcher.Launcher" type="1" />
<event time="5137" package="com.miui.home" class="com.miui.home.launcher.Launcher" type="2" />
<event time="5154" package="com.android.settings" class="com.android.settings.MiuiSettings" type="1" />
<event time="92995" package="com.android.settings" class="com.android.settings.MiuiSettings" type="2" />
<event time="93054" package="com.miui.home" class="com.miui.home.launcher.Launcher" type="1" />
</event-log>
</usagestats>
数据类型
|
简介
|
---|---|
package | 以包为记录单位,例如com.android.settings上一次被访问的时间,上次被使用的时间以及事件类型。注意:其数值是能在event log中查询找到,对应起来 |
configurations | 由AMS发送事件给UsageStatsService来统计,记录下系统配置变化的数据,时间等 |
event-log | 以Activity为单位,例如桌面界面,上一次被访问的时间,以及事件类型 |
注意:XML中package的lastEvent字段,event-log的type字段,都是指上面介绍过的事件类型。但是注意,事件类型是有7种,但真正记录在XML中,除了4种(MOVE_TO_FOREGROUND,MOVE_TO_BACKGROUND,END_OF_DAY,CONTINUE_PREVIOUS_DAY)记录其int值,其他的事件(CONFIGURATION_CHANGE,SYSTEM_INTERACTION,USER_INTERACTION)都记录为0,所以在XML中看到事件类型为0,那么是指这三种。这段逻辑是在数据记录的IntervalStats#update方法中,
- 95 void update(String packageName, long timeStamp, int eventType) {
- 96 UsageStats usageStats = getOrCreateUsageStats(packageName);......108
- if (isStatefulEvent(eventType)) {
- 109 usageStats.mLastEvent = eventType;
- 110
- }
- 111 112
- if (eventType != UsageEvents.Event.SYSTEM_INTERACTION) {
- 113 usageStats.mLastTimeUsed = timeStamp;
- 114
- }......122
- }
isStatefulEvent的实现如下:
- private boolean isStatefulEvent(int eventType) {
- switch (eventType) {
- case UsageEvents.Event.MOVE_TO_FOREGROUND:
- case UsageEvents.Event.MOVE_TO_BACKGROUND:
- case UsageEvents.Event.END_OF_DAY:
- case UsageEvents.Event.CONTINUE_PREVIOUS_DAY:
- return true;
- }
- return false;
- }
另外,格外注意以上方法中第112行,SYSTEM_INTERACTION这个事件在存储时,上次访问时间是不记录真实时间的,取初始默认值0,转换成现代时间,就是
- void onTimeChanged(long oldTime, long newTime) {
- persistActiveStats();
- mDatabase.onTimeChanged(newTime - oldTime);
- loadActiveStats(newTime);
- }
来源: http://www.cnblogs.com/KevinSong/p/7978728.html