Android7.0 Doze模式分析(一)Doze介绍 & DeviceIdleController



参考:http://blog.csdn.net/gaugamela/article/details/52981984

在Android M中,Google就引入了Doze模式。它定义了一种全新的、低能耗的状态。

在该状态,后台只有部分任务被允许运行,其它任务都被强制停止。

在之前的博客中分析过Doze模式,就是device idle状态。可能有的地方分析的不是很详细,现在在android7.0上重新分析下。

一、基本原理

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运行。

另外我在另一篇博客中:http://blog.csdn.net/kc58236582/article/details/50554174也详细介绍了Doze模式,可以参考下,上面有一些命令使用等。

二、DeviceIdleController

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

public class DeviceIdleController extends SystemService
        implements AnyMotionDetector.DeviceIdleCallback 

可以看出DeviceIdleController继承自SystemService,是一个系统级的服务。

同时,继承了AnyMotionDetector定义的接口,便于检测到终端位置变化后进行回调。

2.1 DeviceIdleController的初始化

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

private void startOtherServices() {
    .........
    mSystemServiceManager.startService(DeviceIdleController.class);
    .........
}

如上代码所示,SystemServer在startOtherServices中启动了DeviceIdleController,将先后调用DeviceIdleController的构造函数和onStart函数。

构造函数

public DeviceIdleController(Context context) {
    super(context);
    //deviceidle.xml用于定义idle模式也能正常工作的非系统应用
    mConfigFile = new AtomicFile(new File(getSystemDir(), "deviceidle.xml"));
    mHandler = new MyHandler(BackgroundThread.getHandler().getLooper());
}

DeviceIdleController的构造函数比较简单,就是在创建data/system/deviceidle.xml对应的file文件,同时创建一个对应于后台线程的handler。这里的deviceidle.xml可以在设置中的电池选项那里。有电池优化,可以将一些应用放到白名单中,调用DeviceIdleController的addPowerSaveWhitelistApp方法,最后会写入deviceidle.xml文件,然后在下次开机的时候DeviceIdleController会重新读取deviceidle.xml文件然后放入白名单mPowerSaveWhitelistUserApps中。

onStart函数

public void onStart() {
    final PackageManager pm = getContext().getPackageManager();

    synchronized (this) {
        //读取配置文件,判断Doze模式是否允许被开启
        mLightEnabled = mDeepEnabled = getContext().getResources().getBoolean(
                com.android.internal.R.bool.config_enableAutoPowerModes);

        //分析PKMS时提到过,PKMS扫描系统目录的xml,将形成SystemConfig
        SystemConfig sysConfig = SystemConfig.getInstance();

        //获取除了device Idle模式外,都可以运行的系统应用白名单
        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) {
            }
        }

        //获取device Idle模式下,也可以运行的系统应用白名单
        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;

        //Doze模式定义终端初始时为ACTIVE状态
        mState = STATE_ACTIVE;
        //屏幕状态初始时为ACTIVE状态
        mLightState = LIGHT_STATE_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) {
        ...........
        //PMS拿到的是:系统和普通应用组成的不受Doze限制的app id数组
        mLocalPowerManager.setDeviceIdleWhitelist(mPowerSaveWhitelistAllAppIdArray);
    }

    if (mLocalAlarmManager != null) {
        ..........
        //AlarmManagerService拿到的是:普通应用组成的不受Doze限制的app id数组
        mLocalAlarmManager.setDeviceIdleUserWhitelist(mPowerSaveWhitelistUserAppIdArray);
    }
}

updateWhitelistAppIdsLocked主要是将白名单交给PMS和AlarmManagerService。

注意Android区分了系统应用白名单、普通应用白名单等,因此上面进行了一些合并操作。这里我们有没有发现,systemConfig的app不会加入alarm的白名单,而在Settings中电池那边设置的白名单,会加入Power wakelock的白名单。

onBootPhase函数

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

public void onBootPhase(int phase) {
    //在系统PHASE_SYSTEM_SERVICES_READY阶段,进一步完成一些初始化
    if (phase == PHASE_SYSTEM_SERVICES_READY) {
        synchronized (this) {
            //初始化一些变量
            mAlarmManager = (AlarmManager) getContext().getSystemService(Context.ALARM_SERVICE);
            ..............

            mSensorManager = (SensorManager) getContext().getSystemService(Context.SENSOR_SERVICE);
            //根据配置文件,利用SensorManager获取对应的传感器,保存到mMotionSensor中
            ..............

            //如果配置文件表明:终端需要预获取位置信息
            //则构造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方法:

主要创建一些本地变量,然后根据配置文件初始化一些传感器,同时注册了一些广播接收器和回到接口,

最后更新屏幕显示和连接状态相关的信息。

2.2 DeviceIdleController的状态变化

充电状态的处理

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

............
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_BATTERY_CHANGED);
getContext().registerReceiver(mReceiver, filter);
...........

我们看看receiver中对应的处理:

private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
    @Override public void onReceive(Context context, Intent intent) {
        switch (intent.getAction()) {
            .........
            case Intent.ACTION_BATTERY_CHANGED: {
                synchronized (DeviceIdleController.this) {
                    //从广播中得到是否在充电的消息
                    int plugged = intent.getIntExtra("plugged", 0);
                    updateChargingLocked(plugged != 0);
                }
            } break;
        }
    }
};

根据上面的代码,可以看出当收到电池信息改变的广播后,DeviceIdleController将得到电源是否在充电的消息,然后调用updateChargingLocked函数进行处理。

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

becomeInactiveIfAppropriateLocked函数是开始进入Doze模式,而becomeActiveLocked是退出Doze模式。

显示状态处理

DeviceIdleController中注册了显示变化的回调

                mDisplayManager.registerDisplayListener(mDisplayListener, null);

回调会调用updateDisplayLocked函数

    private final DisplayManager.DisplayListener mDisplayListener
            = new DisplayManager.DisplayListener() {
        @Override public void onDisplayAdded(int displayId) {
        }

        @Override public void onDisplayRemoved(int displayId) {
        }

        @Override public void onDisplayChanged(int displayId) {
            if (displayId == Display.DEFAULT_DISPLAY) {
                synchronized (DeviceIdleController.this) {
                    updateDisplayLocked();
                }
            }
        }
    };

updateDisplayLocked函数和更新充电状态的函数updateChargingLocked类似

    void updateDisplayLocked() {
        mCurDisplay = mDisplayManager.getDisplay(Display.DEFAULT_DISPLAY);
        // We consider any situation where the display is showing something to be it on,
        // because if there is anything shown we are going to be updating it at some
        // frequency so can‘t be allowed to go into deep sleeps.
        boolean screenOn = mCurDisplay.getState() == Display.STATE_ON;
        if (DEBUG) Slog.d(TAG, "updateDisplayLocked: screenOn=" + screenOn);
        if (!screenOn && mScreenOn) {
            mScreenOn = false;
            if (!mForceIdle) {//开始进入Doze模式
                becomeInactiveIfAppropriateLocked();
            }
        } else if (screenOn) {//屏幕点亮,退出Doze模式
            mScreenOn = true;
            if (!mForceIdle) {
                becomeActiveLocked("screen", Process.myUid());
            }
        }
    }

becomeActiveLocked函数退出Doze模式

我们先来看看becomeActiveLocked函数

//activeReason记录的终端变为active的原因
void becomeActiveLocked(String activeReason, int activeUid) {
    ...........
    if (mState != STATE_ACTIVE || mLightState != STATE_ACTIVE) {
        ............
        //1、通知PMS等Doze模式结束
        scheduleReportActiveLocked(activeReason, activeUid);

        //更新DeviceIdleController本地维护的状态
        //在DeviceIdleController的onStart函数中,我们已经知道了
        //初始时,mState和mLightState均为Active状态
        mState = STATE_ACTIVE;//state是指设备通过传感器判断进入idle
        mLightState = LIGHT_STATE_ACTIVE;//mLight是背光的状态

        mInactiveTimeout = mConstants.INACTIVE_TIMEOUT;
        mCurIdleBudget = 0;
        mMaintenanceStartTime = 0;

        //2、重置一些事件
        resetIdleManagementLocked();
        resetLightIdleManagementLocked();

        addEvent(EVENT_NORMAL);
    }
}

scheduleReportActiveLocked函数就是发送MSG_REPORT_ACTIVE消息

void scheduleReportActiveLocked(String activeReason, int activeUid) {
    //发送MSG_REPORT_ACTIVE消息
    Message msg = mHandler.obtainMessage(MSG_REPORT_ACTIVE, activeUid, 0, activeReason);
    mHandler.sendMessage(msg);
}

我们再看下消息的处理,主要调用了PowerManagerService的setDeviceIdleMode函数来退出Doze状态,然后重新更新wakelock的enable状态, 以及通知NetworkPolicyManagerService不再限制应用上网,还有发送Doze模式改变的广播。

.........
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);
        //BSS做好对应的记录
        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);
    }
}
........

resetIdleManagementLocked函数就是取消alarm,检测等。

void resetIdleManagementLocked() {
    //复位一些状态变量
    mNextIdlePendingDelay = 0;
    mNextIdleDelay = 0;
    mNextLightIdleDelay = 0;

    //停止一些工作,主要是位置检测相关的
    cancelAlarmLocked();
    cancelSensingTimeoutAlarmLocked();
    cancelLocatingLocked();
    stopMonitoringMotionLocked();
    mAnyMotionDetector.stop();
}

becomeInactiveIfAppropriateLocked函数开始进入Doze模式

becomeInactiveIfAppropriateLocked函数就是我们开始进入Doze模式的第一个步骤,下面我们就详细分析这个函数

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);
        }
    }
}

要进入Doze流程,就是调用这个函数,首先要保证屏幕灭屏然后没有充电。这里还有mDeepEnable和mLightEnable前面说过是在配置中定义的,一般默认是关闭(也就是不开Doze模式)。这里mLightEnabled是对应禁止wakelock持锁的,禁止网络。而mDeepEnabled对应是检测设备是否静止,除了禁止wakelock、禁止网络、还会机制alarm。

light idle模式

我们先看light idle模式,这个模式下、会禁止网络、wakelock,但是不会禁止alarm。

我们先看scheduleLightAlarmLocked函数,这里设置了一个alarm,delay是5分钟。到时间后调用mLightAlarmListener回调。

    void scheduleLightAlarmLocked(long delay) {
        if (DEBUG) Slog.d(TAG, "scheduleLightAlarmLocked(" + delay + ")");
        mNextLightAlarmTime = SystemClock.elapsedRealtime() + delay;
        mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
                mNextLightAlarmTime, "DeviceIdleController.light", mLightAlarmListener, mHandler);
    }

mLightAlarmListener就是进入lightidle,调用stepLightIdleStateLocked函数

    private final AlarmManager.OnAlarmListener mLightAlarmListener
            = new AlarmManager.OnAlarmListener() {
        @Override
        public void onAlarm() {
            synchronized (DeviceIdleController.this) {
                stepLightIdleStateLocked("s:alarm");
            }
        }
    };

我们来看stepLightIdleStateLocked函数,这个函数会处理mLightState不同状态,会根据不同状态,然后设置alarm,到时间后继续处理下个状态。到LIGHT_STATE_IDLE_MAINTENANCE状态处理时,会发送MSG_REPORT_IDLE_ON_LIGHT。这个消息的处理会禁止网络、禁止wakelock。然后到LIGHT_STATE_WAITING_FOR_NETWORK,会先退出Doze状态(这个时候网络、wakelock恢复)。然后设置alarm,alarm时间到后,还是在LIGHT_STATE_IDLE_MAINTENANCE状态。和之前一样(禁止网络、wakelock)。只是设置的alarm间隔会越来越大,也就是只要屏幕灭屏后,时间越长。设备会隔越来越长的时间才会退出Doze状态,这也符合一个实际情况,但是会有一个上限值。

    void stepLightIdleStateLocked(String reason) {
        if (mLightState == LIGHT_STATE_OVERRIDE) {
            // If we are already in deep device idle mode, then
            // there is nothing left to do for light mode.
            return;
        }

        EventLogTags.writeDeviceIdleLightStep();

        switch (mLightState) {
            case LIGHT_STATE_INACTIVE:
                mCurIdleBudget = mConstants.LIGHT_IDLE_MAINTENANCE_MIN_BUDGET;
                // Reset the upcoming idle delays.
                mNextLightIdleDelay = mConstants.LIGHT_IDLE_TIMEOUT;
                mMaintenanceStartTime = 0;
                if (!isOpsInactiveLocked()) {
                    // We have some active ops going on...  give them a chance to finish
                    // before going in to our first idle.
                    mLightState = LIGHT_STATE_PRE_IDLE;
                    EventLogTags.writeDeviceIdleLight(mLightState, reason);
                    scheduleLightAlarmLocked(mConstants.LIGHT_PRE_IDLE_TIMEOUT);//设置alarm,时间到后到下个步骤
                    break;
                }
                // Nothing active, fall through to immediately idle.
            case LIGHT_STATE_PRE_IDLE:
            case LIGHT_STATE_IDLE_MAINTENANCE:
                if (mMaintenanceStartTime != 0) {
                    long duration = SystemClock.elapsedRealtime() - mMaintenanceStartTime;
                    if (duration < mConstants.LIGHT_IDLE_MAINTENANCE_MIN_BUDGET) {
                        // We didn‘t use up all of our minimum budget; add this to the reserve.
                        mCurIdleBudget += (mConstants.LIGHT_IDLE_MAINTENANCE_MIN_BUDGET-duration);
                    } else {
                        // We used more than our minimum budget; this comes out of the reserve.
                        mCurIdleBudget -= (duration-mConstants.LIGHT_IDLE_MAINTENANCE_MIN_BUDGET);
                    }
                }
                mMaintenanceStartTime = 0;
                scheduleLightAlarmLocked(mNextLightIdleDelay);
                mNextLightIdleDelay = Math.min(mConstants.LIGHT_MAX_IDLE_TIMEOUT,
                        (long)(mNextLightIdleDelay * mConstants.LIGHT_IDLE_FACTOR));
                if (mNextLightIdleDelay < mConstants.LIGHT_IDLE_TIMEOUT) {
                    mNextLightIdleDelay = mConstants.LIGHT_IDLE_TIMEOUT;
                }
                if (DEBUG) Slog.d(TAG, "Moved to LIGHT_STATE_IDLE.");
                mLightState = LIGHT_STATE_IDLE;
                EventLogTags.writeDeviceIdleLight(mLightState, reason);
                addEvent(EVENT_LIGHT_IDLE);
                mHandler.sendEmptyMessage(MSG_REPORT_IDLE_ON_LIGHT);//发送消息,这个消息处理就会关闭网络,禁止wakelock
                break;
            case LIGHT_STATE_IDLE:
            case LIGHT_STATE_WAITING_FOR_NETWORK:
                if (mNetworkConnected || mLightState == LIGHT_STATE_WAITING_FOR_NETWORK) {
                    // We have been idling long enough, now it is time to do some work.
                    mActiveIdleOpCount = 1;
                    mActiveIdleWakeLock.acquire();
                    mMaintenanceStartTime = SystemClock.elapsedRealtime();
                    if (mCurIdleBudget < mConstants.LIGHT_IDLE_MAINTENANCE_MIN_BUDGET) {
                        mCurIdleBudget = mConstants.LIGHT_IDLE_MAINTENANCE_MIN_BUDGET;
                    } else if (mCurIdleBudget > mConstants.LIGHT_IDLE_MAINTENANCE_MAX_BUDGET) {
                        mCurIdleBudget = mConstants.LIGHT_IDLE_MAINTENANCE_MAX_BUDGET;
                    }
                    scheduleLightAlarmLocked(mCurIdleBudget);
                    if (DEBUG) Slog.d(TAG,
                            "Moved from LIGHT_STATE_IDLE to LIGHT_STATE_IDLE_MAINTENANCE.");
                    mLightState = LIGHT_STATE_IDLE_MAINTENANCE;
                    EventLogTags.writeDeviceIdleLight(mLightState, reason);
                    addEvent(EVENT_LIGHT_MAINTENANCE);
                    mHandler.sendEmptyMessage(MSG_REPORT_IDLE_OFF);//醒一下(开启网络、恢复wakelock)
                } else {
                    // We‘d like to do maintenance, but currently don‘t have network
                    // connectivity...  let‘s try to wait until the network comes back.
                    // We‘ll only wait for another full idle period, however, and then give up.
                    scheduleLightAlarmLocked(mNextLightIdleDelay);
                    if (DEBUG) Slog.d(TAG, "Moved to LIGHT_WAITING_FOR_NETWORK.");
                    mLightState = LIGHT_STATE_WAITING_FOR_NETWORK;
                    EventLogTags.writeDeviceIdleLight(mLightState, reason);
                }
                break;
        }
    }

但是这里只是一个light idle,一旦进入deep idle,light idle设置的alarm会无效的(这个后面细说),也就是说light idle一旦进入deep idle后无效了(因为idle step主要靠alarm驱动,而alarm无效后自然就驱动不了)。

deep idle模式

下面我们再来看deep idle模式,这个模式除了禁止网络、wakelock还会禁止alarm。

我们再来看becomeInactiveIfAppropriateLocked函数中下面代码。是关于deep idle的设置 这里的mInactiveTimeout是半小时

    void becomeInactiveIfAppropriateLocked() {
        if (DEBUG) Slog.d(TAG, "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;
                if (DEBUG) Slog.d(TAG, "Moved from STATE_ACTIVE to STATE_INACTIVE");
                resetIdleManagementLocked();
                scheduleAlarmLocked(mInactiveTimeout, false);
                EventLogTags.writeDeviceIdle(mState, "no activity");
            }

我们来看下scheduleAlarmLocked函数,注意如果这里参数idleUntil是true会调用AlarmManager的setIdleUntil函数,调用这个函数后普通应用设置alarm将失效。

void scheduleAlarmLocked(long delay, boolean idleUntil) {
    if (mMotionSensor == null) {
        //在onBootPhase时,获取过位置检测传感器
        //如果终端没有配置位置检测传感器,那么终端永远不会进入到真正的Doze ilde状态
        // 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.
        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函数:

void stepIdleStateLocked(String reason) {
    ..........
    final long now = SystemClock.elapsedRealtime();
    //个人觉得,下面这段代码,是针对Idle状态设计的
    //如果在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为5min
            mNextIdlePendingDelay = mConstants.IDLE_PENDING_TIMEOUT;
            //mNextIdleDelay为60min
            mNextIdleDelay = mConstants.IDLE_TIMEOUT;

            //状态变为STATE_IDLE_PENDING
            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;
            }
        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;
            ...........
            //通知PMS、NetworkPolicyManagerService等Doze模式开启,即进入Idle状态
            //此时PMS disable一些非白名单WakeLock;NetworkPolicyManagerService开始限制一些应用的网络访问
            //消息处理的具体流程比较直观,此处不再深入分析
            mHandler.sendEmptyMessage(MSG_REPORT_IDLE_ON);
            break;

        case STATE_IDLE:
            //进入到这个case时,本次的Idle状态暂时结束,开启maintenance window

            // We have been idling long enough, now it is time to do some work.
            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;
    }
}

上面的流程在注释里面已经很明白了,而我们在进入Deep idle时,发送了一个MSG_REPORT_IDLE_ON消息,我们看下面这个消息的处理和之前的MSG_REPORT_IDLE_ON_LIGHT一样的,关闭网络,禁止wakelock。

                case MSG_REPORT_IDLE_ON:
                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;

而禁止alarm是通过调用如下函数,注意参数是true。参数是true会调用mAlarmManager.setIdleUntil函数。这样其他的alarm会被滞后(除非在白名单中)

scheduleAlarmLocked(mNextIdleDelay, true);

而每隔一段时间会进入Maintenance window的时间,此时是通过发送MSG_REPORT_IDLE_OFF消息,来恢复网络和wakelock。而这个时候之前设置的mAlarmManager.setIdleUntil的alarm也到期了,因此其他alarm也恢复了。但是这个时间只有5分钟,重新设置了alarm再次进入deep idle状态。

Idle总结

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

Doze模式分两种,第一种是light idle:

1.light idle

light idle在手机灭屏且没有充电状态下,5分钟开始进入light idle流程。然后第一次进入LIGHT_STATE_INACTIVE流程时,会再定义一个10分钟的alarm。然后系统进入light idle状态。这个状态会使不是白名单的应用禁止访问网络,以及持wakelock锁。

2.deep idle

deep idle除了light idle的状态还会把非白名单中应用的alarm也禁止了。

此时,系统中非白名单的应用将被禁止访问网络,它们申请的Wakelock也会被disable。

从上面的代码可以看出,系统会周期性的退出Idle状态,进入到MAINTENANCE状态,集中处理相关的任务。

一段时间后,会重新再次回到IDLE状态。每次进入IDLE状态,停留的时间都会是上次的2倍,最大时间限制为6h。

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

这里盗用别人的一样图,但仅仅是deep idle的状态:

(这里特别说明下,alarm和wakelock都是由DeviceIdleController主动调用相关接口设置的,而网络是调用了DeviceIdleController的getAppIdWhitelist接口来获取应用的白名单的,从而禁止非白名单访问网络。)

网络我们不分析了,之前我们在Android6.0时分析过idle状态下alarm和wakelock,7.0稍微有点不一样,下面两篇博客重新分析下吧。

时间: 2024-10-26 23:55:59

Android7.0 Doze模式分析(一)Doze介绍 & DeviceIdleController的相关文章

Android7.0 Doze模式分析(一)Doze介绍 &amp;amp; DeviceIdleController

 參考:http://blog.csdn.net/gaugamela/article/details/52981984 在Android M中,Google就引入了Doze模式.它定义了一种全新的.低能耗的状态. 在该状态,后台仅仅有部分任务被同意执行.其他任务都被强制停止. 在之前的博客中分析过Doze模式.就是device idle状态.可能有的地方分析的不是非常具体,如今在android7.0上又一次分析下. 一.基本原理 Doze模式能够简单概括为: 若推断用户在连续的一段时间内没有

Android7.0 Doze模式

在Android M中,Google就引入了Doze模式.它定义了一种全新的.低能耗的状态. 在该状态,后台只有部分任务被允许运行,其它任务都被强制停止. 本篇博客中,我们就来分析一下Android 7.0中Doze模式相关的流程. 一.基本原理 Doze模式可以简单概括为: 若判断用户在连续的一段时间内没有使用手机,就延缓终端中APP后台的CPU和网络活动,以达到减少电量消耗的目的. 上面这张图比较经典,基本上说明了Doze模式的含义. 图中的横轴表示时间,红色部分表示终端处于唤醒的运行状态,

WmS详解(二)之如何理解Window和窗口的关系?基于Android7.0源码

上篇博客(WmS详解(一)之token到底是什么?基于Android7.0源码)中我们简要介绍了token的作用,这里涉及到的概念非常多,其中出现频率最高的要数Window和窗口这一对搭档了,那么我们今天就来看看到底我们该如何理解Android系统中的Window和窗口. 窗口这个概念,从不同的角度来看它的含义不一样,如果我们从WmS(WindowManagerService)的角度来看窗口,那么这个窗口并不是一个Window类,而是一个View.用户发来的消息被WmS接收之后并不能直接发给各个

Android7.0 Vold 进程工作机制分析之整体流程

Android7.0 Vold 进程工作机制分析之整体流程 一.Vold简介 Vold是Volume Daemon的缩写,负责管理和控制Android平台外部存储设备,包括SD插拨.挂载.卸载.格式化等.它是通过init进程解析init.rc脚本所启动的进程.它处于Native层. 二.基础架构 这里引用Gityuan博客的一张图. SystermServer进程和Vold进程是通过Socket进行通信的,Vold进程和Kernel是通过Netlink 进行通信的,Netlink 是一种特殊的S

Android7.0调用系统相机拍照、读取系统相册照片+CropImageView剪裁照片

Android手机拍照.剪裁,并非那么简单 简书地址:[我的简书–T9的第三个三角] 前言 项目中,基本都有用户自定义头像或自定义背景的功能,实现方法一般都是调用系统相机–拍照,或者系统相册–选择照片,然后进行剪裁,最终设为头像或背景. 而在Android6.0之后,需要动态获取权限,而且Android7.0之后,无法直接根据拍照返回的URI拿到图片,这是因为从安卓7.0开始,直接使用本地真实路径被认为是不安全的,会抛出FileUriExposedExCeption异常,本文就是基于这个功能去针

[Android Pro] Android7.0系统 关于Android获取流量计数TrafficStats.getUidRxBytes(uid)和TrafficStats.getUidTxBytes(uid)返回-1解决方案

reference : http://blog.csdn.net/zhangyong7112/article/details/54574214 最近一个关于流量的项目在Android7.0系统的手机上运行,一直获取不到流量的使用数据,查看源码然后发现TrafficStats.getUidRxBytes(uid)和TrafficStats.getUidTxBytes(uid)一直都是返回的-1, // 获取某个网络UID接收和发送字节的总和 long total = TrafficStats.ge

Android7.0新特性,及Android N适配

新特性部分 Android 7.0 Nougat 提供新功能以提升性能.生产效率和安全性,主要新增了以下的新特性和优化: 一.新的Notification Android N 增加了许多新的notifications API,进行了重新的设计,引入了新的风格. 模板更新: 开发者将能够充分利用新模板,只需进行少量的代码调整. 消息样式自定义: 新增自定义样式.消息回复.消息分组等更加灵活. 捆绑通知: 系统可以将消息组合在一起(例如,按消息主题)并显示组.用户可以适当地进行 Dismiss 或

Android7.0对dlopen的改变

两个内存段 在同一个进程空间中dlopen一个.so文件,理论上在内存中是同一片区域,但实际调试中发现Android7.0(read "/proc/self/maps")中,先后读同一个.so内存中居然出现两个段! 这在低版本Android(比如4.x)中不曾出现. 如下一些blog中分析,与Android7.0对dlopen的改写有关,可能是不同命名空间下读取结果不一样,可能是对安全性的提升. Android 7.0 行为变更 NDK 应用链接至平台库 Android 7.0 dlo

拍照、本地图片工具类(兼容至Android7.0)

拍照.本地图片工具类:解决了4.4以上剪裁会提示"找不到文件"和6.0动态授予权限,及7.0报FileUriExposedException异常问题. package com.hb.weex.util; import android.Manifest; import android.app.Activity; import android.app.Dialog; import android.content.ClipData; import android.content.Conten