[toc]
基于 Android7.1.1 源码分析 UsageStatsService 的架构和原理!
0 综述
启动 UsageStatsService 服务,是从 SystemServer.startCoreServices 开始!1
2
3
4
5
6
7
8
9private void startCoreServices() {
mSystemServiceManager.startService(BatteryService.class);
mSystemServiceManager.startService(UsageStatsService.class);
mActivityManagerService.setUsageStatsManager(
LocalServices.getService(UsageStatsManagerInternal.class));
mWebViewUpdateService = mSystemServiceManager.startService(WebViewUpdateService.class);
}
1 new UsageStatsService
1 | public UsageStatsService(Context context) { |
UsageStatsService 的构造器很简单,没有过多的数据!
2 UsageStatsS.onStart
1 | public void onStart() { |
继续分析;
2.1 new H
创建了一个 Handler 对,处理 UsageStatsService 中的一些重要的消息,下面我们先开看看有哪些消息!1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36class H extends Handler {
public H(Looper looper) {
super(looper);
}
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_REPORT_EVENT: //【1】处理其他进程传递的 UsageEvents;
break;
case MSG_FLUSH_TO_DISK: //【2】处理其他进程传递的 UsageEvents 时间;
break;
case MSG_REMOVE_USER: //【3】移除某个 user;
break;
case MSG_INFORM_LISTENERS: //【4】
break;
case MSG_FORCE_IDLE_STATE: //【5】设置应用进入 idle 状态;
break;
case MSG_CHECK_IDLE_STATES: //【6】每隔一段时间检查 idle 状态;
break;
case MSG_ONE_TIME_CHECK_IDLE_STATES: //【7】只检查一次 idle 状态;
break;
case MSG_CHECK_PAROLE_TIMEOUT: //【8】
break;
case MSG_PAROLE_END_TIMEOUT: //【9】
break;
case MSG_REPORT_CONTENT_PROVIDER_USAGE: //【10】记录 content provider 的使用;
break;
case MSG_PAROLE_STATE_CHANGED: //【11】充电状态变化;
break;
default:
super.handleMessage(msg);
break;
}
}
}
在启动的过程中也会发送一些 MSG 给 H 进行处理,对于消息的处理,我们放在第四节分析!
2.2 new UserActionsReceiver - 监听用户状态
UserActionsReceiver 接收者用于监听 User 相关的广播!1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20private class UserActionsReceiver extends BroadcastReceiver {
public void onReceive(Context context, Intent intent) {
final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
final String action = intent.getAction();
if (Intent.ACTION_USER_REMOVED.equals(action)) {
if (userId >= 0) {
//【1】如果是用户被移除的广播,发送 MSG_REMOVE_USER 消息给 H!
mHandler.obtainMessage(MSG_REMOVE_USER, userId, 0).sendToTarget();
}
} else if (Intent.ACTION_USER_STARTED.equals(action)) {
if (userId >=0) {
//【*2.2.1】如果是用户被启动的广播,调用 postCheckIdleStates 方法,检查 idle 状态信息!
postCheckIdleStates(userId);
}
}
}
}
2.2.1 UsageStatsService.postCheckIdleStates
1 | void postCheckIdleStates(int userId) { |
如果是用户被启动的广播,调用 postCheckIdleStates 方法,发送 MSG_CHECK_IDLE_STATES 消息给 H,检查 idle 状态信息!
2.3 new PackageReceiver - 监听包状态
PackageReceiver 接收者用于监听 package 相关的广播!1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20private class PackageReceiver extends BroadcastReceiver {
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
if (Intent.ACTION_PACKAGE_ADDED.equals(action)
|| Intent.ACTION_PACKAGE_CHANGED.equals(action)) {
//【*2.3.1】清除运营商特权应用程序列表!
clearCarrierPrivilegedApps();
}
if ((Intent.ACTION_PACKAGE_REMOVED.equals(action) ||
Intent.ACTION_PACKAGE_ADDED.equals(action))
&& !intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
//【*2.3.2】清楚 package 的 idle 状态
clearAppIdleForPackage(intent.getData().getSchemeSpecificPart(),
getSendingUserId());
}
}
}
如果接收到的广播是:Intent.ACTION_PACKAGE_ADDED 或者 Intent.ACTION_PACKAGE_CHANGED,那就调用 clearCarrierPrivilegedApps 方法,清除运营商特权应用程序列表了;
如果接收到的广播是:Intent.ACTION_PACKAGE_REMOVED(移除应用) 或者 Intent.ACTION_PACKAGE_ADDED,且 Intent.EXTRA_REPLACING 为 false (新安装的应用),那就会清楚 package 的 idle 状态!
2.3.1 UsageStatsS.clearCarrierPrivilegedApps
清除运营商特权应用程序列表!1
2
3
4
5
6
7
8
9void clearCarrierPrivilegedApps() {
if (DEBUG) {
Slog.i(TAG, "Clearing carrier privileged apps list");
}
synchronized (mLock) {
mHaveCarrierPrivilegedApps = false;
mCarrierPrivilegedApps = null; // Need to be refetched.
}
}
这里涉及到 2 个变量:
- mHaveCarrierPrivilegedApps 表示是否持有运营商特权应用程序;
- mCarrierPrivilegedApps 是一个 list,保存了运营商特权应用程序;
当然了,有删除也就有添加的方法 fetchCarrierPrivilegedAppsLocked:
1 | private void fetchCarrierPrivilegedAppsLocked() { |
至于运营商特权应用程序列表相关内容,我们后续在看!
2.3.2 UsageStatsS.clearAppIdleForPackage
清楚 package 的 idle 状态:1
2
3
4
5
6void clearAppIdleForPackage(String packageName, int userId) {
synchronized (mLock) {
//【2.3.2.1】调用 AppIdleHistory.clearUsageLocked 方法!
mAppIdleHistory.clearUsageLocked(packageName, userId);
}
}
可以看到,调用的是 AppIdleHistory.clearUsageLocked 方法!
2.3.2.1 AppIdleHistory.clearUsageLocked
1 | public void clearUsageLocked(String packageName, int userId) { |
2.3.2.2 AppIdleHistory.getUserHistoryLocked
1 | private ArrayMap<String, PackageHistory> getUserHistoryLocked(int userId) { |
AppIdleHistory 内部有一个 mIdleHistory 集合,用于保存每个 userId 下的所有 package 的空闲历史信息!
2.3.2.3 AppIdleHistory.readAppIdleTimesLocked
1 | private void readAppIdleTimesLocked(int userId, ArrayMap<String, PackageHistory> userHistory) { |
我们来看看 /data/system/users/0/app_idle_stats.xml 文件中的主要内容:1
2
3
4<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<packages>
<package name="com.github.shadowsocks" elapsedIdleTime="6836535861" screenIdleTime="2173999944" />
</packages>
getUserFile 方法返回的是:/data/system/users/<userId>/app_idle_stats.xml
文件对象:1
2
3
4
5
6static final String APP_IDLE_FILENAME = "app_idle_stats.xml";
private File getUserFile(int userId) {
return new File(new File(new File(mStorageDir, "users"),
Integer.toString(userId)), APP_IDLE_FILENAME);
}
#####2.3.2.3.1 new PackageHistory
创建 PackageHistory 对象!1
2
3
4
5private static class PackageHistory {
final byte[] recent = new byte[HISTORY_SIZE];
long lastUsedElapsedTime;
long lastUsedScreenTime;
}
2.4 new DeviceStateReceiver - 监听设备状态
DeviceStateReceiver 监听设备状态的变化!1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private class DeviceStateReceiver extends BroadcastReceiver {
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
if (Intent.ACTION_BATTERY_CHANGED.equals(action)) {
//【2.4.1】设置充电状态!
setChargingState(intent.getIntExtra("plugged", 0) != 0);
} else if (PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED.equals(action)) {
//【2.4.2】处理 device idle (doze)模式变化!
onDeviceIdleModeChanged();
}
}
}
- 如果广播是 Intent.ACTION_BATTERY_CHANGED,说明此时正在充电,那么会调用 setChargingState 设置充电状态;
- 如果广播是 PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED,说明此时 device idle 模式的状态发生了变化;
2.4.1 UsageStatsS.setChargingState - 充电状态变化
1 | void setChargingState(boolean charging) { |
mCharging 表示当前设备是否正在充电,可以看到,只有当设备在未充电和充电状态之间变化!
2.4.1.1 UsageStatsS.postParoleStateChanged
1 | private void postParoleStateChanged() { |
发送 MSG_PAROLE_STATE_CHANGED 消息给 H!
2.4.2 UsageStatsS.onDeviceIdleModeChanged - doze 状态变化
当 device idle (doze)模式发生了变化后,onDeviceIdleModeChanged 方法会被触发:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24void onDeviceIdleModeChanged() {
//【1】调用 PowerManager.isDeviceIdleMode 方法,判断是否进入了 doze 模式!
final boolean deviceIdle = mPowerManager.isDeviceIdleMode();
if (DEBUG) Slog.i(TAG, "DeviceIdleMode changed to " + deviceIdle);
synchronized (mLock) {
//【2】计算距离里上一次的应用假释时间,已经过去的时间!
final long timeSinceLastParole = checkAndGetTimeLocked() - mLastAppIdleParoledTime;
if (!deviceIdle
&& timeSinceLastParole >= mAppIdleParoleIntervalMillis) {
if (DEBUG) Slog.i(TAG, "Bringing idle apps out of inactive state due to deviceIdleMode=false");
//【2.1】如果已经退出了 device idle 模式,并且距离上一次的应用假释时间已经超过了
// mAppIdleParoleIntervalMillis,那么我们就进入假释状态!
setAppIdleParoled(true);
} else if (deviceIdle) {
if (DEBUG) Slog.i(TAG, "Device idle, back to prison");
//【2.2】如果当前处于 device idle 状态,那么不允许应用假释;
setAppIdleParoled(false);
}
}
}
2.4.2.1 UsageStatsS.checkAndGetTimeLocked
checkAndGetTimeLocked 方法用于计算当前的时间!
1 | private long checkAndGetTimeLocked() { |
可以看到 checkAndGetTimeLocked 返回的时间是 actualSystemTime 的值,也就是 System.currentTimeMillis(),这个时间值可以被系统设置修改,然后值就会发生跳变,比如联网对时,手动调时!
而 actualRealtime 的值为 System.elapsedRealtime 自开机后,经过的时间,包括深度睡眠的时间,这部分时间值是不会被修改;
如果判断时间是否有发生调时,对时情况呢?
- mSystemTimeSnapshot 中保存的是上一次 check 时的系统时间;
- mRealTimeSnapshot 中保存的是上一次 check 时的自开机后,经过的时间;
- 先计算出期望的时间:
- 本次距离开机的时间 actualRealtime - 上次距离开机的时间 mRealTimeSnapshot,这个时间差值是正常情况下的时间差值;
- 然后再加上上一次 check 时的系统时间 mSystemTimeSnapshot,如果没有发生调时的话,这个应该是理想的时间点 expectedSystemTime;
- 如果发生了调时,对时的情况,actualSystemTime 一定是会发生变化的!
- 计算 actualSystemTime 和 expectedSystemTime 的差值,如果大于 TIME_CHANGE_THRESHOLD_MILLIS,说明铁定发生了调时,对时;
如果发生上述情况,那就调用 UserUsageStatsService.onTimeChanged 更新本地持久化文件的日期!
对于 UsageStatsService 是如何存储应用数据,如何更新本地持久化文件的,这里我先不关注,我们只需要知道,该方法返回的时间值是实际的时间(正常,手动调时,联网对时)
2.5 UsageStatsS.cleanUpRemovedUsersLocked - 删除被移除的 User 使用数据
删除已经被移除的 User 的使用数据!1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28private void cleanUpRemovedUsersLocked() {
//【1】获得所有的 user 信息!
final List<UserInfo> users = mUserManager.getUsers(true);
if (users == null || users.size() == 0) {
throw new IllegalStateException("There can't be no users");
}
//【2】如果 /data/system/usagestats 目录下没有任何文件,不处理!
ArraySet<String> toDelete = new ArraySet<>();
String[] fileNames = mUsageStatsDir.list();
if (fileNames == null) {
// No users to delete.
return;
}
//【3】去除那些存在的 user 的使用信息!
toDelete.addAll(Arrays.asList(fileNames));
final int userCount = users.size();
for (int i = 0; i < userCount; i++) {
final UserInfo userInfo = users.get(i);
toDelete.remove(Integer.toString(userInfo.id));
}
//【4】移除剩下的没用的 user 信息;
final int deleteCount = toDelete.size();
for (int i = 0; i < deleteCount; i++) {
// 递归删除!
deleteRecursively(new File(mUsageStatsDir, toDelete.valueAt(i)));
}
}
2.6 new AppIdleHistory - 管理 App Idle 信息
创建一个 AppIdleHistory 对象,保存 app idle 相关的状态和信息!
1 | AppIdleHistory(long elapsedRealtime) { |
mStorageDir 指向 /data/system 目录!
2.6.1 AppIdleHistory.readScreenOnTimeLocked
1 | private void readScreenOnTimeLocked() { |
整个流程很简单,不多说了!
2.7 publish LocalService
LocalService 用于系统进程中服务间的相互通信,本地服务实现主要由ActivityManagerService 使用。ActivityManagerService 调用这些方法的时会持有自身的锁,不应该在这些方法中执行任何 IO 工作或其他长时间运行的任务。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133private final class LocalService extends UsageStatsManagerInternal {
//【1】向 UsageStatsService 发送应用的使用信息 UsageEvents;
public void reportEvent(ComponentName component, int userId, int eventType) {
if (component == null) {
Slog.w(TAG, "Event reported without a component name");
return;
}
UsageEvents.Event event = new UsageEvents.Event();
event.mPackage = component.getPackageName();
event.mClass = component.getClassName();
// This will later be converted to system time.
event.mTimeStamp = SystemClock.elapsedRealtime();
event.mEventType = eventType;
//【1.1】发送 MSG_REPORT_EVENT 给 H;
mHandler.obtainMessage(MSG_REPORT_EVENT, userId, 0, event).sendToTarget();
}
public void reportEvent(String packageName, int userId, int eventType) {
if (packageName == null) {
Slog.w(TAG, "Event reported without a package name");
return;
}
UsageEvents.Event event = new UsageEvents.Event();
event.mPackage = packageName;
event.mTimeStamp = SystemClock.elapsedRealtime();
event.mEventType = eventType;
mHandler.obtainMessage(MSG_REPORT_EVENT, userId, 0, event).sendToTarget();
}
//【2】向 UsageStatsService 发送配置的使用信息 UsageEvents;
public void reportConfigurationChange(Configuration config, int userId) {
if (config == null) {
Slog.w(TAG, "Configuration event reported with a null config");
return;
}
UsageEvents.Event event = new UsageEvents.Event();
event.mPackage = "android";
event.mTimeStamp = SystemClock.elapsedRealtime();
event.mEventType = UsageEvents.Event.CONFIGURATION_CHANGE;
event.mConfiguration = new Configuration(config);
//【2.1】发送 MSG_REPORT_EVENT 给 H;
mHandler.obtainMessage(MSG_REPORT_EVENT, userId, 0, event).sendToTarget();
}
//【3】向 UsageStatsService 发送 Shortcut 的使用信息 UsageEvents;
public void reportShortcutUsage(String packageName, String shortcutId, int userId) {
if (packageName == null || shortcutId == null) {
Slog.w(TAG, "Event reported without a package name or a shortcut ID");
return;
}
UsageEvents.Event event = new UsageEvents.Event();
event.mPackage = packageName.intern();
event.mShortcutId = shortcutId.intern();
event.mTimeStamp = SystemClock.elapsedRealtime();
event.mEventType = Event.SHORTCUT_INVOCATION;
//【3.1】发送 MSG_REPORT_EVENT 给 H;
mHandler.obtainMessage(MSG_REPORT_EVENT, userId, 0, event).sendToTarget();
}
//【4】向 UsageStatsService 发送 ContentProvider 的使用信息;
public void reportContentProviderUsage(String name, String packageName, int userId) {
SomeArgs args = SomeArgs.obtain();
args.arg1 = name;
args.arg2 = packageName;
args.arg3 = userId;
//【4.1】发送 MSG_REPORT_CONTENT_PROVIDER_USAGE 给 H;
mHandler.obtainMessage(MSG_REPORT_CONTENT_PROVIDER_USAGE, args)
.sendToTarget();
}
//【5】判断应用是否处于 idle 状态;
public boolean isAppIdle(String packageName, int uidForAppId, int userId) {
return UsageStatsService.this.isAppIdleFiltered(packageName, uidForAppId, userId,
SystemClock.elapsedRealtime());
}
//【6】获得指定 userId 下的处于 idle 状态的 uid;
public int[] getIdleUidsForUser(int userId) {
return UsageStatsService.this.getIdleUidsForUser(userId);
}
//【7】获得指定 userId 下的处于 idle 状态的 uid;
public boolean isAppIdleParoleOn() {
return isParoledOrCharging();
}
//【8】关机时调用!
public void prepareShutdown() {
shutdown();
}
//【9】添加和移除 app idle 状态改变监听器
public void addAppIdleStateChangeListener(AppIdleStateChangeListener listener) {
UsageStatsService.this.addListener(listener);
listener.onParoleStateChanged(isAppIdleParoleOn());
}
public void removeAppIdleStateChangeListener(
AppIdleStateChangeListener listener) {
UsageStatsService.this.removeListener(listener);
}
public byte[] getBackupPayload(int user, String key) {
// Check to ensure that only user 0's data is b/r for now
if (user == UserHandle.USER_SYSTEM) {
final UserUsageStatsService userStats =
getUserDataAndInitializeIfNeededLocked(user, checkAndGetTimeLocked());
return userStats.getBackupPayload(key);
} else {
return null;
}
}
public void applyRestoredPayload(int user, String key, byte[] payload) {
if (user == UserHandle.USER_SYSTEM) {
final UserUsageStatsService userStats =
getUserDataAndInitializeIfNeededLocked(user, checkAndGetTimeLocked());
userStats.applyRestoredPayload(key, payload);
}
}
}
关于 LocalService 中的方法的触发,我们后面再看!
2.8 publish BinderService
BinderService 的所用是跨进程通信!
1 | private final class BinderService extends IUsageStatsManager.Stub { |
关于 BinderService 中的方法的触发,我们后面再看!
3 UsageStatsS.onBootPhase
1 |
|
onBootPhase 方法会在两个阶段下调用:
- PHASE_SYSTEM_SERVICES_READY:此时系统服务已经都启动了;
- PHASE_BOOT_COMPLETED:此时设备重启完成了;
3.1 new SettingsObserver - 监听数据库变化
这里会创建一个 SettingsObserver 观察者,监听数据表变化!1
2
3
4
5
6
7
8
9 private class SettingsObserver extends ContentObserver {
private final KeyValueListParser mParser = new KeyValueListParser(',');
SettingsObserver(Handler handler) {
super(handler);
}
... ... ...
}
3.1.1 SettingsObserver.registerObserver
监听的 Settings 表单是 app_idle_constants!1
2
3
4void registerObserver() {
getContext().getContentResolver().registerContentObserver(Settings.Global.getUriFor(
Settings.Global.APP_IDLE_CONSTANTS), false, this);
}
3.1.2 SettingsObserver.onChange
当数据库有变化后,会触发 onChange 方法:1
2
3
4
5
6
7
public void onChange(boolean selfChange) {
//【3.1.3】初始化数据!
updateSettings();
//【3.4】调用 postOneTimeCheckIdleStates 方法进行一次 idle 检查!
postOneTimeCheckIdleStates();
}
3.1.3 SettingsObserver.updateSettings
1 | void updateSettings() { |
这里涉及到几个重要的时间变量:
- mAppIdleScreenThresholdMillis:12 hours,是应用是否进入 idle 状态的临界值!
- mAppIdleWallclockThresholdMillis:48 hours,是应用是否进入 idle 状态的临界值!
- mCheckIdleIntervalMillis:表示执行 check idle 操作的时间间隔,8 hours!
- mAppIdleParoleIntervalMillis:相邻 2 次进入假释状态的时间间隔,24 hours!
- mAppIdleParoleDurationMillis:假释状态的持续时间,10mins!
这里的 COMPRESS_TIME 的值恒定为 false!
1 | static final boolean COMPRESS_TIME = false; |
3.1.3.1 AppIdleHistory.setThresholds
设置阈值信息,该阈值会用于判断应用是否处于 idle 状态!1
2
3
4public void setThresholds(long elapsedTimeThreshold, long screenOnTimeThreshold) {
mElapsedTimeThreshold = elapsedTimeThreshold;
mScreenOnTimeThreshold = screenOnTimeThreshold;
}
我们来解释下这两个阈值的作用:
3.2 register DisplayListener - 监听屏幕状态
UsageStatsService 内部有一个 DisplayListener 实例:mDisplayListener,专门用于监听屏幕的状态,然后触发 AppIdleHistory 的更新!
1 | private final DisplayManager.DisplayListener mDisplayListener |
当屏幕的状态发生变化后,会触发 DisplayListener.onDisplayChanged 方法,紧接着触发 AppIdleHistory.updateDisplayLocked 方法!
3.2.1 AppIdleHistory.updateDisplayLocked
updateDisplayLocked 方法用于更新屏幕状态信息!
1 | public void updateDisplayLocked(boolean screenOn, long elapsedRealtime) { |
通过 isDisplayOn 方法,来判断当前是亮屏还是熄屏:1
2
3
4private boolean isDisplayOn() {
return mDisplayManager
.getDisplay(Display.DEFAULT_DISPLAY).getState() == Display.STATE_ON;
}
AppIdleHistory 内部是有一个变量 mScreenOn,保存屏幕的状态信息,默认为 false:
mScreenOnSnapshot 用于每次亮屏的时间点,mElapsedSnapshot 用于记录每次灭屏的时间点;
mScreenOnDuration 用于累计所有亮屏的总时长,mElapsedDuration 用于了累计所有相邻两次灭屏的总时长!
如果是此时是亮屏的话,会将时间记录到 mScreenOnSnapshot 中!
3.4 UsageStatsS.postOneTimeCheckIdleStates
1 | void postOneTimeCheckIdleStates() { |
如果 mDeviceIdleController 为 null,说明 UsageStatsService 还没有启动完成,这里会先将 mPendingOneTimeCheckIdleStates 置为 true!等到启动完成后会判断该变量的值,如果为 true,那就会继续调用 postOneTimeCheckIdleStates 方法!
如果 mDeviceIdleController 不为 null,发送 MSG_ONE_TIME_CHECK_IDLE_STATES 消息给 H,触发 check idle 操作!同时设置 mPendingOneTimeCheckIdleStates 为 false;