DeviceIdleController的实现逻辑
1.服务启动 2
1.1SystemServer.java启动阶段 2
1.2准备阶段 5
2. 进入Idle模式 6
2.1进入idle模式的流程 6
3. 状态变化的驱动因素 10
4.进入Idle模式后的处理 10
4.1对于PowerManager的限制 11
4.2对网络的限制 13
4.3 Alarm限制 13
当系统灭屏并长时间处于静止状态时,系统会进入Doze状态,此时
(1)不在White list里的APP会被限制网络访问
(2)wake lock会被忽略
(3)通过alarm manger设置的alarm 被推迟,除非设置了允许在idle状态也能工作的flag
(5)wifi scan被取消
(6)jobschedule 和sync被推迟
的startOtherServices() 中有
mSystemServiceManager.startService(DeviceIdleController.class);这里启动DeviceIdleController的服务
首先调用到DeviceIdleController的构造函数:
public DeviceIdleController(Context context) {
super(context);
mConfigFile = new AtomicFile(new File(getSystemDir(), "deviceidle.xml"));
mHandler = new MyHandler(BackgroundThread.getHandler().getLooper());
}
构造函数仅仅做了两件事:1.创建一个保证原子操作的的mConfigFile的文件(系统/data/system/deviceidle.xml文件);2.创建一个handler线程执行idle的状态变化消息处理。
SystemServer的start的函数在执行了service的构造函数之后,会将该函数add到SystemService的list中,然后执行到Service的onStart函数中。
DeviceIdleController的onStart函数总共做了两件事:
mEnabled = getContext().getResources().getBoolean(
com.android.internal.R.bool.config_enableAutoPowerModes);
Idle模式中开关在frameworks/base/core/res/res/values/config.xml中配置。原生逻辑默认是关闭的,可以修改配置打开
SystemConfig sysConfig = SystemConfig.getInstance();
ArraySet
for (int i=0; i<allowPowerExceptIdle.size(); i++) {
String pkg = allowPowerExceptIdle.valueAt(i);
try {
ApplicationInfo ai = pm.getApplicationInfo(pkg, 0);
if ((ai.flags&ApplicationInfo.FLAG_SYSTEM) != 0) {
int appid = UserHandle.getAppId(ai.uid);
mPowerSaveWhitelistAppsExceptIdle.put(ai.packageName, appid);
mPowerSaveWhitelistSystemAppIdsExceptIdle.put(appid, true);
}
将配置文件中配置系统应用白名单应用添加到mPowerSaveWhitelistAppsExceptIdle列表中,省电模式下这些应用即使不在前台也能有联网权限(除了在Idle状态下)
ArraySet
for (int i=0; i<allowPower.size(); i++) {
String pkg = allowPower.valueAt(i);
try {
ApplicationInfo ai = pm.getApplicationInfo(pkg, 0);
if ((ai.flags&ApplicationInfo.FLAG_SYSTEM) != 0) {
int appid = UserHandle.getAppId(ai.uid);
// These apps are on both the whitelist-except-idle as well
// as the full whitelist, so they apply in all cases.
mPowerSaveWhitelistAppsExceptIdle.put(ai.packageName, appid);
mPowerSaveWhitelistSystemAppIdsExceptIdle.put(appid, true);
mPowerSaveWhitelistApps.put(ai.packageName, appid);
mPowerSaveWhitelistSystemAppIds.put(appid, true);
}
将系统完整省电模式的白名单应用添加到mPowerSaveWhitelistApps,该应用列表中的应用在省电模式下的所有状态都有联网权限。
mConstants = new Constants(mHandler, getContext().getContentResolver());
readConfigFileLocked();
updateWhitelistAppIdsLocked();
将系统中/data/system/deviceidle.xml配置的用户App读取到mPowerSaveWhitelistUserApps白名单列表中去,并更新所有的白名单应用列表。省电模式下出Idle模式不能工作的白名单应用列表:mPowerSaveWhitelistExceptIdleAppIdArray,省电模式下在Idle状态下也能工作的完全白名单列表:mPowerSaveWhitelistAllAppIdArray,将mPowerSaveWhitelistAllAppIdArray设置到PowerManagerService中的白名单中
mScreenOn = true;
mCharging = true;
mState = STATE_ACTIVE;
mInactiveTimeout = mConstants.INACTIVE_TIMEOUT;
}
publishBinderService(Context.DEVICE_IDLE_CONTROLLER, new BinderService());
publishLocalService(LocalService.class, new LocalService());
}
2.将deviceidle注册到BinderService()中。将DeviceIdleController的内部类LocalService注册到本地服务中。
onStart的详细
mPowerSaveWhitelistAppsExceptIdle里读取的类似allow-in-power-save-except-idle,和allow-in-power-save的应用包名例如:
<allow-in-power-save-except-idle package="com.android.providers.downloads" />
<allow-in-power-save package="com.google.android.gms" />
读取allow-in-power-save的应用,并将配置的应用添加到mPowerSaveWhitelistApps的List中。并且通过PackageManager通过包名获取到应用的appid。
并且将mPowerSaveWhitelistSystemAppIds.put(appid, true);
以上两种通过配置文件里面读取的系统应用,将其将入到mPowerSaveWhitelistSystemAppIdsExceptIdle, mPowerSaveWhitelistSystemAppIds两个列表中。在加入到列表时会判断该应用是否为系统应用,所以这两个列表里装的都是系统应用。
readConfigFileLocked()函数将读取系统中/data/system/deviceidle.xml文件,并将其解析出来应用的包名,将其解析出来的包名,通过pm获取应用信息,将其加入到mPowerSaveWhitelistUserApps列表中。
updateWhitelistAppIdsLocked()函数中建立两个列表:
mPowerSaveWhitelistExceptIdleAppIdArray:除了Idle模式下,省电模式的完整白名单
mPowerSaveWhitelistAllAppIdArray:列表里保存的完整的省电模式(包括Idle)白名单的应用ID。
调用PowerManagerService的setDeviceIdleWhitelist函数:
mLocalPowerManager.setDeviceIdleWhitelist(mPowerSaveWhitelistAllAppIdArray);
将完整的省电模式下应用白名单,设置到PowerManagerService的wakelock列表中。
在onStart中创建:
mConstants = new Constants(mHandler, getContext().getContentResolver());
注册ContentObserver的,监听Settings.Global.DEVICE_IDLE_CONSTANTS,在updateConstants中初始化各个阶段的时间
INACTIVE_TIMEOUT = 30min
SENSING_TIMEOUT = 4min
LOCATING_TIMEOUT = 30s
MOTION_INACTIVE_TIMEOUT = 10min
IDLE_AFTER_INACTIVE_TIMEOUT = 30min
IDLE_PENDING_TIMEOUT = 5min
MAX_IDLE_PENDING_TIMEOUT = 10min
IDLE_PENDING_FACTOR =2f
IDLE_TIMEOUT = 1h
MAX_IDLE_TIMEOUT = 6h
IDLE_FACTOR = 2f
MIN_TIME_TO_ALARM = 1h
这些参数在后面进入Idle模式的过程中都将用到
最后将设置设备状态: mState = STATE_ACTIVE;
大致流程图如下:
作为一个系统服务,启动过程都需要走到onBootPhase(int phase),当系统服务准备好了,就可以执行该服务的准备工作。
准备工作:1.获取AlarmManager服务
2.获取BatteryService服务
3.获取网络NetworkPolicyManager服务
4.获取Display服务
5.获取SensorManager服务(mSigMotionSensor)
6.获取LocationManager服务
7.创建运动检测对象
8.创建AlarmIntent(ACTION_STEP_IDLE_STATE),广播Intent。
9.创建mSensingAlarmIntent(ACTION_STEP_IDLE_STATE),在Sensing和Locating的时候广播Intent。
10.并注册广播接收器mReceiver,若接收到Intent(ACTION_STEP_IDLE_STATE)执行进入idle模式流程函数stepIdleStateLocked();
11.注册DisplayManager的监听器mDisplayListener,监听屏幕显示状态变化
1).INACTIVE
进入idle模式主要是在Step,在屏幕进入灭屏幕状态时,mDisplayListener会监听到屏幕显示状态的变化,继而会调用到updateDisplayLocked();更新屏幕状态,进入到灭屏时,会在becomeInactiveIfAppropriateLocked() 中将设备状态mState设置为 STATE_INACTIVE,然后发送延迟为INACTIVE_TIMEOUT的一个带有mAlarmIntent的alarm,由于mAlarmIntent中含有ACTION_STEP_IDLE_STATE的消息广播,所以在INACTIVE_TIMEOUT(30min)后,mReceiver会收到Intent为ACTION_STEP_IDLE_STATE的广播,进入到stepIdleStateLocked()中;
函数开始首先判断,下一次能从Idle状态唤醒的Alarm距离现在的时间,如果小于MIN_TIME_TO_ALARM,则直接唤醒设备,知道下一次唤醒Alarm发送成功:
final long now = SystemClock.elapsedRealtime();
if ((now+mConstants.MIN_TIME_TO_ALARM) > mAlarmManager.getNextWakeFromIdleTime()) {
if (mState != STATE_ACTIVE) {
becomeActiveLocked("alarm", Process.myUid());
}
return;
}
2).STATE_IDLE_PENDING;
如果不满足则开始一步一步进入Idle模式
我们可知当屏幕灭屏时,mState的状态是INACTIVE_TIMEOUT,30min后发送广播,则执行:
case STATE_INACTIVE:
// We have now been inactive long enough, it is time to start looking
// for significant motion and sleep some more while doing so.
startMonitoringSignificantMotion();
scheduleAlarmLocked(mConstants.IDLE_AFTER_INACTIVE_TIMEOUT, false);
// Reset the upcoming idle delays.
mNextIdlePendingDelay = mConstants.IDLE_PENDING_TIMEOUT;
mNextIdleDelay = mConstants.IDLE_TIMEOUT;
mState = STATE_IDLE_PENDING;
if (DEBUG) Slog.d(TAG, "Moved from STATE_INACTIVE to STATE_IDLE_PENDING.");
EventLogTags.writeDeviceIdle(mState, "step");
break;
此时mState已经是STATE_IDLE_PENDING的状态,开始检查SignificantMotion(显著运动)了;然后重新设置下一次Alarm发送的时间为IDLE_AFTER_INACTIVE_TIMEOUT(30min)。并且在此初始化mNextIdlePendingDelay(自动退出Idle的间隙时间5min),和 mNextIdleDelay(Idle持续时间1h).
3).SENSING:
case STATE_IDLE_PENDING:
mState = STATE_SENSING;
if (DEBUG) Slog.d(TAG, "Moved from STATE_IDLE_PENDING to STATE_SENSING.");
EventLogTags.writeDeviceIdle(mState, "step");
scheduleSensingAlarmLocked(mConstants.SENSING_TIMEOUT);
cancelLocatingLocked();
mAnyMotionDetector.checkForAnyMotion();
mNotMoving = false;
mLocated = false;
mLastGenericLocation = null;
mLastGpsLocation = null;
当在STATE_IDLE_PENDING时,30分钟内没有检测到SignificantMotion(显著运动),就进入到下一个状态——STATE_SENSING,在这个状态下会重新设置该状态下发送alarm的时间SENSING_TIMEOUT(4min);并且不检测位置移动(因为位置移动下一个状态会去检查判断);检查设备是否处于一个静止状态;
并初始化几个参数。
4)LOCATIN
case STATE_SENSING:
mState = STATE_LOCATING;
if (DEBUG) Slog.d(TAG, "Moved from STATE_SENSING to STATE_LOCATING.");
EventLogTags.writeDeviceIdle(mState, "step");
scheduleSensingAlarmLocked(mConstants.LOCATING_TIMEOUT);
mLocating = true;
mLocationManager.requestLocationUpdates(mLocationRequest, mGenericLocationListener, mHandler.getLooper());
if (mLocationManager.getProvider(LocationManager.GPS_PROVIDER) != null) {
mHaveGps = true; mLocationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 1000, 5,
mGpsLocationListener, mHandler.getLooper());
} else {
mHaveGps = false;
}
break;
当mAnyMotionDetector检测为静止时,进入到STATE_LOCATING状态,并设置发送Alarm的超时时间为LOCATING_TIMEOUT(30s),并且检测位置移动。
5)IDLE
case STATE_IDLE_MAINTENANCE:
scheduleAlarmLocked(mNextIdleDelay, true);
if (DEBUG) Slog.d(TAG, "Moved to STATE_IDLE. Next alarm in " + mNextIdleDelay + " ms.");
mNextIdleDelay = (long)(mNextIdleDelay * mConstants.IDLE_FACTOR);
if (DEBUG) Slog.d(TAG, "Setting mNextIdleDelay = " + mNextIdleDelay);
mNextIdleDelay = Math.min(mNextIdleDelay, mConstants.MAX_IDLE_TIMEOUT);
mState = STATE_IDLE;
EventLogTags.writeDeviceIdle(mState, "step");
mHandler.sendEmptyMessage(MSG_REPORT_IDLE_ON);
break;
当位置定位满足精确标准时,停掉Alarm检测,位置检测,设备移动检测等,直接进入Idle模式,此时重置Idle的发送超时时间为mNextIdleDelay(1h),并且通过Handler发送消息MSG_REPORT_IDLE_ON,在handleMessage中去做idle模式下的各种限制。
case STATE_IDLE:
// We have been idling long enough, now it is time to do some work.
scheduleAlarmLocked(mNextIdlePendingDelay, false);
if (DEBUG) Slog.d(TAG, "Moved from STATE_IDLE to STATE_IDLE_MAINTENANCE. " + "Next alarm in " + mNextIdlePendingDelay + " ms.");
mNextIdlePendingDelay = Math.min(mConstants.MAX_IDLE_PENDING_TIMEOUT,
(long)(mNextIdlePendingDelay * mConstants.IDLE_PENDING_FACTOR));
mState = STATE_IDLE_MAINTENANCE;
EventLogTags.writeDeviceIdle(mState, "step");
mHandler.sendEmptyMessage(MSG_REPORT_IDLE_OFF);
break;
当进入Idle模式后,每隔一段时间都会进入到STATE_IDLE_MAINTENANCE;的间隙状态去处理相应的一些网络同步,Alarm发送的功能,具体操作在handleMessage中去做。
其整个流程大致如下:
系统启动时设置初始状态为STATE_ACTIVE。之后根据屏幕状态/充电状态/传感器状态来进行状态转移。
(1)当screenoff 且non-charging时,进入STATE_INACTIVE。此时会设置INACTIVE_TIMEOUT,默认为30min
(2)当INACTIVE_TIMEOUT时,进入STATE_IDLE_PENDING,此时会开启SignificantMotion,并设置IDLE_AFTER_INACTIVE_TIMEOUT,默认为30min.
(3)当IDLE_AFTER_INACTIVE_TIMEOUT时,进入STATE_SENSING,此时启动AnyMotionDetetor
(4)当AnyMoTionDetetor检测为静止时,进入STATE_LOCATING,并设置LOCATING_TIMEOUT,默认为30s,并请求当前的Location状态。
(5)当Location状态显示当前位置达到设定的精确标准时,直接进入STATE_IDLE,否则等到LOCATING_TIMEOUT,进入STATE_IDLE.此时停止AnyMotionDetetor,通过AlarmManager.setIdleUntil 通只AlarmManager进入IDLE,直到指定的alarm到来(这里设置的alarm默认为60min);通知PowerManager/NetworkPolicy/BatteryStas 进入IDLE状态。
并发送broadcast: ACTION_DEVICE_IDLE_MODE_CHANGED.
(6)当(5)中设置的alarm唤醒时,进入STATE_IDLE_MAINTAINTENCE,此时设置一个默认为5min的timeout,同时通知PowerManager/NetworkPolicy/BatteryStas退出IDLE状态
(7)当(6)设置的timeout 到时,再次进入STATE_IDLE
(8)在状态STATE_SENSING和STATE_LOCATING,可能会因为AnyMoTionDetetor监测到移动或者SignificantMotion检测到有动作而重新进入STATE_INACTIVE
(9)在状态STATE_IDLE_PENDING,可能会因为SignificantMotion检测到有动作而重新进入STATE_INACTIVE
(10)当screenon 或者 charging 都会重新进入STATE_ACTIVE
(1)screen 状态
(2)充电状态(charging)
(3)AnyMonitionDetetor 监测到的状态。其作用区间为进入STATE_SENSING开始到离开STATE_LOCATING。
(4)SignificantMotionSensor监测到的状态,其作用区间为进入STATE_IDLE_PENDING开始,到重新进入ACTIVE或者INACTIVE状态
(5)外部模块直接调用exitIdle
(6)使用dumpsys 的force-idle
(7)控制IDLE功能的总开关:
config.xml:config_enableAutoPowerModes,默认为false
(8)当检测到线控耳机按钮事件(voice-search)时,会退出idle模式。
case MSG_REPORT_IDLE_ON: {
EventLogTags.writeDeviceIdleOnStart();
mLocalPowerManager.setDeviceIdleMode(true);
try {
mNetworkPolicyManager.setDeviceIdleMode(true);
mBatteryStats.noteDeviceIdleMode(true, null, Process.myUid());
} catch (RemoteException e) {
}
getContext().sendBroadcastAsUser(mIdleIntent, UserHandle.ALL);
EventLogTags.writeDeviceIdleOnComplete();
} break;
可以直观的看到进入Idle模式后,对PowerManager,NetworkPolicy,有直接限制,而进入到Idle模式后BatteryStats统计电池电量使用信息,将其当成与Alarm,Audio等模块一样去计算。
在服务启动阶段,onBootPhase()函数中,有:
mLocalPowerManager.setDeviceIdleWhitelist(mPowerSaveWhitelistAllAppIdArray);
将Idle模式白名单应用设置到PowerManagerService中,所以调用到PowerManagerService中时,去更新wakelock函数updateWakeLockDisabledStatesLocked():
private void updateWakeLockDisabledStatesLocked() {
boolean changed = false;
final int numWakeLocks = mWakeLocks.size();
for (int i = 0; i < numWakeLocks; i++) {
final WakeLock wakeLock = mWakeLocks.get(i);
if ((wakeLock.mFlags & PowerManager.WAKE_LOCK_LEVEL_MASK)
== PowerManager.PARTIAL_WAKE_LOCK) {
if (setWakeLockDisabledStateLocked(wakeLock)) {
changed = true;
if (wakeLock.mDisabled) {
// This wake lock is no longer being respected.
notifyWakeLockReleasedLocked(wakeLock);
} else {
notifyWakeLockAcquiredLocked(wakeLock);
}
}
}
}
if (changed) {
mDirty |= DIRTY_WAKE_LOCKS;
updatePowerStateLocked();
}
}
进入到Idle模式后,会遍历mWakeLocks列表,将该列表将不在白名单应用的PARTIAL_WAKE_LOCK类型的wakelock设置mDisabled参数。
函数setWakeLockDisabledStateLocked:
if (mDeviceIdleMode) {
final int appid = UserHandle.getAppId(wakeLock.mOwnerUid);
// If we are in idle mode, we will ignore all partial wake locks that are
// for application uids that are not whitelisted.
if (appid >= Process.FIRST_APPLICATION_UID &&
Arrays.binarySearch(mDeviceIdleWhitelist, appid) < 0 &&
Arrays.binarySearch(mDeviceIdleTempWhitelist, appid) < 0 &&
mUidState.get(wakeLock.mOwnerUid,
ActivityManager.PROCESS_STATE_CACHED_EMPTY)
> ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) {
disabled = true;
}
}
if (wakeLock.mDisabled != disabled) {
wakeLock.mDisabled = disabled;
return true;
}
判断首先wakelock的appid需要大于Process.FIRST_APPLICATION_UID(10000:非系统应用Uid一般都是大于10000),appid不能在省电模式白名单以及零时白名单中,并且该uid的Service不能为一个前台服务。当满足这几个条件,该appid申请的wakelock会被disable掉,相当于release了,但是还在mWakelocks列表中,退出Idle状态时会重新生效。退出Idle时会重新走一遍setWakeLockDisabledStateLocked,将mWakeLocks列表中的所有wakelock的mDisbale参数置为false,所以退出Idle时,mWakeLocks列表中的所有wakelock会重新生效。
在NetworkPolicyManagerService中来控制idle状态下对网络访问的控制:
(1)监听ACTION_POWER_SAVE_WHITELIST_CHANGED来维护mPowerSaveWhitelistAppIds
(2)向DeviceIdleController注册mTempPowerSaveChangedCallback来维护 mPowerSaveTempWhitelistAppIds
(3)提供setDeviceIdleMode供DeviceIdleController 通知进入idle
当进入Idle模式后,便会通过NetworkPolicyManagerService通过updateRulesForGlobalChangeLocked全局去更新网络访问控制策略规则:
void updateRulesForGlobalChangeLocked(boolean restrictedNetworksChanged) {
final PackageManager pm = mContext.getPackageManager();
updateRulesForDeviceIdleLocked();
updateRulesForAppIdleLocked();
// update rules for all installed applications
final List
final List
PackageManager.GET_UNINSTALLED_PACKAGES | PackageManager.GET_DISABLED_COMPONENTS);
for (UserInfo user : users) {
for (ApplicationInfo app : apps) {
final int uid = UserHandle.getUid(user.id, app.uid);
updateRulesForUidLocked(uid);
}
}
// limit data usage for some internal system services
updateRulesForUidLocked(android.os.Process.MEDIA_UID);
updateRulesForUidLocked(android.os.Process.DRM_UID);
}
(1)将mPowerSaveWhitelistAppIds,和mPowerSaveTempWhitelistAppIds中的app设置rule为FIREWALL_RULE_ALLOW(允许网络访问),加入到FIREWALL_CHAIN_DOZABLE这个chain中。
(2)向UsageStatesService注册AppIdleStateChangeListener,当UsageStatesService检测到有app standby时候,此时NetworkPolicyManagerService将会限制该app访问网络。
(6)当不在White list的APP 在进入IDLE状态时,将无法再使用网络。(通过添加使能firewall的相关chain来实现)
AlarmManagerService增加了一套新的标志,用来区分不同alarm在idle模式下的工作状态。同时新增了3个api接口,用来设置可在idle模式下执行的alarm。具体情况如下:
public static final int FLAG_STANDALONE = 1<<0;
这种Alarm是独立的Alarm,不会和其他Alarm合并到一个batch中。
public static final int FLAG_WAKE_FROM_IDLE = 1<<1;
这种Alarm可以从idle模式下将设备唤醒,(用于闹钟,日程)
public static final int FLAG_ALLOW_WHILE_IDLE = 1<<2;
这种Alarm在Idle状态下依旧可以被发送执行,它不会使设备推出Idle,仅仅让Alarm运行,
public static final int FLAG_IDLE_UNTIL = 1<<4;
这种Alarm是标记一个时间点来退出idle状态。
AlarmManager中新增了三个方法:
setAndAllowWhileIdle:设置可在idle模式下执行的alarm(flag: FLAG_ALLOW_WHILE_IDLE)
setExactAndAllowWhileIdl:同setExact(),设置可以在idle模式下执行的精确Alarm
setIdleUntil:设置idle-until alarm,这种Alarm(flag:FLAG_IDLE_UNTIL)将会使设备退出idle,进入idle-mataintence状态,只能由系统服务调用,在6.0系统中暂时只有DeviceIdleController在Idle状态和Idle-Matainence状态切换时候调用,
在Alarm中alarm manager进入idle模式,会将先前设置的普通alarm(不带FLAG_ALLOW_WHILE_IDLE/ FLAG_WAKE_FROM_IDLE标志)放到PendingList,同时新来的普通alarm也会被存到PendingList。PendingList中的alarm不会被设置到底层alarm设备,因此也就不会有唤醒动作。当idle-until alarm的触发时间到来,PendingList才会重新设置。idle-until alarm的触发时间是一个可设置的参数,默认值是15分钟,也就是说如果设备在idle模式下,每15分钟唤醒一次。类似于15分钟的心跳同步。
以上是google原生代码上对于idle状态的控制逻辑,由于进入Idle状态后,会对系统的wakelock,network,Alarm作限制,以保证最大的省电效率。可以看出原生逻辑对于进入DeviceIdle状态是有着非常严格苛刻的条件的,从进入开始进入Idle到完全进入Idle状态中间需要走五个状态流程,且需要判断位置,移动以及明显动作,这些外界因素一个不满足都会使设备进入Idle状态失败。所以Idle状态基本只能在晚上睡眠时间才能完全进入。