Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >Android UsageStatsService(应用使用统计服务)的学习与调研

Android UsageStatsService(应用使用统计服务)的学习与调研

作者头像
宋凯伦
发布于 2018-07-31 06:22:51
发布于 2018-07-31 06:22:51
73700
代码可运行
举报
运行总次数:0
代码可运行

一. 简介

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 componentboolean resumed) {

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
        ......
        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);
            }
       ......
        }
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
}

在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日

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
112        if (eventType != UsageEvents.Event.SYSTEM_INTERACTION) {
113            usageStats.mLastTimeUsed = timeStamp;
114        }

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,其中每个对象都会先去各自的文件路径下读取数据到内存中。代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
98        for (int i = 0; i < mCurrentStats.length; i++) {
99            mCurrentStats[i] = mDatabase.getLatestUsageStats(i);
100          ......

此后每次外界reportEvent,都会先更新内存中的数据,相当于缓存。那什么时候内存中的数据会更新至文件中呢?主要有以下几种情况:

情况

内存中数据更新至文件中的时机

1. 手机关机,具体见:UsageStatsService.java#shutdown

2. 系统时间跳变(如人为修改系统时间或时间随网络校准)

3. 一天结束时,因为daily下面xml文件存储一天的数据,此时需下次新建文件

4. ......

数据在内存中保存在mCurrentStats变量中

2. 数据目录按daily,monthly,weekly,yearly四个文件夹存储,每个文件夹中包含若干个XML文件

如:

3. 每一个XML中的数据

打开一个XML,即可看到,存储的数据包括三类,package上次访问和使用的时间,configurations,event-log。举例如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<?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方法中,

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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的实现如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
84    private boolean isStatefulEvent(int eventType) {
85        switch (eventType) {
86            case UsageEvents.Event.MOVE_TO_FOREGROUND:
87            case UsageEvents.Event.MOVE_TO_BACKGROUND:
88            case UsageEvents.Event.END_OF_DAY:
89            case UsageEvents.Event.CONTINUE_PREVIOUS_DAY:
90                return true;
91        }
92        return false;
93    }

另外,格外注意以上方法中第112行,SYSTEM_INTERACTION这个事件在存储时,上次访问时间是不记录真实时间的,取初始默认值0,转换成现代时间,就是1970年1月1日。

以下重点介绍:

1. 数据随时间跳变而调整

    手机系统时间会每秒发生变化,但也会发生跳变,常见的方式是2种,一是人为修改时间,二是系统时间通过网络自动校准(SIM卡或WiFi等)。

    举个例子,手机第一次使用,未联网校准时,手机时间是错误的,可能显示为1970年3月25日,这时候用户在手机的操作,各个应用的上次使用时间肯定是被记录为1970年3月25日。但手机联网后,时间被通过网络校准为2017年11月29日。那这样会有一个情况?UsageStatsService中统计的时间仍然记录为1970年3月25日吗?显然不会的,Google的工程师想到了这一点,因此在UsageStatsService中有一个巧妙的机制,来保证记录时间的准确性。

    A. UsageStatsService中有一个方法checkAndGetTimeLocked,此方法会在每次reportEvent记录应用事件时,获取系统时间,在获取的同时呢,它也记录了上一次使用的系统时间。通过差值计算,能够判定出系统的时间是否发生了跳变,例如人为的修改,通过网络进行的时间自动校准等。如果判定系统时间发生了跳变,UsageStatsService会调用onTimeChanged()方法,它会负责更新UsageStatsService记录的时间,以便他们能够跟随系统时间跳变,而相应更新。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
         void onTimeChanged(long oldTime, long newTime) {
                persistActiveStats();
                mDatabase.onTimeChanged(newTime - oldTime);
                loadActiveStats(newTime);
         }

     B. 这里的时间是直接存储在XML中吗?例如XML中Activity1,上次使用时间:2017年11月29日XX时XX分XX秒。不是这样的,这里的设计也它的特别之处,

         首先时间的存储全部是按毫秒来存储的,此毫秒也就是对比1970年1月1日,换算来的差值。

         其次时间的存储分为2部分,这里有一个公式,应用的上次使用时间  = XML文件名 + XML中此应用的上次使用时间。注意:XML文件的名字不是随便起的,是用某个基准时间的毫秒值来存储的。举个例子:

         XML文件名为:1511953275497,打开文件,其中的数据如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    <?xml version='1.0' encoding='utf-8' standalone='yes' ?>
           <usagestats version="1" endTime="2936118">
           <packages>
           <package lastTimeActive="2936118" package="com.android.settings" timeActive="78268" lastEvent="2" />
            .......  
 

         那么在查询上次使用数据时,Settings的上次使用时间为:1511953275497 + 2936118 = 1511956211615,通过对比1970年1月1日,换算为日常时间是大约是2017/11/29 19:50:11。所以这就是手机中设置Settings上次的使用时间。

         这样设计的好处是,当系统时间跳变时,只需要更新XML的文件名时间,XML中所有的值不需要逐条更新。那么通过算加法得到的时间,也就是正确的时间了。再举个例子:

         1. 用户手机时间为1970年3月25日,这时UsageStatsService中XML的文件名为:7142400,其中设置的上次使用时间,在XML中存储的值是10000,那么设置的上次使用时间是:

             7142400 + 10000 = 7152400,换算为正常时间是:1970年3月25日。(注意,举例中时间用得秒,并非毫秒,实际XML中存储的都是毫秒)

         2. 用户手机时间通过跳变,校准为2017年11月29日。这时UsageStatsService通过onTimeChanged方法,XML中文件名变为1511953275497,在XML中存储的值还是10000,那么设置的上次使用时间是:

             1511953275 + 10000 = 1511963275,换算为正常时间是:2017年11月29日。(注意,举例中时间用得秒,并非毫秒,实际XML中存储的都是毫秒)

         通过以上这样的机制,UsageStatsService中记录的时间,就会随着系统时间的跳变(人为修改或网络校准)而保持为正确的值。用户查询时也不会感到诧异。

 我的博客即将搬运同步至腾讯云+社区,邀请大家一同入驻:https://cloud.tencent.com/developer/support-plan

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2017-12-04 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
教你用Android自带统计服务一招制敌
Google从 API 21 新增了接口 android.app.usage , 通过这个api我们可以统计到每个app的使用情况,启动次数,启动时间等,也可以判断是否前后台,比较方便,今天就来深入的学习一下 。
开发者技术前线
2020/11/23
7890
教你用Android自带统计服务一招制敌
[Android][Framework] Android O SystemServer启动流程
SystemServer通过ZygoteInit.java反射启动,首先会进入main方法,main会构造一个新的SystemServer,然后运行run()方法
wOw
2018/09/18
2.9K0
[Android][Framework] Android O SystemServer启动流程
Android Framework学习(三)之SyetemServer进程启动解析
从上篇博客中,我们知道了Zygote进程启动了SyetemServer进程,本篇博客我们就一起来学习SyetemServer进程。
老马的编程之旅
2022/06/22
4200
android adb shell 常用命令
mac: /Users/xx/Library/Android/sdk/tools/bin archquery jobb monkeyrunner sdkmanager avdmanager lint screenshot2 uiautomatorviewer
tea9
2022/09/08
3.5K0
『APP稳定性测试干货』| 基于Monkey的移动端/APP稳定性测试过程和方法
虫无涯
2023/11/29
1.2K0
Android系统源码剖析(一)---Settings
本文为博主辛苦总结,针对Android4.42源码分析,转载请注明出处,http://blog.csdn.net/zrf1335348191/article/details/50837027
fanfan
2022/05/07
2.4K0
Android系统源码剖析(一)---Settings
Android 测试工具——Monkey事件与日志
Monkey所执行的随机事件流中包含11大事件,分别是触摸事件、手势事件、二指缩放事件、轨迹事件、屏幕旋转事件、基本导航事件、主要导航事件、系统按键事件、启动Activity事件、键盘事件、其他类型事件。Monkey通过这11大事件来模拟用户的常规操作,对手机App进行稳定性测试。下面让我们来详细了解这11大事件。
清风穆云
2021/08/09
9440
Android系统启动——6 SystemServer启动
SystemServer是Android系统的核心之一,大部分Android提供的服务都运行在这个进程里,SystemServer中运行的服务总共有60多种。为了防止应用进程对系统造成破坏,Android的应用进程没有权限直接访问设备的底层资源,只能通过SystemService中的代理访问。通过Binder,用户进程在使用SystemService中的服务并没有太多不便变之处。
隔壁老李头
2018/08/30
3.4K2
Android系统启动——6 SystemServer启动
获取Android当前运行的activity之UsageStatsManager
前言:之前总结了如何获取当前界面正在运行的APP包名,也就是上一个博客。这里做一下补充。 UsageStatsManager是用来统计app使用情况的类,用于获取包含特定时间范围的应用包的使用情况统计信息;在Android api21(即Android5.0引入);系统API稳定性好,Android5.0及以后版本都支持,不存在版本兼容问题。
用户7557625
2020/07/15
4.1K0
利用无障碍服务(AccessibilityService)批量清理后台进程
Demo地址:https://github.com/qyxxjd/ClearProcesses
续写经典
2018/08/28
1.9K0
利用无障碍服务(AccessibilityService)批量清理后台进程
使用am start命令启动android apk应用程序
启动tbox service test apk adb root adb shell am start -n com.demo.hmi.xxxservices.xxx/.MainActivity
天天Lotay
2023/10/15
2.5K0
Android SystemServer启动(二)
在之前已经分析到,通过SystemServer的run方法进入到SystemServer内部逻辑。
Rouse
2021/01/12
9600
Android 开发基础常识
可以通过bindService的方式,先在Activity里实现一个ServiceConnection接口,并将该接口传递给bindService()方法,在ServiceConnection接口的onServiceConnected()方法 里执行相关操作。
zhangjiqun
2024/12/16
1110
Android 开发基础常识
谈AMS的诞生和使用
今天接着完善Android系统这一块的体系架构,说说在App启动流程中举足轻重的ActivityManagerService。
码上积木
2021/01/25
1.1K0
Android性能优化(七)之你真的理解ANR吗?
在上一篇文章《Android性能优化(六)之卡顿那些事》中,我们提到了卡顿的成因、检测卡顿的途径以及避免卡顿的方法。卡顿再扩大就会产生大名鼎鼎的ANR(Application Not Responding),然后告诉用户你的App无响应,继续等待或者强制关闭,很大的概率用户可能会顺手卸载如此卡的App。
用户2898788
2018/08/21
1K0
Android性能优化(七)之你真的理解ANR吗?
android学习笔记----解决兼容8.0以上和8.0之前版本通知栏显示、振动、LED呼吸灯闪烁问题(真机验证)
真机(华为荣耀V9,8.0系统),下拉横幅需要手动打开,除非是厂家白名单,比如QQ、微信
砖业洋__
2023/05/06
6590
android学习笔记----解决兼容8.0以上和8.0之前版本通知栏显示、振动、LED呼吸灯闪烁问题(真机验证)
【Android 应用开发】Android应用的自动更新模块
转载请注明出处 : http://blog.csdn.net/shulianghan/article/details/18964835
韩曙亮
2023/03/27
2.4K0
“终于懂了”系列:APK安装过程 完全解析!
最近在了解插件化技术:把未安装的插件apk 集成到 宿主App中,以取得减少宿主APK包体积等优点。也就是说,一个完整的APK 虽然不经过安装过程,但使用了插件化技术后却可以在宿主中使用其功能。
胡飞洋
2021/11/12
6.3K1
02.Android崩溃Crash库之App崩溃分析
目录总结 01.抛出异常导致崩溃分析 02.RuntimeInit类分析 03.Looper停止App就退出吗 04.handleApplicationCrash 05.native_crash如何监控 06.ANR是如何监控的 07.回过头看addErrorToDropBox 前沿 上一篇整体介绍了crash崩溃库崩溃重启,崩溃记录记录,查看以及分享日志等功能。 项目地址:https://github.com/yangchong211/YCAndroidTool 欢迎star,哈哈哈 01.抛出异常导致崩
杨充
2020/09/07
3.1K0
Android:检查通知权限并跳转到通知设置界面
好久没有更新内容了啊。。。 封面是广东南澳岛的风景, 感觉很漂亮。我生于内陆长于内陆,对于大海有着无限的向往,羡慕海边的人可以赶海,可以捡贝壳,可以抓海鲜,还有海边那种温润怡人的气候!
CnPeng
2020/08/11
7.5K0
推荐阅读
相关推荐
教你用Android自带统计服务一招制敌
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验