[toc]
基于 Android7.1.1 源码分析 UsageStatsService 的架构和原理
UsageStatsService 定义的事件类型目前一共有 8 种类型,全部定义在 UsageEvents.java 中,如下:
1 | /** |
0 综述
UsageStatsService 的消息处理是在 H 中, 下面我们去看看具体的消息处理!
1 消息:MSG_REPORT_EVENT - OK
1.0 消息触发时机
当 AcitivtyManagerService 调用 LocalService 的以下方法的时候,会发送 MSG_REPORT_EVENT 消息!
1 | private final class LocalService extends UsageStatsManagerInternal { |
还有其他的 report 方法:1
2
3
4
5
6
7
8
9
public void reportEvent(String packageName, int userId, int eventType) {...}
public void reportConfigurationChange(Configuration config, int userId) {...}
public void reportShortcutUsage(String packageName, String shortcutId, int userId) {...}
}
这些方法会接收到传入的 event,然后发送 MSG_REPORT_EVENT 消息!
1 | case MSG_REPORT_EVENT: |
该消息会携带 2 个重要数据:UsageEvents 和 userId!
1.1 UsageStatsService.reportEvent
1 | void reportEvent(UsageEvents.Event event, int userId) { |
对于 UserUsageStatsService.reportEvent 方法,这里不再多说!
1.1.1 UsageStatsService.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 是如何存储应用数据,如何更新本地持久化文件的,这里我先不关注,我们只需要知道,该方法返回的时间值是实际的时间(正常,手动调时,联网对时)
对于 service.onTimeChanged 方法,这里不再分析!
1.1.2 AppIdleHistory.convertToSystemTimeLocked
UsageEvents.Event 的 mTimeStamp 是在设置的时候,是通过 SystemClock.elapsedRealtime() 计算的;1
2
3private void convertToSystemTimeLocked(UsageEvents.Event event) {
event.mTimeStamp = Math.max(0, event.mTimeStamp - mRealTimeSnapshot) + mSystemTimeSnapshot;
}
convertToSystemTimeLocked 的作用是:
- 将相对于开机的时间转为系统时间:SystemClock.elapsedRealtime() -> SystemClock.currentTimeMillis()!
1.1.3 UsageStatsS.getUserDataAndInitializeIfNeededLocked
尝试获得指定 userId 对应的 UserUsageStatsService 对象,封装了该 userId 的使用情况!
1 | private UserUsageStatsService getUserDataAndInitializeIfNeededLocked(int userId, |
这里要涉及到另外一个数据结构 UserUsageStatsService,UserUsageStatsService 用于保存每个 userId 的使用情况!
UsageStatsService 内部会通过 mUserState 来记录 userId 的使用情况:1
private final SparseArray<UserUsageStatsService> mUserState = new SparseArray<>();
1.1.3.1 new UserUsageStatsService
创建一个 UserUsageStatsService 对象!1
2
3
4
5
6
7
8
9
10UserUsageStatsService(Context context, int userId, File usageStatsDir,
StatsUpdatedListener listener) {
mContext = context;
mDailyExpiryDate = new UnixCalendar(0);
mDatabase = new UsageStatsDatabase(usageStatsDir);
mCurrentStats = new IntervalStats[UsageStatsManager.INTERVAL_COUNT];
mListener = listener;
mLogPrefix = "User[" + Integer.toString(userId) + "] ";
mUserId = userId;
}
1.1.4 AppIdleHistory.isIdleLocked
判断在 elapsedRealtime 指定的时间点下,该 package 是否是 idle 状态!
1 | public boolean isIdleLocked(String packageName, int userId, long elapsedRealtime) { |
1.1.4.1 AppIdleHistory.hasPassedThresholdsLocked
接下来看看系统是如何判断一个应用是否进入 idle 状态!1
2
3
4
5
6private boolean hasPassedThresholdsLocked(PackageHistory packageHistory, long elapsedRealtime) {
return (packageHistory.lastUsedScreenTime
<= getScreenOnTimeLocked(elapsedRealtime) - mScreenOnTimeThreshold)
&& (packageHistory.lastUsedElapsedTime
<= getElapsedTimeLocked(elapsedRealtime) - mElapsedTimeThreshold);
}
需要满足 2 个条件:
- 自开机时算起,当前时间距离应用上次被使用的时间(自开机时算起),超过了 24 小时!
- 只算亮屏时间,当前时间距离应用上次被使用的时间(只算亮屏时间),超过了 12 小时!
1.1.6 AppIdleHistory.reportUsageLocked
根据 report event 的时间点,更新 app idle 相关的时间信息!
1 | public void reportUsageLocked(String packageName, int userId, long elapsedRealtime) { |
我们看到,当每次 report event 后,会更新 PackageHistory 的如下两个变量:
- lastUsedElapsedTime:自开机后,应用最后一次被使用的时间;
- lastUsedScreenTime:只计算亮屏时间的情况下,应用最后一次被使用的时间;
2 消息:MSG_FLUSH_TO_DISK - OK
2.0 消息触发时机
在如下的情况下会触发 UsageStatsService.onStatsUpdated :
- UserUsageStatsService.init 方法在加载完数据后,会将 LastEvent 为 MOVE_TO_FOREGROUND 或者 CONTINUE_PREVIOUS_DAY 的 UsageStats 的 LastEvent 改为 END_OF_DAY,此时会触发 notifyStatsChanged 方法!
- UserUsageStatsService.reportEvent 方法处理完上报的事件后,会触发 notifyStatsChanged 方法!
- UserUsageStatsService.rolloverStats 方法一天时间过去会后,数据进行回滚后,会触发 notifyStatsChanged 方法!
当 UserUsageStatsService 的 notifyStatsChanged 触发后,会触发 UsageStatsService.onStatsUpdated 方法:1
2
3
4
5
6private void notifyStatsChanged() {
if (!mStatsChanged) {
mStatsChanged = true;
mListener.onStatsUpdated();
}
}
在 UsageStatsService.onStatsUpdated 方法中会发送 MSG_FLUSH_TO_DISK 消息!1
2
3
4
5@Override
public void onStatsUpdated() {
//【1】延迟 20mins 发送 MSG_FLUSH_TO_DISK 消息!
mHandler.sendEmptyMessageDelayed(MSG_FLUSH_TO_DISK, FLUSH_INTERVAL);
}
这里延迟了 FLUSH_INTERVAL 时间间隔发送 MSG_FLUSH_TO_DISK 消息!1
2
3
4
5static final boolean COMPRESS_TIME = false;
private static final long TEN_SECONDS = 10 * 1000;
private static final long TWENTY_MINUTES = 20 * 60 * 1000;
private static final long FLUSH_INTERVAL = COMPRESS_TIME ? TEN_SECONDS : TWENTY_MINUTES;
由于 COMPRESS_TIME 为 false,所以是 20mins!
最后进入 H:1
2
3
4case MSG_FLUSH_TO_DISK:
//【2.1】进入 flushToDisk 方法!
flushToDisk();
break;
MSG_FLUSH_TO_DISK 消息用于将 App idle 的数据写入本地持久化文件!
2.1 UsageStatsService.flushToDisk
调用了 flushToDisk 方法:1
2
3
4
5
6void flushToDisk() {
synchronized (mLock) {
//【2.2】调用了 flushToDiskLocked 方法!
flushToDiskLocked ();
}
}
继续来看:
2.2 UsageStatsService.flushToDiskLocked
1 | private void flushToDiskLocked() { |
对于 UserUsageStatsService.persistActiveStats,这里我们不再分析,可以看 UserUsageStatsService 分析!
2.2.1 AppIdleHistory.writeAppIdleTimesLocked
将系统中的 app idle 信息保存到本地文件中!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
39public void writeAppIdleTimesLocked(int userId) {
FileOutputStream fos = null;
//【1】目标文件 /data/system/users/<userId>/app_idle_stats.xml 文件
AtomicFile appIdleFile = new AtomicFile(getUserFile(userId));
try {
fos = appIdleFile.startWrite();
final BufferedOutputStream bos = new BufferedOutputStream(fos);
FastXmlSerializer xml = new FastXmlSerializer();
xml.setOutput(bos, StandardCharsets.UTF_8.name());
xml.startDocument(null, true);
xml.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
xml.startTag(null, TAG_PACKAGES); // packages 标签
ArrayMap<String,PackageHistory> userHistory = getUserHistoryLocked(userId);
final int N = userHistory.size();
for (int i = 0; i < N; i++) {
//【1】遍历处理每一个 PackageHistory 对象!
String packageName = userHistory.keyAt(i);
PackageHistory history = userHistory.valueAt(i);
xml.startTag(null, TAG_PACKAGE); // package 标签
xml.attribute(null, ATTR_NAME, packageName); // name 标签
xml.attribute(null, ATTR_ELAPSED_IDLE,
Long.toString(history.lastUsedElapsedTime)); // elapsedIdleTime 标签
xml.attribute(null, ATTR_SCREEN_IDLE,
Long.toString(history.lastUsedScreenTime)); // screenIdleTime 标签
xml.endTag(null, TAG_PACKAGE);
}
xml.endTag(null, TAG_PACKAGES);
xml.endDocument();
appIdleFile.finishWrite(fos);
} catch (Exception e) {
appIdleFile.failWrite(fos);
Slog.e(TAG, "Error writing app idle file for user " + userId);
}
}
我们来看看,保存后的文件内容:1
2
3<packages>
<package name="com.github.shadowsocks" elapsedIdleTime="6836535861" screenIdleTime="2173999944" />
</packages>
2.2.2 AppIdleHistory.writeAppIdleDurationsLocked
将系统中的屏幕亮灭时间信息保存到本地文件中!
1 | public void writeAppIdleDurationsLocked() { |
2.2.2.1 AppIdleHistory.writeScreenOnTimeLocked
1 | private void writeScreenOnTimeLocked() { |
我们来看看 /data/system/screen_on_time 文件内容!1
22201351882
6898458370
3 消息:MSG_REMOVE_USER - OK
1 | case MSG_REMOVE_USER: |
3.0 消息触发机制
UsageStatsService 内部有一个广播接收者 UserActionsReceiver,用于监听 User 相关的广播!
1 | private class UserActionsReceiver extends BroadcastReceiver { |
当其接收到用户被移除的广播,接受到被启动的 UserId,会发送 MSG_REMOVE_USER 消息!
3.1 UsageStatsService.onUserRemoved
这里调用了 onUserRemoved 方法,我们去看看该方法的逻辑:1
2
3
4
5
6
7
8
9
10
11void onUserRemoved(int userId) {
synchronized (mLock) {
Slog.i(TAG, "Removing user " + userId + " and all data.");
//【1】从 mUserState 中移除该 userId 对应的 UserUsageStatsService 实例;
mUserState.remove(userId);
//【3.1.1】从 AppIdleHistory 中移除该 userId 下的 app idle 信息;
mAppIdleHistory.onUserRemoved(userId);
//【3】清楚被移除的 userId 对应的 UsageStats 信息!
cleanUpRemovedUsersLocked();
}
}
3.1.1 AppIdleHistory.onUserRemoved
1 | public void onUserRemoved(int userId) { |
流程很简单,不多说了!
4 消息:MSG_INFORM_LISTENERS - OK
4.0 消息触发时机
1 | case MSG_INFORM_LISTENERS: |
4.1 UsageStatsService.informListeners
informListeners 用于通知那件监听 app idle 的应用,app idle 状态的变化!1
2
3
4
5void informListeners(String packageName, int userId, boolean isIdle) {
for (AppIdleStateChangeListener listener : mPackageAccessListeners) {
listener.onAppIdleStateChanged(packageName, userId, isIdle);
}
}
逻辑很见简单,不多说了!
5 消息:MSG_FORCE_IDLE_STATE
5.0 消息触发时机
当 BinderService 的 setAppInactive 方法触发后!
1 | private final class BinderService extends IUsageStatsManager.Stub { |
UsageStatsService.setAppIdle 会发送 MSG_FORCE_IDLE_STATE 消息给 H:1
2
3
4
5
6void setAppIdle(String packageName, boolean idle, int userId) {
if (packageName == null) return;
mHandler.obtainMessage(MSG_FORCE_IDLE_STATE, userId, idle ? 1 : 0, packageName)
.sendToTarget();
}
我们来看看 H 对于消息 MSG_FORCE_IDLE_STATE 的处理:1
2
3
4case MSG_FORCE_IDLE_STATE:
//【5.1】调用 forceIdleState 将 app 设置为 idle 状态!
forceIdleState((String) msg.obj, msg.arg1, msg.arg2 == 1);
break;
5.1 UsageStatsService.forceIdleState
设置 app 进入 idle 状态:
1 | void forceIdleState(String packageName, int userId, boolean idle) { |
5.1.1 AppIdleHistory.setIdleLocked
设置 package 的 idle 状态!1
2
3
4
5
6
7
8
9
10
11
12public void setIdleLocked(String packageName, int userId, boolean idle, long elapsedRealtime) {
//【1】同样先获得该 userId 下该 packageName 的 PackageHistory 对象!
ArrayMap<String, PackageHistory> userHistory = getUserHistoryLocked(userId);
PackageHistory packageHistory = getPackageHistoryLocked(userHistory, packageName,
elapsedRealtime);
//【5.1.1.1】设置 package 上一次被使用的时间
packageHistory.lastUsedElapsedTime = getElapsedTimeLocked(elapsedRealtime)
- mElapsedTimeThreshold;
packageHistory.lastUsedScreenTime = getScreenOnTimeLocked(elapsedRealtime)
- (idle ? mScreenOnTimeThreshold : 0) - 1000 /* just a second more */;
}
对于 getUserHistoryLocked 和 getPackageHistoryLocked 我们不再重新分析!
5.1.1.1 AppIdleHistory.getElapsedTimeLocked
1 | private long getElapsedTimeLocked(long elapsedRealtime) { |
5.1.1.2 AppIdleHistory.getScreenOnTimeLocked
获得亮屏的总时长!
1 | public long getScreenOnTimeLocked(long elapsedRealtime) { |
如果此时亮屏 mScreenOn 为 true,那么会在 mScreenOnDuration 基础上,再加上当前时间减去上次亮屏的时间点 mScreenOnSnapshot!
5.1.2 UsageStatsService.notifyBatteryState
通知 PowerManagerService,app 退出了 idle 状态!
1 | private void notifyBatteryStats(String packageName, int userId, boolean idle) { |
6 消息:MSG_CHECK_IDLE_STATES
1 | case MSG_CHECK_IDLE_STATES: |
6.0 消息触发机制
UsageStatsService 内部有一个广播接收者 UserActionsReceiver,用于监听 User 相关的广播!
1 | private class UserActionsReceiver extends BroadcastReceiver { |
当其接收到用户被启动的广播,接受到被启动的 UserId:1
2
3void postCheckIdleStates(int userId) {
mHandler.sendMessage(mHandler.obtainMessage(MSG_CHECK_IDLE_STATES, userId, 0));
}
MSG_CHECK_IDLE_STATES 用于检查指定 userid 下的 app idle 状态:
1 | case MSG_CHECK_IDLE_STATES: |
checkIdleStates 返回 true 表示检查操作完成,那么就会设置下一次的 check idle 的消息的发送时间,延迟 mCheckIdleIntervalMillis 8 hours 后再次发送 MSG_CHECK_IDLE_STATES 消息!
关于 mCheckIdleIntervalMillis 的取值,可以看下 SettingsObserver 相关代码!
6.1 UsageStatsService.checkIdleStates
checkIdleStates 方法用于检查当前时间系统中 app idle 的状态,同时更新 AppIdleHistory 中的数据!
1 | boolean checkIdleStates(int checkUserId) { |
6.1.1 UsageStatsService.isAppIdleFiltered
isAppIdleFiltered 方法用于过滤一些特殊的条件!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
54private boolean isAppIdleFiltered(String packageName, int appId, int userId,
long elapsedRealtime) {
if (packageName == null) return false;
//【1】如果不允许应用进入 idle 状态,显然,没有应用会处于 idle 状态!
if (!mAppIdleEnabled) {
return false;
}
//【2】如果 appId 小于 FIRST_APPLICATION_UID,说明是系统应用,系统应用不能进入 idle 状态!
if (appId < Process.FIRST_APPLICATION_UID) {
return false;
}
//【3】如果包名是 "android",不能处于 idle 状态!
if (packageName.equals("android")) {
return false;
}
if (mSystemServicesReady) {
try {
//【4】如果 package 在 doze 模式的白名单中,那就不能进入 app idle (app standby) 状态
if (mDeviceIdleController.isPowerSaveWhitelistExceptIdleApp(packageName)) {
return false;
}
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
}
//【5】接着处理一些特殊情况,满足条件,也不能处于 idle 状态!
if (isActiveDeviceAdmin(packageName, userId)) {
return false;
}
if (isActiveNetworkScorer(packageName)) {
return false;
}
if (mAppWidgetManager != null
&& mAppWidgetManager.isBoundWidgetPackage(packageName, userId)) {
return false;
}
if (isDeviceProvisioningPackage(packageName)) {
return false;
}
}
//【*6.1.1.1】进入 isAppIdleUnfiltered 方法!
if (!isAppIdleUnfiltered(packageName, userId, elapsedRealtime)) {
return false;
}
//【7】如果是运营商特权应用,不能进入 idle 状态!
if (isCarrierApp(packageName)) {
return false;
}
return true;
}
接着来看 isAppIdleUnfiltered 方法!
6.1.1.1 UsageStatsService.isAppIdleUnfiltered
isAppIdleUnfiltered 方法是不过滤条件,直接读取数据,判断 app 是否处于 idle 状态!一般是先调用 isAppIdleFiltered 过滤特殊情况,然后再调用该方法:1
2
3
4
5
6private boolean isAppIdleUnfiltered(String packageName, int userId, long elapsedRealtime) {
synchronized (mLock) {
//【×1.4】调用 isIdleLocked 判断是否是 idle 状态!
return mAppIdleHistory.isIdleLocked(packageName, userId, elapsedRealtime);
}
}
我们看到,是直接调用 AppIdleHistory.isIdleLocked 方法读取数据!
对于 AppIdleHistory.isIdleLocked 方法,这里不再分析!
6.1.2 AppIdleHistory.setIdle
1 | public void setIdle(String packageName, int userId, long elapsedRealtime) { |
6.1.2.1 AppIdleHistory.getPackageHistoryLocked
从指定的 userId 的 userHistory 获得该 package 的 PackageHistory 对象,如果没有就重新创建一个!
1 | private PackageHistory getPackageHistoryLocked(ArrayMap<String, PackageHistory> userHistory, |
6.1.2.1.1 AppIdleHistory.getElapsedTimeLocked
1 | private long getElapsedTimeLocked(long elapsedRealtime) { |
6.1.2.1.2 AppIdleHistory.getScreenOnTimeLocked
获得亮屏的总时长!
1 | public long getScreenOnTimeLocked(long elapsedRealtime) { |
如果此时亮屏 mScreenOn 为 true,那么会在 mScreenOnDuration 基础上,再加上当前时间减去上次亮屏的时间点 mScreenOnSnapshot!
6.1.2.2 AppIdleHistory.shiftHistoryToNow
1 | private void shiftHistoryToNow(ArrayMap<String, PackageHistory> userHistory, |
mLastPeriod 用来记录能够更新的周期!
7 消息:MSG_ONE_TIME_CHECK_IDLE_STATES - OK
7.1 消息触发时机
- UsageStatsService.onBootPhase
1 |
|
在 UsageStatsService 开机初始化的时候,会进行一次 app idle check!
- SettingsObserver.onChange
1 |
|
当数据库时间属性变化后!
- UsageStatsService.onStatsReloaded
1 |
|
这几种情况下,都是开机初始化,或者数据库配置更新,使用状态重新加载的情况,这些情况会导致 app idle 发生变化,所以需要重新 check idle!
1 | case MSG_ONE_TIME_CHECK_IDLE_STATES: |
7.2 UsageStatsService.postOneTimeCheckIdleStates
1 | void postOneTimeCheckIdleStates() { |
MSG_ONE_TIME_CHECK_IDLE_STATES 也用于检查 device idle 状态!
1 | case MSG_ONE_TIME_CHECK_IDLE_STATES: |
和 MSG_CHECK_IDLE_STATES 的区别是:
- MSG_ONE_TIME_CHECK_IDLE_STATES 只会检测一次,而 MSG_CHECK_IDLE_STATES 会持续检查!
- MSG_ONE_TIME_CHECK_IDLE_STATES 会检查所有 user 下的 app idle 状态,而 MSG_CHECK_IDLE_STATES 是检查指定的 userId 下的 app idle 状态;
8 消息:MSG_CHECK_PAROLE_TIMEOUT
该消息用于进入下一次假释状态!
8.1 消息触发时机
我们在 DeviceStateReceiver 接收者中,当接收到 PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED 广播后,即 doze 模式发生了变化,我们会调用 onDeviceIdleModeChanged 方法!
1 | void onDeviceIdleModeChanged() { |
- UsageStatsService.setAppIdleParoled
setAppIdleParoled 方法用于将 app 在 idle 的状态下唤醒进入假释状态,参数 boolean paroled 表示当前是否进入假释状态!
mAppIdleTempParoled 用于保存假释状态,boolean paroled 表示是否进入假释状态!1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24void setAppIdleParoled(boolean paroled) {
synchronized (mLock) {
if (mAppIdleTempParoled != paroled) {
//【1】缓存本次假释状态!
mAppIdleTempParoled = paroled;
if (DEBUG) Slog.d(TAG, "Changing paroled to " + mAppIdleTempParoled);
if (paroled) {
//【1.1】如果本次是进入假释,设置退出假释的超时消息!
postParoleEndTimeout();
} else {
//【1.2】如果本次是退出假释,设置下一次进入假释的消息,即 24Hours 后!
// 保存上次退出假释的时间!
mLastAppIdleParoledTime = checkAndGetTimeLocked();
//【*8.1.1】设置进入下一次假释的消息!
postNextParoleTimeout();
}
//【common】处理假释状态的变化!
postParoleStateChanged();
}
}
}
最后,调用了 postNextParoleTimeout 方法!
8.1.1 UsageStatsService.postNextParoleTimeout
1 | private void postNextParoleTimeout() { |
我么你可以看到,延迟发送 MSG_CHECK_PAROLE_TIMEOUT 的时间是剩余的等待时间!!
我们来看看 H 对 MSG_CHECK_PAROLE_TIMEOUT 消息的处理:1
2
3
4case MSG_CHECK_PAROLE_TIMEOUT:
//【8.2】调用了 checkParoleTimeout 方法!
checkParoleTimeout();
break;
8.2 UsageStatsService.checkParoleTimeout
当该消息触发后,进入 checkParoleTimeout 方法!
1 | void checkParoleTimeout() { |
当距离上次退出假释模式经过的时间如果超过了 24 hours 后,会调用 setAppIdleParoled 方法进入假释模式!
整个流程很简单,不多说了!
9 消息:MSG_PAROLE_END_TIMEOUT
该消息用于退出当前的假释状态!
9.1 消息触发时机
当 app 进入了假释状态时,会触发 setAppIdleParoled 方法,此时 boolean paroled 为 true!
1 | void setAppIdleParoled(boolean paroled) { |
- UsageStatsService.postParoleEndTimeout
1
2
3
4
5
6private void postParoleEndTimeout() {
if (DEBUG) Slog.d(TAG, "Posting MSG_PAROLE_END_TIMEOUT");
//【1】延迟 10mins,发送 MSG_PAROLE_END_TIMEOUT 消息,退出假释模式!
mHandler.removeMessages(MSG_PAROLE_END_TIMEOUT);
mHandler.sendEmptyMessageDelayed(MSG_PAROLE_END_TIMEOUT, mAppIdleParoleDurationMillis);
}
mAppIdleParoleDurationMillis 值为 10mins!
我们来看看 H 是如处理该消息的:1
2
3
4
5case MSG_PAROLE_END_TIMEOUT:
if (DEBUG) Slog.d(TAG, "Ending parole");
//【1】调用了 setAppIdleParoled 退出假释模式,同时设置下一次进入假释的消息!
setAppIdleParoled(false);
break;
10 消息:MSG_REPORT_CONTENT_PROVIDER_USAGE - OK
10.0 消息触发时机
当 ActivityManagerService 触发了 LocalService 下面的方法时:
1 | private final class LocalService extends UsageStatsManagerInternal { |
会发送 MSG_REPORT_CONTENT_PROVIDER_USAGE 消息!上报 ContentProvider 的使用情况!
1 | case MSG_REPORT_CONTENT_PROVIDER_USAGE: |
10.1 UsageStatsService.reportContentProviderUsage
1 | void reportContentProviderUsage(String authority, String providerPkgName, int userId) { |
对于 forceIdleState 方法,请看 5.1 节!
11 消息:MSG_PAROLE_STATE_CHANGED
当应用的假释状态发生变化后,会发送该消息!
11.1 消息触发时机
- UsageStatsService.setChargingState
当充电状态发生变化时,会触发 postParoleStateChanged 方法!1
2
3
4
5
6
7
8
9void setChargingState(boolean charging) {
synchronized (mLock) {
if (mCharging != charging) {
mCharging = charging;
//【*11.1.1-common】当充电状态发生变化后,处理假释状态的变化!
postParoleStateChanged();
}
}
}
我们在 UsageStatsService.onBootPhase 方法中会初始化下开机时的充电状态,这时候会调用 setChargingState 方法!
同时,我们在 DeviceStateReceiver 接收者中,当接收到 Intent.ACTION_BATTERY_CHANGED 广播后,我们会调用 setChargingState 方法!
- UsageStatsService.setAppIdleParoled
我们在 DeviceStateReceiver 接收者中,当接收到 PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED 广播后,即 doze 模式发生了变化,我们会调用 onDeviceIdleModeChanged 方法!
1 | void onDeviceIdleModeChanged() { |
setAppIdleParoled 方法用于将 app 在 idle 的状态下唤醒进入假释状态,参数 boolean paroled 表示当前是否进入假释状态!
mAppIdleTempParoled 用于保存假释状态!1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22void setAppIdleParoled(boolean paroled) {
synchronized (mLock) {
if (mAppIdleTempParoled != paroled) {
//【1】缓存本次假释状态!
mAppIdleTempParoled = paroled;
if (DEBUG) Slog.d(TAG, "Changing paroled to " + mAppIdleTempParoled);
if (paroled) {
//【1.1】如果本次是进入假释,设置退出假释的超时消息!
postParoleEndTimeout();
} else {
//【1.2】如果本次是退出假释,设置下一次进入假释的消息!
// 保存上次假释的最后时间!
mLastAppIdleParoledTime = checkAndGetTimeLocked();
postNextParoleTimeout();
}
//【*11.1.1-common】处理假释状态的变化!
postParoleStateChanged();
}
}
}
最后,调用了 postParoleStateChanged 方法!
11.1.1 UsageStatsService.postParoleStateChanged
postParoleStateChanged 会发送 MSG_PAROLE_STATE_CHANGED 消息给 H:1
2
3
4
5private void postParoleStateChanged() {
if (DEBUG) Slog.d(TAG, "Posting MSG_PAROLE_STATE_CHANGED");
mHandler.removeMessages(MSG_PAROLE_STATE_CHANGED);
mHandler.sendEmptyMessage(MSG_PAROLE_STATE_CHANGED);
}
我们去看看 H 是如何处理 MSG_PAROLE_STATE_CHANGED 消息的:1
2
3
4
5
6case MSG_PAROLE_STATE_CHANGED:
if (DEBUG) Slog.d(TAG, "Parole state: " + mAppIdleTempParoled
+ ", Charging state:" + mCharging);
//【*11.2】处理 app 假释状态改变的消息!
informParoleStateChanged();
break;
11.2 UsageStatsService.informParoleStateChanged
当假释状态改变后,会通知所有的监听者!1
2
3
4
5
6void informParoleStateChanged() {
final boolean paroled = isParoledOrCharging();
for (AppIdleStateChangeListener listener : mPackageAccessListeners) {
listener.onParoleStateChanged(paroled);
}
}