Android 7.0加强了idle模式,就是设备不充电且屏幕关闭情况下就会逐渐进入idle模式,这个比6.0加强了限制,也就是不考虑手机是否静止。idle模式的影响可以查看google官网介绍,这里就不多说了。这边主要介绍的是如何处理idle,如果你的应用需要用到推送等功能时,这种模式就是一个致命打击,那么有没有办法应对呢,答案当然是有了,官网推荐的是使用google自带服务GCM,但是国内需要,所以这种办法不太现实,那么就头疼了,有没有其他办法呢?
可以说是无意中一个发现让我了解到了另一种方式,之前在测试一个应用在android N上面关于Idle模式的影响时发现其不受影响,这就觉得很奇怪了,于是经过一番测试发现好像是由于前台service造成的,但是又不确定,于是在好奇心的驱动下把源码下了下来,经过一番分析,还真是这个原因,接下来就来具体分析一下:
一、DeviceIdleController.java
(framework/base/services/core/java/com/android/server/DeviceIdleController.java),这个类就是主要处理Idle模式的控制类,首先从这个类下手,下面是启动Idle模式时的处理方法:
case MSG_REPORT_IDLE_ON_LIGHT: {
EventLogTags.writeDeviceIdleOnStart();
final boolean deepChanged;
final boolean lightChanged;
if (msg.what == MSG_REPORT_IDLE_ON) {
deepChanged = mLocalPowerManager.setDeviceIdleMode(true);
lightChanged = mLocalPowerManager.setLightDeviceIdleMode(false);
} else {
deepChanged = mLocalPowerManager.setDeviceIdleMode(false);
lightChanged = mLocalPowerManager.setLightDeviceIdleMode(true);
}
try {
mNetworkPolicyManager.setDeviceIdleMode(true);
mBatteryStats.noteDeviceIdleMode(msg.what == MSG_REPORT_IDLE_ON
? BatteryStats.DEVICE_IDLE_MODE_DEEP
: BatteryStats.DEVICE_IDLE_MODE_LIGHT, null, Process.myUid());
} catch (RemoteException e) {
}
if (deepChanged) {
getContext().sendBroadcastAsUser(mIdleIntent, UserHandle.ALL);
}
if (lightChanged) {
getContext().sendBroadcastAsUser(mLightIdleIntent, UserHandle.ALL);
}
EventLogTags.writeDeviceIdleOnComplete();
} break;
从上面可以看出,进入Idle模式主要执行三个操作
1)mLocalPowerManager.setDeviceIdleMode(true);
2)mNetworkPolicyManager.setDeviceIdleMode(true);
3)mBatteryStats.noteDeviceIdleMode(..)
我们比较关心的是网络方面的,所以接下来主要分析mNetworkPolicyManager。
二、NetworkPolicyManager.java
(framework/base/services/core/java/com/android/server/net/NetworkPolicyManager.java)这个类就是mNetworkPolicyManager实例对象,首先看setDeviceIdleMode函数:
public void setDeviceIdleMode(boolean enabled) {
mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG);
synchronized (mRulesLock) {
if (mDeviceIdleMode != enabled) {
mDeviceIdleMode = enabled;
if (mSystemReady) {
// Device idle change means we need to rebuild rules for all
// known apps, so do a global refresh.
updateRulesForGlobalChangeLocked(false);
}
if (enabled) {
EventLogTags.writeDeviceIdleOnPhase("net");
} else {
EventLogTags.writeDeviceIdleOffPhase("net");
}
}
}
}
从上面可以看出主要调用的函数是updateRulesForGlobalChangeLocked(false);那我们继续跟踪下去:
Private void updateRulesForGlobalChangeLocked(boolean restrictedNetworksChanged) {
long start;
if (LOGD) start = System.currentTimeMillis();
updateRulesForDeviceIdleLocked();
updateRulesForAppIdleLocked();
updateRulesForRestrictPowerLocked();
updateRulesForRestrictBackgroundLocked();
setRestrictBackgroundLocked(mRestrictBackground);
// If the set of restricted networks may have changed, re-evaluate those.
if (restrictedNetworksChanged) {
normalizePoliciesLocked();
updateNetworkRulesLocked();
}
if (LOGD) {
final long delta = System.currentTimeMillis() - start;
Slog.d(TAG, "updateRulesForGlobalChangeLocked(" + restrictedNetworksChanged + ") took "
+ delta + "ms");
}
}
这个函数里面前面几个调用都是更新所有安装应用rule的,主要还是setRestrictBackgroundLocked(mRestrictBackground);那我们接着来看这个函数:
private void setRestrictBackgroundLocked(boolean restrictBackground) {
Slog.d(TAG, "setRestrictBackgroundLocked(): " + restrictBackground);
final boolean oldRestrictBackground = mRestrictBackground;
mRestrictBackground = restrictBackground;
// Must whitelist foreground apps before turning data saver mode on.
// TODO: there is no need to iterate through all apps here, just those in the foreground,
// so it could call AM to get the UIDs of such apps, and iterate through them instead.
updateRulesForRestrictBackgroundLocked();
try {
if (!mNetworkManager.setDataSaverModeEnabled(mRestrictBackground)) {
Slog.e(TAG, "Could not change Data Saver Mode on NMS to " + mRestrictBackground);
mRestrictBackground = oldRestrictBackground;
// TODO: if it knew the foreground apps (see TODO above), it could call
// updateRulesForRestrictBackgroundLocked() again to restore state.
return;
}
} catch (RemoteException e) {
// ignored; service lives in system_server
}
updateNotificationsLocked();
writePolicyLocked();
}
重点来了,我们看到里面的注释中出现foreground apps,这个说明Idle模式和foreground apps还是有关系的,分析代码可以发现主要函数是updateRulesForRestrictBackgroundLocked();那我们继续下去,感觉快得出答案了:
private void updateRulesForRestrictBackgroundLocked() {
final PackageManager pm = mContext.getPackageManager();
// update rules for all installed applications
final List users = mUserManager.getUsers();
final List apps = pm.getInstalledApplications(
PackageManager.MATCH_UNINSTALLED_PACKAGES | PackageManager.MATCH_DISABLED_COMPONENTS
| PackageManager.MATCH_DIRECT_BOOT_AWARE
| PackageManager.MATCH_DIRECT_BOOT_UNAWARE);
final int usersSize = users.size();
final int appsSize = apps.size();
for (int i = 0; i < usersSize; i++) {
final UserInfo user = users.get(i);
for (int j = 0; j < appsSize; j++) {
final ApplicationInfo app = apps.get(j);
final int uid = UserHandle.getUid(user.id, app.uid);
updateRulesForDataUsageRestrictionsLocked(uid);
updateRulesForPowerRestrictionsLocked(uid);
}
}
}
这个函数比较容易分析,主要就是两个函数调用,这两个函数都是用来更新rule的,所以基本流程是一样的,我们分析一个就行了,这里选择updateRulesForPowerRestrictionsLocked(uid);
private void updateRulesForPowerRestrictionsLocked(int uid) {
if (!isUidValidForBlacklistRules(uid)) {
if (LOGD) Slog.d(TAG, "no need to update restrict power rules for uid " + uid);
return;
}
final boolean isIdle = isUidIdle(uid);
final boolean restrictMode = isIdle || mRestrictPower || mDeviceIdleMode;
final int uidPolicy = mUidPolicy.get(uid, POLICY_NONE);
final int oldUidRules = mUidRules.get(uid, RULE_NONE);
final boolean isForeground = isUidForegroundOnRestrictPowerLocked(uid);
final boolean isWhitelisted = isWhitelistedBatterySaverLocked(uid);
final int oldRule = oldUidRules & MASK_ALL_NETWORKS;
int newRule = RULE_NONE;
// First step: define the new rule based on user restrictions and foreground state.
// NOTE: if statements below could be inlined, but it's easier to understand the logic
// by considering the foreground and non-foreground states.
if (isForeground) {
if (restrictMode) {
newRule = RULE_ALLOW_ALL;
}
} else if (restrictMode) {
newRule = isWhitelisted ? RULE_ALLOW_ALL : RULE_REJECT_ALL;
}
final int newUidRules = (oldUidRules & MASK_METERED_NETWORKS) | newRule;
if (LOGV) {
Log.v(TAG, "updateRulesForNonMeteredNetworksLocked(" + uid + ")"
+ ", isIdle: " + isIdle
+ ", mRestrictPower: " + mRestrictPower
+ ", mDeviceIdleMode: " + mDeviceIdleMode
+ ", isForeground=" + isForeground
+ ", isWhitelisted=" + isWhitelisted
+ ", oldRule=" + uidRulesToString(oldRule)
+ ", newRule=" + uidRulesToString(newRule)
+ ", newUidRules=" + uidRulesToString(newUidRules)
+ ", oldUidRules=" + uidRulesToString(oldUidRules));
}
if (newUidRules == RULE_NONE) {
mUidRules.delete(uid);
} else {
mUidRules.put(uid, newUidRules);
}
// Second step: notify listeners if state changed.
if (newRule != oldRule) {
if (newRule == RULE_NONE || (newRule & RULE_ALLOW_ALL) != 0) {
if (LOGV) Log.v(TAG, "Allowing non-metered access for UID " + uid);
} else if ((newRule & RULE_REJECT_ALL) != 0) {
if (LOGV) Log.v(TAG, "Rejecting non-metered access for UID " + uid);
} else {
// All scenarios should have been covered above
Log.wtf(TAG, "Unexpected change of non-metered UID state for " + uid
+ ": foreground=" + isForeground
+ ", whitelisted=" + isWhitelisted
+ ", newRule=" + uidRulesToString(newUidRules)
+ ", oldRule=" + uidRulesToString(oldUidRules));
}
mHandler.obtainMessage(MSG_RULES_CHANGED, uid, newUidRules).sendToTarget();
}
}
这边我们发现出现了一个变量 final boolean isForeground = isUidForegroundOnRestrictPowerLocked(uid);那这个变量什么时候为true呢,我们来分析一下这个函数:
private boolean isUidForegroundOnRestrictPowerLocked(int uid) {
final int procState = mUidState.get(uid, ActivityManager.PROCESS_STATE_CACHED_EMPTY);
return isProcStateAllowedWhileIdleOrPowerSaveMode(procState);
}
static boolean isProcStateAllowedWhileIdleOrPowerSaveMode(int procState) {
return procState <= ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE;
}
答案揭晓了,当前的进程优先级至少是前台service时就为true,好的,我们回到原来的代码,继续看下去我们又发现
if (isForeground) {
if (restrictMode) {
newRule = RULE_ALLOW_ALL;
}
}
RULE_ALLOW_ALL一看就是允许了该应用访问网络的权限,后面代码就是发送广播通知rule变化,这里就不多说了。
三、总结
经过上面的分析,我们大致明白了,如果把应用优先级提高到前台service及以上,那么idle模式就不会限制你访问网络等,这样就多了一种方法来绕过idle模式影响,其实想一下也应该知道,就像播放音乐时,即使你屏幕关闭,也不应该把你的音乐停掉,不然肯定被用户骂死,好了,分析就到这里了~~