Android doze模式分析一

本文详细剖析了Android系统从6.0版本开始引入的DOZE模式,该模式旨在优化低电量和应用待机状态。文章通过源码分析,解释了DOZE模式的工作原理,包括如何监控设备动作、位置变化,以及如何决定设备进入或退出空闲状态。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、引言

DOZE是安卓系统从6.0(API级别23)开始引入的对低电和应用待机模式的优化。具体介绍可以参考developers官方文档。

阅读完官文后,思考几个问题:

  1. 6.0之前,APP怎么处理可以让系统不休眠?
  2. 6.0开始,系统如何解决APP不让系统休眠的问题?

二、问题简答

6.0之前,如果不希望APP运行过程系统休眠,可以通过在APP内申请PowerManager.WakeLock保证不会被CPU挂起。这么做的好处是保持应用的正常运行,缺点是没有考虑到设备处于低电状态的时候对于节电的需求可能会更加强烈。

6.0开始,设计了DOZE机制,主要思想是通过设置超时定时器、监测设备是否长期没发生动作、监控是否接收到GPS位置信息变化来决定要不要让设备进入空闲状态,当进入空闲状态的时候,所有APP申请的WakeLock锁将被设置无效。但是DOZE的意思是打盹,不是死亡,就是说设备进入空闲状态之后,APP不是一直被CPU挂起不执行指令,而是定时会退出IDLE状态让APP正常使用CPU和网络。

三、源码分析

下面我们通过源码学习一下DOZE的实现思路。

源码地址:/frameworks/base/services/core/java/com/android/server/DeviceIdleController.java

顾名思义,设备空闲控制器,用来控制设备进入退出空闲状态的管理者。首先查看方法 stepIdleStateLocked,意思是跳进空闲状态。被触发的地方有:

  1. mDeepAlarmListener:通过AlarmManager.setIdleUntil设置监听。原因是:“s:alarm”,表示由于alarm触发。
  2. onAnyMotionResult,原因是”s:stationary“,表示长期没有动作发生。
  3. exitMaintenanceEarlyIfNeededLocked,原因是:“s:early”
  4. receivedGenericLocationLocked,原因是"s:location"
  5. receivedGpsLocationLocked,原因是"s:gps"
  6. onShellCommand,原因是"s:shell",通过adb shell命令触发
 void stepIdleStateLocked(String reason) {
        if (DEBUG) Slog.d(TAG, "stepIdleStateLocked: mState=" + mState);
        EventLogTags.writeDeviceIdleStep();
		//计算当前系统时间
        final long now = SystemClock.elapsedRealtime();
        //MIN_TIME_TO_ALARM: !COMPRESS_TIME ? 60 * 60 * 1000L : 6 * 60 * 1000L
        //mAlarmManager.getNextWakeFromIdleTime()计算下一次唤醒的时间
        if ((now+mConstants.MIN_TIME_TO_ALARM) > mAlarmManager.getNextWakeFromIdleTime()) {
            // Whoops, there is an upcoming alarm.  We don't actually want to go idle.
            //如果在计划休眠时刻之前有alarm到来,那么不会启动休眠策略。因此,如果当前mState是ACTIVE,
            //则直接返回;否则,
            if (mState != STATE_ACTIVE) {
                becomeActiveLocked("alarm", Process.myUid());
                becomeInactiveIfAppropriateLocked();
            }
            return;
        }
		//mState 在服务初始化的时候是STATE_ACTIVE:0,但是switch里面的选项没有STATE_ACTIVE,那么是在		  //哪里改变mState?看下文的分析
        switch (mState) {
           ...
        }
    }

mState 在服务初始化的时候是STATE_ACTIVE,但是switch里面的选项没有STATE_ACTIVE,那么是在哪里改变mState?查询调用发现是在becomeInactiveIfAppropriateLocked里面设置,代码如下:

//从服务初始化状态Activie变成InActive
void becomeInactiveIfAppropriateLocked() {
        if (DEBUG) Slog.d(TAG, "becomeInactiveIfAppropriateLocked()");
        //两个可能的条件:1.屏幕熄灭 且 没充电 2.人为强制设置
        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");
            }
            if (mLightState == LIGHT_STATE_ACTIVE && mLightEnabled) {
                mLightState = LIGHT_STATE_INACTIVE;
                if (DEBUG) Slog.d(TAG, "Moved from LIGHT_STATE_ACTIVE to LIGHT_STATE_INACTIVE");
                resetLightIdleManagementLocked();
                scheduleLightAlarmLocked(mConstants.LIGHT_IDLE_AFTER_INACTIVE_TIMEOUT);
                EventLogTags.writeDeviceIdleLight(mLightState, "no activity");
            }
        }
    

becomeInactiveIfAppropriateLocked的意思是如果条件合适,则进入INACTIVE状态。检测条件包括:

  • mScreenOn屏幕是否熄灭
  • mCharging设备是否在充电
  • mForceIdle是否人为强制设置

所以当

  • 屏幕屏幕熄灭且没充电
  • 人为强制设置INACTIVE

两个条件中一个满足,那么再检测当前mState是否为ACTIVE,如果成立则将mState设置为INACTIVE。

那么,becomeInactiveIfAppropriateLocked在什么条件下会触发呢?

  1. mSensingTimeoutAlarmListener,通过mAlarmManager.set设置
  2. updateDisplayLocked,在mDisplayListener的onDisplayChanged方法里面调用
  3. updateChargingLocked,在mReceiver注册的Intent.ACTION_BATTERY_CHANGED里面调用
  4. handleMotionDetectedLocked,检测是否有运动,如果发生运动,且此时设备没处于ACTIVE状态,那么将mState设置为ACTIVE状态,并设置INACTIVE的超时时间,然后等待下一次没有MOTION发生进入IDLE状态。
  5. onShellCommand,通过shell命令强制进入
  6. stepIdleStateLocked,这里回到上文,由于超时时间到来之前会有alarm到来,说明不会进入IDLE,因此这里会先进入ACTIVE状态,然后调用becomeInactiveIfAppropriateLocked检测是否满足进入INACTIVE的条件。

根据上面分析,我们可以推导一个设备从ACTIVE进入IDLE的完整过程:

  1. 假设一个设备开机之后,mState初始状态为ACTIVE。

  2. 按下电源按键熄灭屏幕,此时接收到onDisplayChanged,调用updateDisplayLocked,最终调用becomeInactiveIfAppropriateLocked,由于设备没有处于充电状态,因此会进入INACTIVE状态。

  3. 当长期没有动作响应,会收到onAnyMotionResult,此时会进入文章最初提到的stepIdleStateLocked(“s:stationary”)。

     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.
        startMonitoringMotionLocked();
        scheduleAlarmLocked(mConstants.IDLE_AFTER_INACTIVE_TIMEOUT, false);
        // Reset the upcoming idle delays.
        mNextIdlePendingDelay = mConstants.IDLE_PENDING_TIMEOUT;
        mNextIdleDelay = mConstants.IDLE_TIMEOUT;
        mState = STATE_IDLE_PENDING;
        if (DEBUG) Slog.d(TAG, "Moved from STATE_INACTIVE to STATE_IDLE_PENDING.");
        EventLogTags.writeDeviceIdle(mState, reason);
        break;
    
    

    首先开启对运动的监测,然后设置alarm超时计时器,最后将mState设置为STATE_IDLE_PENDING,意思是即将进入IDLE状态

  4. 当mDeepAlarmListener超时到点了,那么会调用stepIdleStateLocked(“s:alarm”)

    case STATE_IDLE_PENDING:
        mState = STATE_SENSING;
        if (DEBUG) Slog.d(TAG, "Moved from STATE_IDLE_PENDING to STATE_SENSING.");
        EventLogTags.writeDeviceIdle(mState, reason);
        scheduleSensingTimeoutAlarmLocked(mConstants.SENSING_TIMEOUT);
        cancelLocatingLocked();
        mNotMoving = false;
        mLocated = false;
        mLastGenericLocation = null;
        mLastGpsLocation = null;
        mAnyMotionDetector.checkForAnyMotion();
        break;
    
    

    由于此时mState为STATE_IDLE_PENDING,所以会将mState设置为STATE_SENSING,然后通过scheduleSensingTimeoutAlarmLocked设置一个超时计时器。

  5. 当mDeepAlarmListener超时到点了,那么会调用stepIdleStateLocked(“s:alarm”)

    			case STATE_SENSING:
                    cancelSensingTimeoutAlarmLocked();
                    mState = STATE_LOCATING;
                    if (DEBUG) Slog.d(TAG, "Moved from STATE_SENSING to STATE_LOCATING.");
                    EventLogTags.writeDeviceIdle(mState, reason);
                    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;
                    }
                    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) {
                        break;
                    }
    
                    // Otherwise, we have to move from locating into idle maintenance.
                case STATE_LOCATING:
                    cancelAlarmLocked();
                    cancelLocatingLocked();
                    mAnyMotionDetector.stop();
    
                case STATE_IDLE_MAINTENANCE:
                    scheduleAlarmLocked(mNextIdleDelay, true);
                    if (DEBUG) Slog.d(TAG, "Moved to STATE_IDLE. Next alarm in " + mNextIdleDelay +
                            " ms.");
                    mNextIdleDelay = (long)(mNextIdleDelay * mConstants.IDLE_FACTOR);
                    if (DEBUG) Slog.d(TAG, "Setting mNextIdleDelay = " + mNextIdleDelay);
                    mNextIdleDelay = Math.min(mNextIdleDelay, mConstants.MAX_IDLE_TIMEOUT);
                    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);
                    mGoingIdleWakeLock.acquire();
                    mHandler.sendEmptyMessage(MSG_REPORT_IDLE_ON);
                    break;
    

    由于此时mState为STATE_SENSING,可以看到只有当mLocating为true的时候才会break,否则会执行下一个break之前的代码块,可以看到会将mState 设置为STATE_IDLE。至此,设备进入IDLE状态,然后通过handler发送MSG_REPORT_IDLE_ON消息。下面来看看都做了什么动作:

    				case MSG_REPORT_IDLE_ON_LIGHT: {
                        // mGoingIdleWakeLock is held at this point
                        EventLogTags.writeDeviceIdleOnStart();
                        final boolean deepChanged;
                        final boolean lightChanged;
                        if (msg.what == MSG_REPORT_IDLE_ON) {
                        	//更改WAKELOCK锁的状态
                            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();
                        mGoingIdleWakeLock.release();
                    } break;
    
    • mLocalPowerManager.setDeviceIdleMode(true),最终实现在PowerManagerService
    //PowerManagerService.java
    private void updateWakeLockDisabledStatesLocked() {
            boolean changed = false;
            final int numWakeLocks = mWakeLocks.size();
            for (int i = 0; i < numWakeLocks; i++) {
                final WakeLock wakeLock = mWakeLocks.get(i);
                if ((wakeLock.mFlags & PowerManager.WAKE_LOCK_LEVEL_MASK)
                        == PowerManager.PARTIAL_WAKE_LOCK) {
                    //更新系统内所有APP申请的WAKELOCK状态,如果是设备处于IDLE状态
                    //则把WAKELOCK禁止,否则取消禁止
                    if (setWakeLockDisabledStateLocked(wakeLock)) {
                        changed = true;
                        if (wakeLock.mDisabled) {
                            // This wake lock is no longer being respected.
                            notifyWakeLockReleasedLocked(wakeLock);
                        } else {
                            notifyWakeLockAcquiredLocked(wakeLock);
                        }
                    }
                }
            }
            if (changed) {
                mDirty |= DIRTY_WAKE_LOCKS;
                updatePowerStateLocked();
            }
        }
    
    

    我们知道在doze模式出现之前,我们可以通过在应用端申请wakelock锁来避免系统进入休眠。这里发现doze的最核心的地方正是通过释放系统中所有被申请的wakelock锁来达到系统休眠的目的。

    • mNetworkPolicyManager.setDeviceIdleMode(true)
     public void setDeviceIdleMode(boolean enabled) {
            mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG);
            Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "setDeviceIdleMode");
            try {
                synchronized (mUidRulesFirstLock) {
                    if (mDeviceIdleMode == enabled) {
                        return;
                    }
                    mDeviceIdleMode = enabled;
                    if (mSystemReady) {
                        // Device idle change means we need to rebuild rules for all
                        // known apps, so do a global refresh.
                        updateRulesForRestrictPowerUL();
                    }
                }
                if (enabled) {
                    EventLogTags.writeDeviceIdleOnPhase("net");
                } else {
                    EventLogTags.writeDeviceIdleOffPhase("net");
                }
            } finally {
                Trace.traceEnd(Trace.TRACE_TAG_NETWORK);
            }
        }
    

    针对所有app修改网络受限规则,具体规则可以继续跟踪updateRulesForRestrictPowerUL

    1. 步骤5设备已经开始进入了IDLE状态,但是DOZE的思想并不是让APP一直处于被限制的状态,而是定时让APP恢复常的使用状态。DOZE里面是怎么实现的呢?
    			case STATE_IDLE:
                    // We have been idling long enough, now it is time to do some work.
                    mActiveIdleOpCount = 1;
                    mActiveIdleWakeLock.acquire();
                    scheduleAlarmLocked(mNextIdlePendingDelay, false);
                    if (DEBUG) Slog.d(TAG, "Moved from STATE_IDLE to STATE_IDLE_MAINTENANCE. " +
                            "Next alarm in " + mNextIdlePendingDelay + " ms.");
                    mMaintenanceStartTime = SystemClock.elapsedRealtime();
                    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;
                    EventLogTags.writeDeviceIdle(mState, reason);
                    addEvent(EVENT_DEEP_MAINTENANCE);
                    //退出IDLE状态
                    mHandler.sendEmptyMessage(MSG_REPORT_IDLE_OFF);
                    break;
    

    步骤5中设置的定时器到点后,走到stepIdleStateLocked,此时mState等于STATE_IDLE,此时会将mState设置为STATE_IDLE_MAINTENANCE,同时发送消息MSG_REPORT_IDLE_OFF,表示设备临时退出IDLE状态。与

MSG_REPORT_IDLE_ON刚好相反,MSG_REPORT_IDLE_OFF执行的动作是:

  • 将系统中的WakeLock锁的状态还原到IDLE之前的状态

     private boolean setWakeLockDisabledStateLocked(WakeLock wakeLock) {
            if ((wakeLock.mFlags & PowerManager.WAKE_LOCK_LEVEL_MASK)
                    == PowerManager.PARTIAL_WAKE_LOCK) {
                boolean disabled = false;
                //如果处于IDLE状态,将白名单之外的应用申请的WAKELOCK的DISABLE设置为TRUE
                if (mDeviceIdleMode) {
                    final int appid = UserHandle.getAppId(wakeLock.mOwnerUid);
                    // If we are in idle mode, we will ignore all partial wake locks that are
                    // for application uids that are not whitelisted.
                    if (appid >= Process.FIRST_APPLICATION_UID &&
                            Arrays.binarySearch(mDeviceIdleWhitelist, appid) < 0 &&
                            Arrays.binarySearch(mDeviceIdleTempWhitelist, appid) < 0 &&
                            mUidState.get(wakeLock.mOwnerUid,
                                    ActivityManager.PROCESS_STATE_CACHED_EMPTY)
                                    > ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) {
                        disabled = true;
                    }
                }
                //更新WAKELOCK锁的DISABLE状态:1.如果是idle状态,会统一改为true2.否则统一改为默认的			//false
                if (wakeLock.mDisabled != disabled) {
                    wakeLock.mDisabled = disabled;
                    return true;
                }
            }
            return false;
        }
    
    

  • 修改网络使用规则,还原到IDLE之前的使用状态

四、总结

DOZE模式核心思想是通过设置定时器,监控设备是否发生动作,位置是否变化来决定要不要让设备进入IDLE状态。上文对实现源码做了简单的分析,里面还有一些细节没有说明,比如:

  • 如果实现的监控设备长时间没有动作
  • 监听GPS位置是做什么用的
  • IDLE状态的网络受限规则具体如何
  • IDLE状态下除了APP CPU挂起,网络受限,系统中还有哪些资源受限
  • mGoingIdleWakeLock是用来做什么的

先留个疑问吧,后面接着一起学习

五 参考文档

  1. Android 官方学习文档

  2. Android源码查看网站XREF

Android 11 中的触摸唤醒功能是通过 "Ambient Display" 实现的。当设备处于待机状态时,用户触摸屏幕时,Ambient Display 会将设备的屏幕亮起来,显示些基本的信息,例如时间、日期、未读消息等。 具体实现源码如下: 1. 在 Android 11 中,Ambient Display 的代码位于 frameworks/base/packages/SystemUI/src/com/android/systemui/doze 中。其中,DozeService 和 DozeScreenState 实现了 Ambient Display 的核心逻辑。 2. DozeService 是个 Service,它会在设备进入待机模式时启动。在启动时,它会注册些监听器,以便在设备唤醒时触发相应的操作。 3. DozeScreenState 是个接口,它定义了 Ambient Display 的状态和行为。具体实现位于 frameworks/base/packages/SystemUI/src/com/android/systemui/doze/state 中。其中,DozeScreenStateMachine 实现了 DozeScreenState 的状态机。 4. DozeScreenStateMachine 定义了 Ambient Display 的状态转换和事件处理逻辑。例如,当用户触摸屏幕时,它会触发 SCREEN_WAKE_UP事件,然后根据当前状态和事件类型执行相应的操作。 5. 在 frameworks/base/packages/SystemUI/src/com/android/systemui/doze/DozeHardwareComposer.java 中,定义了如何显示 Ambient Display 的内容。具体来说,它会创建个 SurfaceView,并将其添加到 WindowManager 中,然后在 SurfaceView 上绘制需要显示的内容。 总体来说,Android 11 的触摸唤醒功能是通过 Ambient Display 实现的。Ambient Display 的核心逻辑位于 DozeService 和 DozeScreenState 中,具体的状态转换和事件处理逻辑则由 DozeScreenStateMachine 完成。同时,DozeHardwareComposer 定义了如何显示 Ambient Display 的内容。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值