Android6.0中Doze模式实现原理的源码分析

Android 6.0引入的Doze模式旨在降低设备能耗,当用户长时间未使用手机时,系统限制APP的网络访问、CPU活动及服务,仅在维护窗口(maintenance window)允许执行延迟任务。该模式通过暂停网络、忽略唤醒锁、延迟alarms等方式保护电池。在特定情况下,如setAlarmClock()触发时,系统会短暂退出Doze模式。分析发现,DeviceIdleController是驱动Doze模式的关键,负责在系统启动时初始化并管理Idle状态。
摘要由CSDN通过智能技术生成

Doze模式是Android6.0上新出的一种模式,是一种全新的、低能耗的状态,在后台只有部分任务允许运行,其他都被强制停止。当用户一段时间内没有使用手机时,Doze模式通过延缓APP后台的CPU和网络活动来减少电量的消耗。

如果用户断开了充电连接,灭屏不动手机一段时间后会进入Doze模式。在Doze模式中系统尝试去通过减少应用的网络访问和CPU敏感的服务来保护电池。它也阻止应用访问网络、延迟应用的任务、同步和alarm。

系统定期退出Doze模式(maintenance window)去让app完成它们被延迟的动作,在maintenance window期间,系统运行所有挂起的任务、同步、alarm以及访问网络。如下图所示:


Doze模式的限制:

1.网络访问被暂停

2.忽略wakelock

3.alarm被延迟到下一个maintenance window执行

4.如果需要在Doze状态下启动设置的alarm,可以使用setAndAllowWhileIdle()或者setExactAndAllowWhileIdle()

5.当有setAlarmClock()的alarm启动时,系统会短暂退出Doze模式

6.系统不会扫描WiFi热点

7.系统不允许sync adapter运行

8.系统不允许JobScheduler运行

Doze模式在系统中主要是DeviceIdleController类来驱动,下面来分析下它:

DeviceIdleController的启动和初始化和之前分析的AlarmManagerService、JobSchedulerService等一样也是在SystemServer的startOtherServices()方法中启动的:

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

SystemServiceManager类的startService方法之前已经分析过,这里贴下代码:

private final ArrayList<SystemService> mServices = new ArrayList<SystemService>();  
  
/** 
 * 创建并启动一个继承自SystemService类的系统服务。 
 * 
 * @param 一个继承自SystemService类的服务类  
 * @return 服务类的实例 
 * @throws 如果服务启动失败则抛RuntimeException异常 
 */  
@SuppressWarnings("unchecked")  
public <T extends SystemService> T startService(Class<T> serviceClass) {  
    // 获取服务类的类名  
    final String name = serviceClass.getName();  
    Slog.i(TAG, "Starting " + name);  
  
    // 判断服务类是否是SystemService的子类  
    if (!SystemService.class.isAssignableFrom(serviceClass)) {  
        throw new RuntimeException("Failed to create " + name  
                + ": service must extend " + SystemService.class.getName());  
    }  
    final T service;  
    try {  
        // 获取服务类包含一个Context参数的构造方法  
        Constructor<T> constructor = serviceClass.getConstructor(Context.class);  
        // 创建这个服务类的实例  
        service = constructor.newInstance(mContext);  
    } catch (InstantiationException ex) {  
        throw new RuntimeException("Failed to create service " + name  
                + ": service could not be instantiated", ex);  
    } catch (IllegalAccessException ex) {  
        throw new RuntimeException("Failed to create service " + name  
                + ": service must have a public constructor with a Context argument", ex);  
    } catch (NoSuchMethodException ex) {  
        throw new RuntimeException("Failed to create service " + name  
                + ": service must have a public constructor with a Context argument", ex);  
    } catch (InvocationTargetException ex) {  
        throw new RuntimeException("Failed to create service " + name  
                + ": service constructor threw an exception", ex);  
    }  
  
    // 把服务添加到mServices列表中,方便后续使用时取出  
    mServices.add(service);  
  
    try {  
        // 回调服务的onStart方法  
        service.onStart();  
    } catch (RuntimeException ex) {  
        throw new RuntimeException("Failed to start service " + name  
                + ": onStart threw an exception", ex);  
    }  
    return service;  
}

DeviceIdleController类的构造方法:

public final AtomicFile mConfigFile;
final MyHandler mHandler;

public DeviceIdleController(Context context) {
    super(context);
	// 创建原子操作文件deviceidle.xml
    mConfigFile = new AtomicFile(new File(getSystemDir(), "deviceidle.xml"));
	// 创建处理消息的handler,BackgroundThread是HandlerThread的子类
    mHandler = new MyHandler(BackgroundThread.getHandler().getLooper());
}

// 获取data/system目录
private static File getSystemDir() {
    return new File(Environment.getDataDirectory(), "system");
}

mHandler处理消息的代码后面再分析,先看onStart()方法:

/**
 * 省电模式下能在后台运行的白名单,idle模式除外
 */
private final ArrayMap<String, Integer> mPowerSaveWhitelistAppsExceptIdle = new ArrayMap<>();

/**
 * 所有省电模式下的白名单
 */
private final ArrayMap<String, Integer> mPowerSaveWhitelistApps = new ArrayMap<>();

/**
 * APPID是否在idle模式外的白名单中
 */
private final SparseBooleanArray mPowerSaveWhitelistSystemAppIdsExceptIdle
        = new SparseBooleanArray();

/**
 * APPID是否在全模式的白名单中
 */
private final SparseBooleanArray mPowerSaveWhitelistSystemAppIds = new SparseBooleanArray();

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

    synchronized (this) {
        // 获取Doze模式是否开启的默认值,该参数在frameworks/base/core/res/res/values/config.xml文件中设置,默认值为false
        mEnabled = getContext().getResources().getBoolean(
                com.android.internal.R.bool.config_enableAutoPowerModes);
        /// M: Config Doze and App Standby { MTK做的修改,可以直接修改这里的默认值来打开或关闭Doze模式
        if (SystemProperties.get(CONFIG_AUTO_POWER, "0").equals("-1")) {
            mEnabled = false;
        } else if (SystemProperties.get(CONFIG_AUTO_POWER, "0").equals("1")) {
            mEnabled = true;
        }
        /// Config Doze and App Standby }
        // 从SystemConfig中获取默认系统应用白名单,该类之前在分析apk的安装过程时分析过,
        // 主要是解析system/etc/sysconfig、system/etc/permissions目录下的各xml文件
        SystemConfig sysConfig = SystemConfig.getInstance();
        // 从系统配置文件中读取省电模式下能在后台运行的白名单(除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, 0);
                if ((ai.flags&ApplicationInfo.FLAG_SYSTEM) != 0) {
                    int appid = UserHandle.getAppId(ai.uid);
                    mPowerSaveWhitelistAppsExceptIdle.put(ai.packageName, appid);
                    mPowerSaveWhitelistSystemAppIdsExceptIdle.put(appid, true);
                }
            } catch (PackageManager.NameNotFoundException e) {
            }
        }
        // 从系统配置文件中读取省电模式下能在后台运行的白名单,包括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, 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);
                }
            } catch (PackageManager.NameNotFoundException e) {
            }
        }

        // 初始化常量类,后面有详细分析
        mConstants = new Constants(mHandler, getContext().getContentResolver());

        // 读取deviceidle.xml文件,解析该文件并将用户应用的白名单保存在map中
        readConfigFileLocked();
        // 将系统应用白名单和用户应用白名单合并
        updateWhitelistAppIdsLocked();

        // 初始化变量:屏幕为开启、充电状态为true、Doze模式为ACTIVE状态等
        mScreenOn = true;
        mCharging = true;
        mState = STATE_ACTIVE;
        mInactiveTimeout = mConstants.INACTIVE_TIMEOUT;
    }

    // 发布服务到ServiceManager和LocalService中
    publishBinderService(Context.DEVICE_IDLE_CONTROLLER, new BinderService());
    // LocalService后续用到时再分析
    publishLocalService(LocalService.class, new LocalService());
}

onStart()方法中的初始化常量类操作:

/**
 * 该类中所有的时间单位都是ms,这些常量跟settings数据库中的值保持一致。所有访问该常量类的类或方法都
 * 必须持有DeviceIdleController对象锁
 */
private final class Constants extends ContentObserver {
    // Key names stored in the settings value.
    private static final String KEY_INACTIVE_TIMEOUT = "inactive_to";
    private static final String KEY_SENSING_TIMEOUT = "sensing_to";
    private static final String KEY_LOCATING_TIMEOUT = "locating_to";
    private static final String KEY_LOCATION_ACCURACY = "location_accuracy";
    private static final String KEY_MOTION_INACTIVE_TIMEOUT = "motion_inactive_to";
    private static final String KEY_IDLE_AFTER_INACTIVE_TIMEOUT = "idle_after_inactive_to";
    private static final String KEY_IDLE_PENDING_TIMEOUT = "idle_pending_to";
    private static final String KEY_MAX_IDLE_PENDING_TIMEOUT = "max_idle_pending_to";
    private static final String KEY_IDLE_PENDING_FACTOR = "idle_pending_factor";
    private static final String KEY_IDLE_TIMEOUT = "idle_to";
    private static final String KEY_MAX_IDLE_TIMEOUT = "max_idle_to";
    private static final String KEY_IDLE_FACTOR = "idle_factor";
    private static final String KEY_MIN_TIME_TO_ALARM = "min_time_to_alarm";
    private static final String KEY_MAX_TEMP_APP_WHITELIST_DURATION =
            "max_temp_app_whitelist_duration";
    private static final String KEY_MMS_TEMP_APP_WHITELIST_DURATION =
            "mms_temp_app_whitelist_duration";
    private static final String KEY_SMS_TEMP_APP_WHITELIST_DURATION =
            "sms_temp_app_whitelist_duration";

    /**
     * 切换到inactive状态后,INACTIVE_TIMEOUT时间后开始检测运动传感器以确定设备是否是静止状态。
     * 因为在屏幕关闭时切换到inactive状态后我们不想连续运行重要的运动传感器
     */
    public long INACTIVE_TIMEOUT;

    /**
     * Sensing状态的超时时间
     */
    public long SENSING_TIMEOUT;

    /**
     * 进入空闲模式之前等待尝试修复位置信息的时间
     */
    public long LOCATING_TIMEOUT;

    /**
     * 满足位置信息最大精度(单位:米)的要求以确保能够进入Idle模式。
     * 会尝试修复位置信息或直到LOCATING_TIMEOUT时间到期
     */
    public float LOCATION_ACCURACY;

    /**
     * 操作手机后,从切换到inactive状态后,下次检测操作手机需要等待的时间
     */
    public long MOTION_INACTIVE_TIMEOUT;

    /**
     * 在inactive超时之后,等待监测重大操作的时间,以便让手机进入Idle状态
     */
    public long IDLE_AFTER_INACTIVE_TIMEOUT;

    /**
     * 切换到Idle状态时的初始化时间,此时允许回到IDLE_PENDING(空闲延迟)状态允许系统正常运行直到回到Idle状态
     */
    public long IDLE_PENDING_TIMEOUT;

    /**
     *进入到Idle模式后,系统能正常运行的最大时间段
     */
    public long MAX_IDLE_PENDING_TIMEOUT;

    /**
     * 空闲延迟缩放因子
     */
    public float IDLE_PENDING_FACTOR;

    /**
     * 在再次唤醒手机以便回到待机空闲并允许正常工作运行之前,想要处于空闲状态的时间
     * 即:空闲持续时间
     */
    public long IDLE_TIMEOUT;

    /**
     * 最大空闲持续时间
     */
    public long MAX_IDLE_TIMEOUT;

    /**
     * 空闲持续缩放因子
     */
    public float IDLE_FACTOR;

    /**
     * 进入空闲模式后,到下一次执行alarm的最小时间
     */
    public long MIN_TIME_TO_ALARM;

    /**
     * 当一个应用接收到高优先级事件时,将其加入到临时白名单的最长时间
     */
    public long MAX_TEMP_APP_WHITELIST_DURATION;

    /**
     * 将正在接收彩信的应用加入临时白名单的时间
     */
    public long MMS_TEMP_APP_WHITELIST_DURATION;

    /**
     * 将正在接收短信的应用加入临时白名单的时间
     */
    public long SMS_TEMP_APP_WHITELIST_DURATION;

    private final ContentResolver mRe
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值