Android7.0 Doze模式

        在Android M中,Google就引入了Doze模式。它定义了一种全新的、低能耗的状态。在该状态,后台只有部分任务被允许运行,其它任务都被强制停止。这里我们就来分析一下Android 7.0中Doze模式相关的流程。

基本原理

基本原理

        Doze模式可以简单概括为:

        若判断用户在连续的一段时间内没有使用手机,就延缓终端中APP后台的CPU和网络活动,以达到减少电量消耗的目的。

        上面这张图比较经典,基本上说明了Doze模式的含义。

        图中的横轴表示时间,红色部分表示终端处于唤醒的运行状态,绿色部分就是Doze模式定义的休眠状态。

        从图中的描述,我们可以看到:如果一个用户停止充电(on battery: 利用电池供电),关闭屏幕(screen off),手机处于静止状态(stationary: 位置没有发生相对移动),保持以上条件一段时间之后,终端就会进入Doze模式。一旦进入Doze模式,系统就减少(延缓)应用对网络的访问、以及对CPU的占用,来节省电池电量。

        如图所示,Doze模式还定义了maintenance window。

        在maintenance window中,系统允许应用完成它们被延缓的动作,即可以使用CPU资源及访问网络。

        从图中我们可以看出,当进入Doze模式的条件一直满足时,Doze模式会定期的进入到maintenance window,但进入的间隔越来越长。 通过这种方式,Doze模式可以使终端处于较长时间的休眠状态。

        需要注意的是:一旦Doze模式的条件不再满足,即用户充电、或打开屏幕、或终端的位置发生了移动,终端就恢复到正常模式。 因此,当用户频繁使用手机时,Doze模式几乎是没有什么实际用处的。

        具体来讲,当终端处于Doze模式时,进行了以下操作:

        1、暂停网络访问。

        2、系统忽略所有的WakeLock。

        3、标准的AlarmManager alarms被延缓到下一个maintenance window。 但使用AlarmManager的 setAndAllowWhileIdle、setExactAndAllowWhileIdle和setAlarmClock时,alarms定义事件仍会启动。 在这些alarms启动前,系统会短暂地退出Doze模式。

        4、系统不再进行WiFi扫描。

        5、系统不允许sync adapters运行。

        6、系统不允许JobScheduler运行。

测试Doze和App Standby模式的方法(Adb命令)
测试Doze模式

    1. 首先确保你的硬件或虚拟设备是Android6.0或更高版本系统;

    2. 连接设备到开发机上并安装你的app;

    3. 运行app并让其运行活动;

    4. 关闭设备的屏幕;

    5. 运行以下adb命令使系统进入Doze模式:

$ adb shell dumpsys battery unplug

$ adb shell dumpsys deviceidle step

    6. 观察你的app表现行为是否有需优化改进的地方。

测试App Standby模式

    步骤1-3同测试Doze模式

    4. 运行以下adb命令迫使系统进入App Standby模式:

$ adb shell dumpsys battery unplug

$ adb shell am set-inactive <packageName> true

    5. 模拟唤醒你的应用程序使用以下命令:

$ adb shell am set-inactive <packageName> false

$ adb shell am get-inactive <packageName>

    6. 观察你的App,确保应用程序恢复正常从待机模式过程中,App的通知及其背部活动能达到预期结果。

Doze模式状态转变

DeviceIdleController维持着设备包含的五种状态:

    1. ACTIVE:设备在使用中,或者连接着电源,手机设备处于激活活动状态;

    2. INACTIVE:屏幕关闭或者未充电,进入非活动状态;

    3. IDLE_PENDING:每隔30分钟让APP进入等待空闲预备状态;

    4. IDLE:进入空闲状态;

    5. IDLE_MAINTENANCE:处理挂起任务。

当设备被唤醒和正在使用中,控制器就处于ACTIVE状态,外部事件(不活跃时间超时,用户关闭屏幕等)将会使设备状态进入INACTIVE。这时,DeviceIdleController将会通过AlarmManager来设置alarm来驱动进程:

    1. 一个alarm会被设置在一个预设的时刻(30分钟)。

    2. 当这个alarm生效后,DeviceIdleController会进入到IDLE_PENDING状态,然后再次设置同样的alarm。

    3. 当触发下一个alarm后,控制器会进入IDLE状态,进入到这个状态后,应用特性会被完全限制。

    4. 再向前推进这个服务会在IDLE和IDLE_MAINTENANCE两个状态之间周期性的跳转,后者在服务完全被禁前,等待的应用事件被触发。

开发者可以使用下面的命令,手动改变设备的这些状态:adb shell dumpsys deviceidle step

更多基本信息的描述可以参考: What is Doze mode in android 6.0 Marshmallow?

DeviceIdleController的初始化

        Android中的Doze模式主要由DeviceIdleController来控制。

/**
 * Keeps track of device idleness and drives low power mode based on that.
 */
public class DeviceIdleController extends SystemService
        implements AnyMotionDetector.DeviceIdleCallback {
    可以看出DeviceIdleController继承自SystemService,是一个系统级的服务。同时,实现了AnyMotionDetector定义的接口,便于检测到终端位置变化后进行回调。

    接下来看下它的初始化过程。

private void startOtherServices() {
    .........
    mSystemServiceManager.startService(DeviceIdleController.class);
    .........
}
    上面代码是在SystemServier的startOtherServices中启动了DeviceIdleController,将先后调用DeviceIdleController的构造方法和onStart()方法。

构造方法

    public DeviceIdleController(Context context) {
        super(context);
        mConfigFile = new AtomicFile(new File(getSystemDir(), "deviceidle.xml"));
        mHandler = new MyHandler(BackgroundThread.getHandler().getLooper());
    }
    deviceidle.xml用于定义idle模式也能正常工作的非系统应用,也就是说可以将一些应用放到白名单中,调用DeviceIdleController的appPowerSaveWhitelistApp()方法,最后会写入deviceidle.xml文件中,下次开机的时候DeviceIdleController会重新读取deviceidle.xml文件,加入到白名单mPowerSaveWhitelistUserApps中。一般终端似乎并没有定义deviceidle.xml。

    DeviceIdleController的构造函数比较简单,就是在创建data/system/deviceidle.xml对应的file文件,同时创建一个对应于后台线程的Handler。

onStart()

@Override
public void onStart() {
    final PackageManager pm = getContext().getPackageManager();//创建PackageManager对象

    synchronized (this) {
        //读取配置文件,判断Doze模式是否允许被开启
        mLightEnabled = mDeepEnabled = getContext().getResources().getBoolean(
                com.android.internal.R.bool.config_enableAutoPowerModes);
        SystemConfig sysConfig = SystemConfig.getInstance();//获取一个SystemConfig对象
        //获取除了deviceidle模式外,都可以运行的系统应用白名单
        ArraySet<String> allowPowerExceptIdle = sysConfig.getAllowInPowerSaveExceptIdle();
        for (int i=0; i<allowPowerExceptIdle.size(); i++) {
            String pkg = allowPowerExceptIdle.valueAt(i);//获取对应的包名
            try {
                ApplicationInfo ai = pm.getApplicationInfo(pkg,
                        PackageManager.MATCH_SYSTEM_ONLY);
                int appid = UserHandle.getAppId(ai.uid);
                mPowerSaveWhitelistAppsExceptIdle.put(ai.packageName, appid);
                mPowerSaveWhitelistSystemAppIdsExceptIdle.put(appid, true);
            } catch (PackageManager.NameNotFoundException e) {
            }
        }
        //获取deviceidle模式下,也可以运行的系统应用白名单
        ArraySet<String> allowPower = sysConfig.getAllowInPowerSave();
        for (int i=0; i<allowPower.size(); i++) {
            String pkg = allowPower.valueAt(i);
            try {
                ApplicationInfo ai = pm.getApplicationInfo(pkg,
                        PackageManager.MATCH_SYSTEM_ONLY);
                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);
            } catch (PackageManager.NameNotFoundException e) {
            }
        }

        //Constants为deviceIdleController中的内部类,继承ContentObserver
        //监控数据库变化,同时得到Doze模式定义的一些时间间隔
        mConstants = new Constants(mHandler, getContext().getContentResolver());

        //解析deviceidle.xml,并将其中定义的package对应的app,加入到mPowerSaveWhitelistUserApps中
        readConfigFileLocked();
        //将白名单的内容给AlarmManagerService和PowerMangerService
        //例如:DeviceIdleController判断开启Doze模式时,会通知PMS
        //此时除去白名单对应的应用外,PMS会将其它所有的WakeLock设置为Disable状态
        updateWhitelistAppIdsLocked();

        //以下的初始化,都是假设目前处在进入Doze模式相反的条件上
        mNetworkConnected = true;
        mScreenOn = true;
        // Start out assuming we are charging.  If we aren't, we will at least get
        // a battery update the next time the level drops.
        mCharging = true;
        mState = STATE_ACTIVE;//Doze模式定义终端初始时为ACTIVE状态
        mLightState = LIGHT_STATE_ACTIVE;//屏幕状态初始时为ACTIVE状态
        mInactiveTimeout = mConstants.INACTIVE_TIMEOUT;
    }

    //发布服务
    //BinderService和LocalService均为DeviceIdleController的内部类
    mBinderService = new BinderService();
    publishBinderService(Context.DEVICE_IDLE_CONTROLLER, mBinderService);
    publishLocalService(LocalService.class, new LocalService());
}
    除去发布服务外,DeviceIdleController在onStart方法中,主要时读取配置文件更新自己的变量,思路比较清晰。

    这里我们看下updateWhitelistAppIdsLocked()方法,如下:

private void updateWhitelistAppIdsLocked() {
    //构造出除去idle模式外,可运行的app id数组 (可认为是系统和普通应用的集合)
    //mPowerSaveWhitelistAppsExceptIdle从系统目录下的xml得到
    //mPowerSaveWhitelistUserApps从deviceidle.xml得到,或调用接口加入;
    //mPowerSaveWhitelistExceptIdleAppIds并未使用
    mPowerSaveWhitelistExceptIdleAppIdArray = buildAppIdArray(mPowerSaveWhitelistAppsExceptIdle,
            mPowerSaveWhitelistUserApps, mPowerSaveWhitelistExceptIdleAppIds);
    //构造不受Doze限制的app id数组 (可认为是系统和普通应用的集合)
    //mPowerSaveWhitelistApps从系统目录下的xml得到
    //mPowerSaveWhitelistAllAppIds并未使用
    mPowerSaveWhitelistAllAppIdArray = buildAppIdArray(mPowerSaveWhitelistApps,
            mPowerSaveWhitelistUserApps, mPowerSaveWhitelistAllAppIds);
    //构造不受Doze限制的app id数组(仅普通应用的集合)、
    //mPowerSaveWhitelistUserAppIds并未使用
    mPowerSaveWhitelistUserAppIdArray = buildAppIdArray(null,
            mPowerSaveWhitelistUserApps, mPowerSaveWhitelistUserAppIds);
    if (mLocalPowerManager != null) {
        if (DEBUG) {
            Slog.d(TAG, "Setting wakelock whitelist to "
                    + Arrays.toString(mPowerSaveWhitelistAllAppIdArray));
        }
        //PMS拿到的是:系统和普通应用组成的不受Doze限制的app id数组
        mLocalPowerManager.setDeviceIdleWhitelist(mPowerSaveWhitelistAllAppIdArray);
    }
    if (mLocalAlarmManager != null) {
        if (DEBUG) {
            Slog.d(TAG, "Setting alarm whitelist to "
                    + Arrays.toString(mPowerSaveWhitelistUserAppIdArray));
        }
        //AlarmManagerService拿到的是:普通应用组成的不受Doze限制的app id数组
        mLocalAlarmManager.setDeviceIdleUserWhitelist(mPowerSaveWhitelistUserAppIdArray);
    }
}
    updateWhitelistAppIdsLocked()主要是将白名单交给PMS和AlarmMS。主要Android区分了系统应用白名单、普通应用白名单等,因此上面进行了一些合并操作。

onBootPhase

    与PowerManagerService一样,DeviceIdleController在初始化的最后一个阶段需要调用onBootPhase函数:

@Override
public void onBootPhase(int phase) {
    //在系统PHASE_SYSTEM_SERVICES_READY阶段,进一步完成一些初始化
    if (phase == PHASE_SYSTEM_SERVICES_READY) {
        synchronized (this) {
          //初始化一些变量
            mAlarmManager = (AlarmManager) getContext().getSystemService(Context.ALARM_SERVICE);
            mBatteryStats = BatteryStatsService.getService();
            mLocalPowerManager = getLocalService(PowerManagerInternal.class);
            mPowerManager = getContext().getSystemService(PowerManager.class);
            mActiveIdleWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
                    "deviceidle_maint");
            mActiveIdleWakeLock.setReferenceCounted(false);
            mConnectivityService = (ConnectivityService)ServiceManager.getService(
                    Context.CONNECTIVITY_SERVICE);
            mLocalAlarmManager = getLocalService(AlarmManagerService.LocalService.class);
            mNetworkPolicyManager = INetworkPolicyManager.Stub.asInterface(
                    ServiceManager.getService(Context.NETWORK_POLICY_SERVICE));
            mDisplayManager = (DisplayManager) getContext().getSystemService(
                    Context.DISPLAY_SERVICE);
            mSensorManager = (SensorManager) getContext().getSystemService(Context.SENSOR_SERVICE);
          //根据配置文件,利用SensorManager获取对应的传感器,保存到mMotionSensor中
            int sigMotionSensorId = getContext().getResources().getInteger(
                    com.android.internal.R.integer.config_autoPowerModeAnyMotionSensor);
            if (sigMotionSensorId > 0) {
                mMotionSensor = mSensorManager.getDefaultSensor(sigMotionSensorId, true);
            }
            if (mMotionSensor == null && getContext().getResources().getBoolean(
                    com.android.internal.R.bool.config_autoPowerModePreferWristTilt)) {
                mMotionSensor = mSensorManager.getDefaultSensor(
                        Sensor.TYPE_WRIST_TILT_GESTURE, true);
            }
            if (mMotionSensor == null) {
                // As a last ditch, fall back to SMD.
                mMotionSensor = mSensorManager.getDefaultSensor(
                        Sensor.TYPE_SIGNIFICANT_MOTION, true);
            }

          //如果配置文件表明:终端需要预获取位置信息,则构造LocationRequest
            if (getContext().getResources().getBoolean(
                    com.android.internal.R.bool.config_autoPowerModePrefetchLocation)) {
                mLocationManager = (LocationManager) getContext().getSystemService(
                        Context.LOCATION_SERVICE);
                mLocationRequest = new LocationRequest()
                    .setQuality(LocationRequest.ACCURACY_FINE)
                    .setInterval(0)
                    .setFastestInterval(0)
                    .setNumUpdates(1);
            }

          //根据配置文件,得到角度变化的门限
            float angleThreshold = getContext().getResources().getInteger(
                    com.android.internal.R.integer.config_autoPowerModeThresholdAngle) / 100f;
            //创建一个AnyMotionDetector,同时将DeviceIdleController注册到其中
            //当AnyMotionDetector检测到手机变化角度超过门限时,就会回调DeviceIdleController的接口
            mAnyMotionDetector = new AnyMotionDetector(
                    (PowerManager) getContext().getSystemService(Context.POWER_SERVICE),
                    mHandler, mSensorManager, this, angleThreshold);

          //创建两个常用的Intent,用于通知Doze模式的变化
            mIdleIntent = new Intent(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED);
            mIdleIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
                    | Intent.FLAG_RECEIVER_FOREGROUND);
            mLightIdleIntent = new Intent(PowerManager.ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED);
            mLightIdleIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
                    | Intent.FLAG_RECEIVER_FOREGROUND);

          //监听ACTION_BATTERY_CHANGED广播(电池信息发生改变)
            IntentFilter filter = new IntentFilter();
            filter.addAction(Intent.ACTION_BATTERY_CHANGED);
            getContext().registerReceiver(mReceiver, filter);

          //监听ACTION_PACKAGE_REMOVED广播(包被移除)
            filter = new IntentFilter();
            filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
            filter.addDataScheme("package");
            getContext().registerReceiver(mReceiver, filter);

          //监听CONNECTIVITY_ACTION广播(连接状态发生改变)
            filter = new IntentFilter();
            filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
            getContext().registerReceiver(mReceiver, filter);

            //重新将白名单信息交给PowerManagerService和AlarmManagerService
            //这个工作在onStart函数中,已经调用updateWhitelistAppIdsLocked进行过了
            //到onBootPhase时,重新进行一次,可能:一是为了保险;二是,其它进程可能调用接口,更改了对应数据,于是进行更新
            mLocalPowerManager.setDeviceIdleWhitelist(mPowerSaveWhitelistAllAppIdArray);
            mLocalAlarmManager.setDeviceIdleUserWhitelist(mPowerSaveWhitelistUserAppIdArray);
          //监听屏幕显示相关的变化
            mDisplayManager.registerDisplayListener(mDisplayListener, null);
          //更新屏幕显示相关的信息
            updateDisplayLocked();
        }
      //更新连接状态相关的信息
        updateConnectivityState(null);
    }
}
    从代码可以看出,onBootPhase()方法主要创建一些本地变量,然后根据配置文件初始化一些传感器,同时注册了一些广播接收器和回调接口,最后更新屏幕显示和连接状态相关的信息。

DeviceIdleController定义的状态变化

    前面我们提到了Doze模式的原理,终端进入Doze模式的条件是:未充电、手机位置不发生变化、屏幕熄灭。因此,在DeviceIdleController中监听了这三个条件对应的状态,以决定终端是否真正进入到Doze模式。

    对这三个条件的分析,最终都会进入到DeviceIdleController定义的状态变化流程。这里我们先以充电状态的变化为例,看看DeviceIdleController进行了哪些处理。

充电状态的变化

    对于充电状态,在onBootPhase函数中已经提到,DeviceIdleController监听了ACTION_BATTERY_CHANGED广播:

          //监听ACTION_BATTERY_CHANGED广播(电池信息发生改变)
            IntentFilter filter = new IntentFilter();
            filter.addAction(Intent.ACTION_BATTERY_CHANGED);
            getContext().registerReceiver(mReceiver, filter);
    mReceiver的定义如下:

private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
    @Override public void onReceive(Context context, Intent intent) {
        switch (intent.getAction()) {
            case ConnectivityManager.CONNECTIVITY_ACTION: {
                updateConnectivityState(intent);//更新连接状态
            } break;
            case Intent.ACTION_BATTERY_CHANGED: {
                synchronized (DeviceIdleController.this) {
                    //从广播中的都是否充电的消息
                    int plugged = intent.getIntExtra("plugged", 0);
                    updateChargingLocked(plugged != 0);
                }
            } break;
            case Intent.ACTION_PACKAGE_REMOVED: {
                if (!intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
                    Uri data = intent.getData();
                    String ssp;
                    if (data != null && (ssp = data.getSchemeSpecificPart()) != null) {
                        removePowerSaveWhitelistAppInternal(ssp);//从白名单中移除该应用
                    }
                }
            } break;
        }
    }
};
    根据上面的代码,可以看出当收到电池信息改变的广播后,DeviceIdleController将得到电源是否在充电的消息,然后调用updateChargingLocked函数进行处理。

void updateChargingLocked(boolean charging) {
    if (DEBUG) Slog.i(TAG, "updateChargingLocked: charging=" + charging);
    if (!charging && mCharging) {
        ///从充电状态变为未充电状态
        mCharging = false;
        //mForceIdle值一般为false,是通过dumpsys命令将mForceIdle改成true的
        if (!mForceIdle) {
            becomeInactiveIfAppropriateLocked();//判断是否进入Doze模式
        }
    } else if (charging) {
        mCharging = charging;//进入充电状态
        if (!mForceIdle) {
            becomeActiveLocked("charging", Process.myUid());//手机退出Doze模式
        }
    }
}

becomeActiveLocked()

    我们先看看becomeActiveLocked函数:

//activeReason记录的终端变为active的原因
void becomeActiveLocked(String activeReason, int activeUid) {
    if (DEBUG) Slog.i(TAG, "becomeActiveLocked, reason = " + activeReason);
    if (mState != STATE_ACTIVE || mLightState != STATE_ACTIVE) {
        ........
        //1、通知PMS等Doze模式结束
        scheduleReportActiveLocked(activeReason, activeUid);
        //更新DeviceIdleController本地维护的状态
        //初始时,mState和mLightState均为Active状态
        mState = STATE_ACTIVE;
        mLightState = LIGHT_STATE_ACTIVE;
        mInactiveTimeout = mConstants.INACTIVE_TIMEOUT;
        mCurIdleBudget = 0;
        mMaintenanceStartTime = 0;
        //2、重置一些事件
        resetIdleManagementLocked();
        resetLightIdleManagementLocked();
        addEvent(EVENT_NORMAL);
    }
}

scheduleReportActiveLocked()

void scheduleReportActiveLocked(String activeReason, int activeUid) {
    //发送MSG_REPORT_ACTIVE消息
    Message msg = mHandler.obtainMessage(MSG_REPORT_ACTIVE, activeUid, 0, activeReason);
    mHandler.sendMessage(msg);
}
    对应的处理流程:

case MSG_REPORT_ACTIVE: {
    ........
    //通知PMS Doze模式结束,于是PMS将一些Doze模式下,disable的WakeLock重新enable
    //然后调用updatePowerStateLocked函数更新终端的状态
    final boolean deepChanged = mLocalPowerManager.setDeviceIdleMode(false);
    final boolean lightChanged = mLocalPowerManager.setLightDeviceIdleMode(false);
    try {
        //通过NetworkPolicyManagerService更改Ip-Rule,不再限制终端应用上网
        mNetworkPolicyManager.setDeviceIdleMode(false);
        mBatteryStats.noteDeviceIdleMode(BatteryStats.DEVICE_IDLE_MODE_OFF,
                activeReason, activeUid);
    } catch (RemoteException e) {
    }
    发送广播
    if (deepChanged) {
        getContext().sendBroadcastAsUser(mIdleIntent, UserHandle.ALL);
    }
    if (lightChanged) {
        getContext().sendBroadcastAsUser(mLightIdleIntent, UserHandle.ALL);
    }
    .........
} break;
    从上面的代码可以看出,scheduleReportActiveLocked()方法最主要的工作是:

    通知PMS等重新更新终端的状态;

    通知NetworkPolicyManager不再限制应用上网;

    发送Doze模式改变的广播。

resetIdleManagementLocked()

void resetIdleManagementLocked() {
    复位一些状态变量
    mNextIdlePendingDelay = 0;
    mNextIdleDelay = 0;
    mNextLightIdleDelay = 0;
    停止一些工作,主要是位置检测相关的
    cancelAlarmLocked();
    cancelSensingTimeoutAlarmLocked();
    cancelLocatingLocked();
    stopMonitoringMotionLocked();
    mAnyMotionDetector.stop();
}
    从上面的代码可以看出,resetIdleManagementLocked的工作相对简单,就是停止进入Doze模式时启动的一些任务。

becomeInActiceIfAppropriateLocked

    与becomeActiveLocked方法相比,becomeInActiveIfAppropriateLocked方法较为复杂。因为调用becomeInactiveIfAppropriateLocked()方法时,终端可能只是满足进入Doze模式的条件,离进入真正的Doze模式还有很长的“一段路”需要走。

    我们看看becomeInactiveIfAppropriateLocked的代码:

void becomeInactiveIfAppropriateLocked() {
    .......
    //屏幕熄灭,未充电
    if ((!mScreenOn && !mCharging) || mForceIdle) {
        // Screen has turned off; we are now going to become inactive and start
        // waiting to see if we will ultimately go idle.
        if (mState == STATE_ACTIVE && mDeepEnabled) {
            mState = STATE_INACTIVE;
            ......
            resetIdleManagementLocked();//重置事件

            //开始检测是否可以进入Doze模式的Idle状态
            //若终端没有watch feature, mInactiveTimeout时间为30min
            scheduleAlarmLocked(mInactiveTimeout, false);
            .......
        }
        if (mLightState == LIGHT_STATE_ACTIVE && mLightEnabled) {
            mLightState = LIGHT_STATE_INACTIVE;
            ........
            resetLightIdleManagementLocked();
            scheduleLightAlarmLocked(mConstants.LIGHT_IDLE_AFTER_INACTIVE_TIMEOUT);
            ........
        }
    }
}
    从上面的代码中可以看出,在DeviceIdleState中,用mState和mLightState来衡量终端是否真的进入了Doze模式。我们目前仅关注mState变量的改变情况,mLightState的变化流程可类似分析。

    此时,mState的状态为INACTIVE。

scheduleAlarmLocked

void scheduleAlarmLocked(long delay, boolean idleUntil) {
    ........
    if (mMotionSensor == null) {
        // If there is no motion sensor on this device, then we won't schedule
        // alarms, because we can't determine if the device is not moving.  This effectively
        // turns off normal execution of device idling, although it is still possible to
        // manually poke it by pretending like the alarm is going off.
        //在onBootPhase时,获取过位置检测传感器
        //如果终端没有配置位置检测传感器,那么终端永远不会进入到真正的Doze ilde状态
        return;
    }
    mNextAlarmTime = SystemClock.elapsedRealtime() + delay;
    if (idleUntil) {//此时IdleUtil的值为false
        mAlarmManager.setIdleUntil(AlarmManager.ELAPSED_REALTIME_WAKEUP,
                mNextAlarmTime, "DeviceIdleController.deep", mDeepAlarmListener, mHandler);
    } else {
        30min后唤醒,调用mDeepAlarmListener的onAlarm函数
        mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
                mNextAlarmTime, "DeviceIdleController.deep", mDeepAlarmListener, mHandler);
    }
}

    需要注意的是,DeviceIdleController一直在监控屏幕状态和充电状态,一旦不满足Doze模式的条件,前面提到的becomeActiveLocked方法就会被调用。mAlarmManager设置的定时唤醒事件将被取消,mDeepAlarmListener的onAlarm方法不会被调用。

    因此,我们知道了终端必须保持Doze模式的入口条件长达30min,才会进入mDeepAlarmListener.onAlarm:

private final AlarmManager.OnAlarmListener mDeepAlarmListener
        = new AlarmManager.OnAlarmListener() {
    @Override
    public void onAlarm() {
        synchronized (DeviceIdleController.this) {
            //进入到stepIdleStateLocked函数
            stepIdleStateLocked("s:alarm");
        }
    }
};
    这里直接调用了stepIdleStateLocked()方法。需要注意的是stepIdleStateLocked方法将决定DeviceIdleController状态之间的转移。这种通过AlarmManager设定唤醒时间,然后通过回调接口来调用stepIdleStateLocked的方式,将被多次使用。

stepIdleStateLocked

void stepIdleStateLocked(String reason) {
    .......

    final long now = SystemClock.elapsedRealtime();
    //如果在Idle状态收到Alarm,那么将先唤醒终端,然后重新判断是否需要进入Idle态
    //在介绍Doze模式原理时提到过,若应用调用AlarmManager的一些指定接口,仍然可以在Idle状态进行工作
    if ((now+mConstants.MIN_TIME_TO_ALARM) > mAlarmManager.getNextWakeFromIdleTime()) {
        // Whoops, there is an upcoming alarm.  We don't actually want to go idle.
        if (mState != STATE_ACTIVE) {
            becomeActiveLocked("alarm", Process.myUid());
            becomeInactiveIfAppropriateLocked();
        }
        return;
    }

    以下是Doze模式的状态转变相关的代码
    switch (mState) {
        case STATE_INACTIVE:
            // We have now been inactive long enough, it is time to start looking
            // for motion and sleep some more while doing so.
            //保持屏幕熄灭,同时未充电达到30min,进入此分支

            //注册一个mMotionListener,检测是否移动
            //如果检测到移动,将重新进入到ACTIVE状态
            startMonitoringMotionLocked();
            //再次调用scheduleAlarmLocked函数,此次的时间仍为30min
            //也就说如果不发生退出Doze模式的事件,30min后将再次进入到stepIdleStateLocked函数
            //不过届时的mState已经变为STATE_IDLE_PENDING
            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;
            .........
            break;
        case STATE_IDLE_PENDING:
            //保持息屏、未充电、静止状态,经过30min后,进入此分支
            mState = STATE_SENSING;
            .........
            //保持Doze模式条件,4min后再次进入stepIdleStateLocked
            scheduleSensingTimeoutAlarmLocked(mConstants.SENSING_TIMEOUT);
            cancelLocatingLocked();//停止定位相关的工作
            mNotMoving = false;
            mLocated = false;
            mLastGenericLocation = null;
            mLastGpsLocation = null;
            //开始检测手机是否发生运动(这里应该是更细致的侧重于角度的变化)
            //若手机运动过,则重新变为active状态
            mAnyMotionDetector.checkForAnyMotion();
            break;
        case STATE_SENSING:
          //上面的条件满足后,进入此分支,开始获取定位信息
            cancelSensingTimeoutAlarmLocked();
            mState = STATE_LOCATING;
            ...........
          //保持条件30s,再次调用stepIdleStateLocked
            scheduleAlarmLocked(mConstants.LOCATING_TIMEOUT, false);
          //网络定位
            if (mLocationManager != null
                    && mLocationManager.getProvider(LocationManager.NETWORK_PROVIDER) != null) {
                mLocationManager.requestLocationUpdates(mLocationRequest,
                        mGenericLocationListener, mHandler.getLooper());
                mLocating = true;
            } else {
                mHasNetworkLocation = false;
            }
            //GPS定位
            if (mLocationManager != null
                    && mLocationManager.getProvider(LocationManager.GPS_PROVIDER) != null) {
                mHasGps = true;
                mLocationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 1000, 5,
                        mGpsLocationListener, mHandler.getLooper());
                mLocating = true;
            } else {
                mHasGps = false;
            }
            // If we have a location provider, we're all set, the listeners will move state
            // forward.
            if (mLocating) {//无法定位则直接进入下一个case
                break;
            }

            // Otherwise, we have to move from locating into idle maintenance.
        case STATE_LOCATING://停止定位和运动检测,直接进入到STATE_IDLE_MAINTENANCE
            cancelAlarmLocked();
            cancelLocatingLocked();
            mAnyMotionDetector.stop();

        case STATE_IDLE_MAINTENANCE:
            //进入到这个case后,终端开始进入Idle状态,也就是真正的Doze模式
            //定义退出Idle的时间此时为60min
            scheduleAlarmLocked(mNextIdleDelay, true);
            ............
          //退出周期逐步递增,每次乘2
            mNextIdleDelay = (long)(mNextIdleDelay * mConstants.IDLE_FACTOR);
            ...........
          //周期有最大值6h
            mNextIdleDelay = Math.min(mNextIdleDelay, mConstants.MAX_IDLE_TIMEOUT);
            if (mNextIdleDelay < mConstants.IDLE_TIMEOUT) {
                mNextIdleDelay = mConstants.IDLE_TIMEOUT;
            }
            mState = STATE_IDLE;
            if (mLightState != LIGHT_STATE_OVERRIDE) {
                mLightState = LIGHT_STATE_OVERRIDE;
                cancelLightAlarmLocked();
            }
            EventLogTags.writeDeviceIdle(mState, reason);
            addEvent(EVENT_DEEP_IDLE);
            //通知PMS、NetworkPolicyManagerService等Doze模式开启,即进入Idle状态
            //此时PMS disable一些非白名单WakeLock;NetworkPolicyManagerService开始限制一些应用的网络访问
            mHandler.sendEmptyMessage(MSG_REPORT_IDLE_ON);
            break;
        case STATE_IDLE:
            // We have been idling long enough, now it is time to do some work.
            //进入到这个case时,本次的Idle状态暂时结束,开启maintenance window
            mActiveIdleOpCount = 1;
            mActiveIdleWakeLock.acquire();
          //定义重新进入Idle的时间为5min (也就是手机可处于Maintenance window的时间)
            scheduleAlarmLocked(mNextIdlePendingDelay, false);
            ...........
            mMaintenanceStartTime = SystemClock.elapsedRealtime();
          //调整mNextIdlePendingDelay,乘2(最大为10min)
            mNextIdlePendingDelay = Math.min(mConstants.MAX_IDLE_PENDING_TIMEOUT,
                    (long)(mNextIdlePendingDelay * mConstants.IDLE_PENDING_FACTOR));
            if (mNextIdlePendingDelay < mConstants.IDLE_PENDING_TIMEOUT) {
                mNextIdlePendingDelay = mConstants.IDLE_PENDING_TIMEOUT;
            }
            mState = STATE_IDLE_MAINTENANCE;
            .........
            //通知PMS等暂时退出了Idle状态,可以进行一些工作
            //此时PMS enable一些非白名单WakeLock;NetworkPolicyManagerService开始允许应用的网络访问
            mHandler.sendEmptyMessage(MSG_REPORT_IDLE_OFF);
            break;
    }
}
    至此,stepIdleStateLocked的流程介绍完毕。 我们知道了,在DeviceIdleController中,为终端定义了7中状态,如下图所示:

    手机被操作的时候为Active状态。

    当手机关闭屏幕或者拔掉电源的时候,手机开始判断是否进入Doze模式。

    经过一系列的状态后,最终会进入到IDLE状态,此时才算进入到真正的Doze模式,系统进入到了深度休眠状态。 此时,系统中非白名单的应用将被禁止访问网络,它们申请的Wakelock也会被disable。

    从上面的代码可以看出,系统会周期性的退出Idle状态,进入到MAINTENANCE状态,集中处理相关的任务。一段时间后,会重新再次回到IDLE状态。每次进入IDLE状态,停留的时间都会是上次的2倍,最大时间限制为6h。

    当手机运动,或者点亮屏幕,插上电源等,系统都会重新返回到ACTIVIE状态。

总结

    这里我们主要分析了Doze模式对应的服务DeviceIdleController,接下来我们看下Doze模式对APP的影响。

参考:Android M新特性Doze and App Standby模式详解

  • 2
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值