Android 8.1 Doze模式分析(一)——Doze简介和DeviceIdleController的启动

概述

Doze模式,官方翻译为低电耗模式,是Andoriod6.0增加的一项系统服务,主要目的是为了优化电池性能,增加电池续航时间,Doze模式又分两种模式:深度Doze模式(Deep Doze)和轻度Doze模式(Light Doze),如果用户长时间没有主动使用其设备,处于静止状态且屏幕已关闭,则系统会使设备进入Doze模式,也就是深度Doze模式。如果用户关闭设备屏幕但仍处于移动状态时,则设备进入轻度Doze模式,此外,轻度Doze模式只适合Android7.0及以上版本。

当用户长时间未使用设备时,设备进入Doze模式,Doze模式会延迟应用后台 CPU 和网络活动,从而延长电池续航时间。处于Doze模式的设备会定期进入维护时段,在此期间,应用可以完成待进行的活动。然后,Doze模式会使设备重新进入较长时间的休眠状态,接着进入下一个维护时段。在达到几个小时的休眠时间上限之前,平台会周而复始地重复Doze模式休眠/维护的序列,且每一次都会延长Doze模式时长。处于Doze模式的设备始终可以感知到动作,且会在检测到动作时立即退出Doze模式。整个Doze图示如下:
Android 8.1 Doze模式分析(一)——Doze简介和DeviceIdleController的启动_第1张图片

Deep Doze 和Light Doze模式对比如下:

操作 低电耗模式 轻度低电耗模式
触发因素 屏幕关闭、电池供电、静止 屏幕关闭、电池供电(未插电)
时间 随维护时段依次增加 随维护时段反复持续 N 分钟
限制 无法进行网络访问、唤醒锁忽略、 GPS/WLAN无法扫描、闹钟和SyncAdapter/JobScheduler被延迟。 无法进行网络访问、SyncAdapter/JobScheduler
行为 仅接收优先级较高的推送通知消息。 接收所有实时消息(即时消息、来电等)。优先级较高的推送通知消息可以暂时访问网络。
退出 设备有移动、和用户有交互、屏幕开启、闹钟响铃 屏幕开启。

接下来就分析Doze的实现原理。Doze模式是通过DeviceIdleController来实现的。

1.DeviceIdleController的启动流程

DeviceIdleController(以下简称DIC)继承于SystemService,因此也是一个系统服务,在分析PMS的时候说过,继承于SytemService的服务启动有以下几个共同点:

  • 1.在SystemServer中实例化并启动,启动时会执行SytemService的生命周期方法:
    Constructor()->onStart()->onBootPhase()
  • 2.内部维护一个Binder和其他服务进行IPC通讯;
  • 3.内部维护一个Internal类用于和System进程进行交互;

下面就从SystemServer开始分析DIC的启动流程。

在SystemServer启动其他服务时启动DIC:

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

在SystemServiceManager中,通过反射的方式获取了DIC对象,并且调用了onStart()方法:

public <T extends SystemService> T startService(Class<T> serviceClass) {
    try {
        ....................
        final String name = serviceClass.getName();
        Slog.i(TAG, "Starting " + name);
        final T service;
        try {
            //通过反射获取实例
            Constructor<T> constructor = serviceClass.getConstructor(Context.class);
            service = constructor.newInstance(mContext);
        } catch (InstantiationException ex) {
        }
        //调用同名重载方法
        startService(service);
        return service;
    } 
    ....................
}
public void startService(@NonNull final SystemService service) {
    // 加入到mServices列表中
    mServices.add(service);
    long time = SystemClock.elapsedRealtime();
    try {
        //调用onStart()开始服务
        service.onStart();
    } catch (RuntimeException ex) {
    }
}

执行到这里,DIC的启动就开始了,再来看看最后一个生命周期方法onBootPhase()的调用,这个方法表示启动服务的过程,在SystemServer中会调用多次,从而在不同的启动阶段完成不同的工作,代码如下:

private void startOtherServices() {
    .............
    mSystemServiceManager.startBootPhase(SystemService.
             PHASE_LOCK_SETTINGS_READY);
    .............
    mSystemServiceManager.startBootPhase(SystemService.
             PHASE_SYSTEM_SERVICES_READY);
    .............
mSystemServiceManager.startBootPhase(
        SystemService.PHASE_ACTIVITY_MANAGER_READY);
    .............
    mSystemServiceManager.startBootPhase(
        SystemService.PHASE_THIRD_PARTY_APPS_CAN_START);
    .............
}

在SystemServiceManager中的startBootPhase()方法中,遍历已启动的服务列表,调用onBootPhase():

public void startBootPhase(final int phase) {
    if (phase <= mCurrentPhase) {
        throw new IllegalArgumentException("Next phase must be larger than previous");
    }
    mCurrentPhase = phase;
    Slog.i(TAG, "Starting phase " + mCurrentPhase);
    try {
        for (int i = 0; i < serviceLen; i++) {
            final SystemService service = mServices.get(i);
            long time = SystemClock.elapsedRealtime();
            try {
                //调用每个service的onBootPhase()
                service.onBootPhase(mCurrentPhase);
            } catch (Exception ex) {
            }
        }
    } 
}

当这个方法执行完毕后,DIC的启动就完成了,下面我们看看DIC的生命周期方法中做了什么。首先看其构造方法:

public DeviceIdleController(Context context) {
    super(context);
    //创建可以执行原子操作的文件,/data/system/deviceidle.xml
    mConfigFile = new AtomicFile(new File(getSystemDir(), "deviceidle.xml"));
    //创建Handler,和BackgroundThread的Looper进行绑定
    mHandler = new MyHandler(BackgroundThread.getHandler().getLooper());
}

构造方法中,首先创建在/data/sytem下创建了deviceidle.xml文件,然后创建了一个Handler,并将BackgroundThread中Handler的Looper和该Handler进行绑定。BackgroundThread是一个单例模式实现的后台线程,可以用于任何一个进程。

再来看看DIC的onStart()方法:

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

    synchronized (this) {
        //Light doze模式和Deep Doze是否可用,默认不可用
        mLightEnabled = mDeepEnabled = getContext().getResources().getBoolean(
                com.android.internal.R.bool.config_enableAutoPowerModes);
        //获取系统全局配置信息,如权限
        SystemConfig sysConfig = SystemConfig.getInstance();
        //从配置文件中读取省电模式下的白名单列表且不在在idle状态的白名单列表,即列表中的app能够在省电模式下在后台运行,
        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);
                //添加到Map中,表示这些应用在省电模式下可后台运行,但在Doze下后台不可运行
                mPowerSaveWhitelistAppsExceptIdle.put(ai.packageName, appid);
                //添加到SpareArray中,表示这是处于powersave白名单但不处于doze白名单的系统应用                
                mPowerSaveWhitelistSystemAppIdsExceptIdle.put(appid, true);
            } catch (PackageManager.NameNotFoundException e) {
            }
        }
        //从配置文件中读取时能够在省电模式白名单列表,即包括不在doze白名单中的应用,也包括所有的白名单上的应用
        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);
                mPowerSaveWhitelistAppsExceptIdle.put(ai.packageName, appid);
                mPowerSaveWhitelistSystemAppIdsExceptIdle.put(appid, true);
                //添加到DIC中的白名单列表中
                mPowerSaveWhitelistApps.put(ai.packageName, appid);
                //添加到DIC中的系统应用白名单列表中
                mPowerSaveWhitelistSystemAppIds.put(appid, true);
            } catch (PackageManager.NameNotFoundException e) {
            }
        }
        //设置Contants内容观察者
        mConstants = new Constants(mHandler, getContext().getContentResolver());
        //读取/data/system/deviceidle.xml文件,将读取的app添加到表示用户设置的白名单中
        readConfigFileLocked();
        //更新白名单列表
        updateWhitelistAppIdsLocked();
        //网络是否连接
        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的初始值
        mLightState = LIGHT_STATE_ACTIVE;//设备保持活动状态,轻度Doze的初始值
        mInactiveTimeout = mConstants.INACTIVE_TIMEOUT;
    }
    mBinderService = new BinderService();
    //发布远程服务
    publishBinderService(Context.DEVICE_IDLE_CONTROLLER, mBinderService);
    //发布本地服务
    publishLocalService(LocalService.class, new LocalService());
}

在onStart()方法中,首先通过SystemConfig读取了两类白名单列表:在低电量模式下后台允许运行的应用的白名单、在低电量模式和Doze模式下都允许后台运行的应用白名单;
然后读取配置文件/data/system/deviceidle.xml,将读取的应用包名添加到mPowerSaveWhiteListUserApps这个Map中,表示用户添加到白名单中的应用。接下来更新白名单列表,然后给成员变量进行初始化。
最后,发布了远程服务和本地服务,从而可以和其他服务进行交互。发布后其他服务中就可以通过这种获取到DIC的BinderService从而进行交互:

IDeviceIdleController dic = IDeviceIdleController.Stub.asInterface(
        ServiceManager.getService(Context.DEVICE_IDLE_CONTROLLER));

获取本地服务通过如下方式:

mLocalDeviceIdleController
        = LocalServices.getService(DeviceIdleController.LocalService.class);

在onStart()方法中还调用了用来更新白名单列表的方法,来看下这个方法:


private void updateWhitelistAppIdsLocked() {
    //处于省电模式白名单但不处于Idle状态白名单的app
    mPowerSaveWhitelistExceptIdleAppIdArray = 
            buildAppIdArray(mPowerSaveWhitelistAppsExceptIdle,
            mPowerSaveWhitelistUserApps, mPowerSaveWhitelistExceptIdleAppIds);
    //处于所有的白名单的app,包括用户添加的
    mPowerSaveWhitelistAllAppIdArray = buildAppIdArray(mPowerSaveWhitelistApps,
            mPowerSaveWhitelistUserApps, mPowerSaveWhitelistAllAppIds);
    //用户添加的白名单列表应用
    mPowerSaveWhitelistUserAppIdArray = buildAppIdArray(null,
            mPowerSaveWhitelistUserApps, mPowerSaveWhitelistUserAppIds);
    if (mLocalActivityManager != null) {
        //将适用于所有情况的白名单列表通知给AMS
        mLocalActivityManager.setDeviceIdleWhitelist
               (mPowerSaveWhitelistAllAppIdArray);
    }
    if (mLocalPowerManager != null) {
        //将适用于所有情况的白名单列表通知给PMS
        mLocalPowerManager.setDeviceIdleWhitelist(
                   mPowerSaveWhitelistAllAppIdArray);
    }
    if (mLocalAlarmManager != null) {
        //将用户添加到白名单列表中的应用通知给AlarmManagerService
       mLocalAlarmManager.setDeviceIdleUserWhitelist
                    (mPowerSaveWhitelistUserAppIdArray);
    }
}

在这个方法中,会将三类白名单列表中的应用id添加到一个int数组中,该方法在添加应用到白名单列表、将应用移除白名单列表,都会调用该方法来更新白名单列表。

执行完onStart()方法后,就开始执行最后一个生命周期方法onBootPhase()方法了,该方法如下:


@Override
public void onBootPhase(int phase) {
    if (phase == PHASE_SYSTEM_SERVICES_READY) {
        synchronized (this) {
            mAlarmManager = (AlarmManager) getContext().getSystemService
                    (Context.ALARM_SERVICE);
            mBatteryStats = BatteryStatsService.getService();
            mLocalActivityManager = getLocalService(ActivityManagerInternal.class);
            mLocalPowerManager = getLocalService(PowerManagerInternal.class);
            mPowerManager = getContext().getSystemService(PowerManager.class);
            //申请一个wakelock锁保持CPU唤醒
            mActiveIdleWakeLock = mPowerManager.newWakeLock
                     (PowerManager.PARTIAL_WAKE_LOCK,"deviceidle_maint");
            //设置wakelock锁为非计数锁,只要执行一次release()就能释所有非计数锁
            mActiveIdleWakeLock.setReferenceCounted(false);
            mGoingIdleWakeLock = mPowerManager.newWakeLock
                   (PowerManager.PARTIAL_WAKE_LOCK,"deviceidle_going_idle");
            //设置wakelock锁为计数锁,一次申请对应一次释放
            mGoingIdleWakeLock.setReferenceCounted(true);
            mConnectivityService = (ConnectivityService)ServiceManager.getService(
                    Context.CONNECTIVITY_SERVICE);
            mLocalAlarmManager = getLocalService(AlarmManagerService.
                    LocalService.class);
            mNetworkPolicyManager = INetworkPolicyManager.Stub.asInterface(
                    ServiceManager.getService
                    (Context.NETWORK_POLICY_SERVICE));
            mSensorManager = (SensorManager)getContext().getSystemService
                   (Context.SENSOR_SERVICE);
            //可用于自动省电模式时的传感器id,0表示没有可用传感器
            int sigMotionSensorId = getContext().getResources().getInteger(
                    com.android.internal.R.integer.config_
                    autoPowerModeAnyMotionSensor);
            if (sigMotionSensorId > 0) {
                //根据传感器id获取传感器
                mMotionSensor = mSensorManager.getDefaultSensor
                     (sigMotionSensorId, true);
            }
            //如果没有指定任一传感器&&在没有指定传感器情况下首选WristTilt
            //传感器配置为true
            if (mMotionSensor == null && getContext().getResources().getBoolean(
                    com.android.internal.R.bool.config_
                    autoPowerModePreferWristTilt)) {
                //获取一个WristTilt传感器(手腕抖动)
                mMotionSensor = mSensorManager.getDefaultSensor(
                        Sensor.TYPE_WRIST_TILT_GESTURE, true);
            }
            if (mMotionSensor == null) {
                //如果以上条件都不满足,则获取一个SMD传感器
                mMotionSensor = mSensorManager.getDefaultSensor(
                        Sensor.TYPE_SIGNIFICANT_MOTION, true);
            }
            //是否在进入Doze模式时预先获取位置
            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;
            //用于检测设备是否已静止
            mAnyMotionDetector = new AnyMotionDetector(
                    (PowerManager) getContext().getSystemService
                    (Context.POWER_SERVICE),
                    mHandler, mSensorManager, this, angleThreshold);
            //用于Doze状态发生改变时发送广播
            mIdleIntent = new Intent(PowerManager.ACTION_DEVICE
                      _IDLE_MODE_CHANGED);
            mIdleIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
                    | Intent.FLAG_RECEIVER_FOREGROUND);
            //用于当轻度Doze状态发生改变时发送广播
            mLightIdleIntent = new Intent(PowerManager.ACTION_LIGHT_
                    DEVICE_IDLE_MODE_CHANGED);
            mLightIdleIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
                    | Intent.FLAG_RECEIVER_FOREGROUND);
            //注册监听电池状态改变的广播
            IntentFilter filter = new IntentFilter();
            filter.addAction(Intent.ACTION_BATTERY_CHANGED);
            getContext().registerReceiver(mReceiver, filter);
            //注册监听卸载应用的广播
            filter = new IntentFilter();
            filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
            filter.addDataScheme("package");
            getContext().registerReceiver(mReceiver, filter);
            //注册监听网络连接改变的广播
            filter = new IntentFilter();
            filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
            getContext().registerReceiver(mReceiver, filter);
            //注册监听亮灭屏的广播
            filter = new IntentFilter();
            filter.addAction(Intent.ACTION_SCREEN_OFF);
            filter.addAction(Intent.ACTION_SCREEN_ON);
            getContext().registerReceiver(mInteractivityReceiver, filter);
            //将适用于所有情况的白名单列表通知给AMS、PMS、AlarmManagerService
            mLocalActivityManager.setDeviceIdleWhitelist(
                    mPowerSaveWhitelistAllAppIdArray);
            mLocalPowerManager.setDeviceIdleWhitelist(
                    mPowerSaveWhitelistAllAppIdArray);
            mLocalAlarmManager.setDeviceIdleUserWhitelist(
                     mPowerSaveWhitelistUserAppIdArray)
            //更新交互状态
            updateInteractivityLocked();
        }
        //更新网络连接状态
        updateConnectivityState(null);
    } else if (phase == PHASE_BOOT_COMPLETED) {
    }
}

首先,只有在SystemServer中启动阶段为PHASE_SYSTEM_SERVICE_READY时,才会进入到onBootPhase()方法,也就是说,DIC的onBootPhase()方法在到达这一启动阶段时才执行。在onBootPhase()方法中,获取了一些系统服务管理类和用于System进程的本地服务,如AlarmManager、PowerManagerInternal等,用于和它们对应的系统服务进行交互;
其次获取一个Sensor,具体获取哪种类型的Sensor根据判断条件而定;接下来获取了一个AnyMotionDector对象,该对象用来检测设备是否处于禁止状态;然后注册了四种广播的监听,分别是:

  • 1.电源状态发生改变时的广播:Intent.ACTION_BATTERY_CHANGED,由BatteryService中发送。
  • 2.卸载应用时的广播:Intent.ACTION_PACKAGE_REMOVED,由PKMS发送;
  • 3.网络连接状态改变时的广播:ConnectivityManager.CONNECTIVITY_ACTION
  • 4亮灭屏时的广播:Intent.SCREEN_ON/SCREEN_OFF,由PMS相关的Notifier发送;

最后,调用了一个用于更新交互状态的updateInteractivityLocked()方法和用于更新网络状态的updateConnectivityState()方法。先看看updateInteractivityLocked()方法:

void updateInteractivityLocked() {
    // The interactivity state from the power manager tells us whether the display is
    // in a state that we need to keep things running so they will update at a normal
    // frequency.
    //获取设备是否处于交互状态
    boolean screenOn = mPowerManager.isInteractive();
    if (DEBUG) Slog.d(TAG, "updateInteractivityLocked: screenOn=" + screenOn);
    //表示当前不处于交互状态且上次处于交互状态
    if (!screenOn && mScreenOn) {
        mScreenOn = false;
        if (!mForceIdle) {//是否强制进入Idle
            //进入Idle模式的入口方法
            becomeInactiveIfAppropriateLocked();
        }
    } else if (screenOn) {
        mScreenOn = true;
        if (!mForceIdle) {
            //退出Idle模式
            becomeActiveLocked("screen", Process.myUid());
        }
    }
}

这个方法在Android 8.0及以前,是updateDisplayLocked(),在8.1中,修改了名字。这个方法中通过设备当前的交互状态,决定是否进入Doze模式或者退出Doze模式,如果当前处于不交互状态(PMS中分析过,wakefulness=asleep或者wakefulness=doze),且上次处于交互状态,则会调用becomeInteractiveIfAppropriateLocked()方法,开始准备进入Doze的工作;如果条件相反,调用becomeActiveLocked(),做退出Doze模式Idle状态的工作。这两个方法在下面的内容中进行分析。

分析到这里,DIC的启动流程就完毕了。启动流程图及主要工作如下所示:
Android 8.1 Doze模式分析(一)——Doze简介和DeviceIdleController的启动_第2张图片

下篇文章中将分析Light Doze模式的实现流程。

你可能感兴趣的:(Android系统开发)