AOD的概念:
AOD,即A(lways) O(n) D(isplay)是android一种低功耗的显示模式的一种应用,他能保证屏幕某块区域一直亮,该应用开启时绘制的频率会低于正常的频率。由于AOD现实的不是和正常的亮屏之后显示的一样,只 会显示非黑色的部分,而且频率低于正常的频率 ,所以即使一直在显示功耗也比较低。
AOD进程的流程:
AOD是一个应用,主要是通过DreamManagerService来切换DOZE、DOZE_SUSPEND实现AOD的进入、退出和显示。 如图一 transitionTo开始进入到setDozeScreenState结束,而DreamService也就是DreamServiceManager的 代理,后面就开始到了DreamServiceManager里面来做了。
DreamManager和DreamManagerService
在DreamManager里面是通过updateDoze 从而调用到PowerManagerService里面的mSandman.startDozing方法来和DreamManagerService通讯的,此处调用逻辑简单看下源码就知道。
在DreamManagerService中,我们以进入DOZE状态为例,分析下调用流程。在进入DOZE的时候最重要的方法是
private void startDozingInternal(IBinder token, int screenState,
225 int screenBrightness) {
226 if (DEBUG) {
227 Slog.d(TAG, "Dream requested to start dozing: " + token
228 + ", screenState=" + screenState
229 + ", screenBrightness=" + screenBrightness);
230 }
231
232 synchronized (mLock) {
233 if (mCurrentDreamToken == token && mCurrentDreamCanDoze) {
234 mCurrentDreamDozeScreenState = screenState;
235 mCurrentDreamDozeScreenBrightness = screenBrightness;
236 mPowerManagerInternal.setDozeOverrideFromDreamManager(
237 screenState, screenBrightness);//设置亮度
238 if (!mCurrentDreamIsDozing) {
239 mCurrentDreamIsDozing = true;
240 mDozeWakeLock.acquire();//申请一个DozeWakeLock的锁
241 }
242 }
243 }
244 }
这个方法中其实最重要的就是设置一个亮度,申请一个锁。
先说亮度:这个亮度screenBrightness最终将通过PowerManagerService的mDozeScreenBrightnessOverrideFromDreamManager封装成一个request,给PowerManagerService和DisplayManagerServices交互的类DisplayPowerController
然后在recordBrightnessChange方法中通过binder直接给surface底层,从而把这个亮度给设置进去。
前面说过状态的切换最重要的是申请一个DozeWakeLock锁,这个锁定义如下:
mDozeWakeLock = mPowerManager.newWakeLock(PowerManager.DOZE_WAKE_LOCK, TAG);
这个锁对应的类型未 public static final int DOZE_WAKE_LOCK = OsProtoEnums.DOZE_WAKE_LOCK; // 0x00000040
也就是说申请了0x00000040的锁,熟悉PowerManagerServices的同学都知道,这个锁最终会和用户行为一起合成一个drity,在getDesiredScreenPolicyLocked方法里计算出一个最终的policy给DisplayPowerController,
最终在DisplayPowerState里面将状态设置成为对应的状态(在这里要注意的是brightness原生流程中也是在DisplayPowerState里面去设置的,但是因为我们将传统的亮度和doze的亮度两个节点在底层分开了,所以doze的亮度是通过
recordBrightnessChange,利用binder直接和底层通信,将亮度设置进去的,具体的J1/J2 AOD节点移植方案)。
我们看可以看下getDesiredScreenPolicyLocked里面DOZE在是如何影响影响policy的
2 int getDesiredScreenPolicyLocked() {
2703 。。。。。。
2707 if (mWakefulness == WAKEFULNESS_DOZING) {
2708 if ((mWakeLockSummary & WAKE_LOCK_DOZE) != 0) {
2709 return DisplayPowerRequest.POLICY_DOZE;//policy的状态为doze
2710 }
2711 。。。。
2737
2738 return DisplayPowerRequest.POLICY_DIM;
2739 }
如果当前状态为WAKEFULNESS_DOZING且mWakeLockSummary里面有WAKE_LOCK_DOZE时当前policy就是DOZE
private static final int WAKE_LOCK_DOZE = 1 << 6;
前面DOZE_WAKE_LOCK是十进制的64,WAKE_LOCK_DOZE换成10进制也是64,换句话说进入DOZE状态的途径之一就是申请DOZE_WAKE_LOCK锁。
既然是途径之一还有什么方法可以进入DOZE状态能,通过源码我们发现,如果当前是本来就是POLICY_DOZE且有WAKE_LOCK_DRAW锁,这个时候回将dozeScreenState变成DOZE状态
private boolean updateDisplayPowerStateLocked(int dirty) {
2592 。。。。。。
2626 if (mDisplayPowerRequest.policy == DisplayPowerRequest.POLICY_DOZE) {
2627 mDisplayPowerRequest.dozeScreenState = mDozeScreenStateOverrideFromDreamManager;
2628 if ((mWakeLockSummary & WAKE_LOCK_DRAW) != 0
2629 && !mDrawWakeLockOverrideFromSidekick) {
2630 if (mDisplayPowerRequest.dozeScreenState == Display.STATE_DOZE_SUSPEND) {
2631 mDisplayPowerRequest.dozeScreenState = Display.STATE_DOZE;//display为doze状态
2632 }
2633 if (mDisplayPowerRequest.dozeScreenState == Display.STATE_ON_SUSPEND) {
2634 mDisplayPowerRequest.dozeScreenState = Display.STATE_ON;
2635 }
2636 }
2637 。。。。。。。。。。。。
2665 return mDisplayReady && !oldDisplayReady;
2666 }
要通过dozeScreenState来进入DOZE,在源码中还有一种情况就是dozeScreenState为STATE_UNKNOWN状态,这个时候会用dozeScreenState的状态,但这种情况进入DOZE的会比较少。
private void updatePowerState() {
780 。。。。。
831 case DisplayPowerRequest.POLICY_DOZE:
832 if (mPowerRequest.dozeScreenState != Display.STATE_UNKNOWN) {
833 state = mPowerRequest.dozeScreenState;
834 } else {
835 state = Display.STATE_DOZE;
836 }
837 if (!mAllowAutoBrightnessWhileDozingConfig) {
838 brightness = mPowerRequest.dozeScreenBrightness;
839 mBrightnessReasonTemp.setReason(BrightnessReason.REASON_DOZE);
840 }
841 break;
。。。。。。
842 }
那如何切成DOZE_SUSPEND的呢,对应用(如指纹)会使用AOD暴露给他们的接口fireAodState,调到AOD的流程里,而AOD那边也是通过setDozeScreenState方法切成DOZE_SUSPEND同时应用(如指纹)释放DOZE_WAKE_LOCK锁的时候,CPU会
挂起,再通过DreamManagerService将mDozeScreenBrightnessOverrideFromDreamManager然后进入DOZE_SUSPEND状态,这里就不贴代码了,对应的log如下,
01-17 21:47:17.348 29499 29514 D PowerManagerService: acquireWakeLockInternal: lock=158204803, flags=0x80, tag="gxzw", ws=null, uid=1000, pid=29873 // 申请01-17 21:47:17.356 29499 29809 I LocalDisplayAdapter: setDisplayState(id=0, state=DOZE) // DOZE_SUSPEND -> DOZE01-17 21:47:17.665 29499 30650 D PowerManagerService: releaseWakeLockInternal: lock=158204803 [gxzw], flags=0x10000 // 释放01-17 21:47:17.672 29499 29809 I LocalDisplayAdapter: setDisplayState(id=0, state=DOZE_SUSPEND) // DOZE -> DOZE_SUSPEND01-17 21:47:18.882 29499 29514 D PowerManagerService: acquireWakeLockInternal: lock=206718903, flags=0x80, tag="Window:AOD", ws=WorkSource{1000 com.miui.aod}, uid=1000, pid=2949901-17 21:47:18.899 29499 29809 I LocalDisplayAdapter: setDisplayState(id=0, state=DOZE)01-17 21:47:19.019 29499 29499 D PowerManagerService: releaseWakeLockInternal: lock=206718903 [Window:AOD], flags=0x1000001-17 21:47:19.023 29499 29809 I LocalDisplayAdapter: setDisplayState(id=0, state=DOZE_SUSPEND)01-17 21:47:22.412 29499 30650 D PowerManagerService: acquireWakeLockInternal: lock=258767652, flags=0x80, tag="Window:gxzw_anim", ws=WorkSource{1000 com.android.systemui}, uid=1000, pid=2949901-17 21:47:22.416 29499 29809 I LocalDisplayAdapter: setDisplayState(id=0, state=DOZE)01-17 21:47:22.423 29499 30103 D PowerManagerService: acquireWakeLockInternal: lock=158204803, flags=0x80, tag="gxzw", ws=null, uid=1000, pid=2987301-17 21:47:22.749 29499 29809 I LocalDisplayAdapter: setDisplayState(id=0, state=DOZE_SUSPEND)
关于DOZE,DOZE_SUSPEND的切换还有另外一种情况就是在LocalDisplayAdapter.java中,如果两次发下来的都是DOZE_SUSPEND,这个时候会在requestDisplayStateLocked方法里面切成DOZE,这也是很多时候把指纹等一些应用关掉之后,没有任何进程在上层切换,也会来回切的原因,这是Google原生的逻辑,最好还是别在这里动刀,这样设计的目的估计也是因为下两次联系的DOZE_SUSPEND的可能性非常小,下了两次就肯定是要做其他的事情(如修改亮度),这个时候就不能在DOZE_SUSPEND下修改了。
分析方法
在AOD的分析过程中,更多的是底层发现问题,fw层定位问题,给出解决方案,上层修改问题。
总的来说就是找到根本原因,上层能改就尽量上层改。
对于问题首先要确定上层的状态指令已经下到fw层,也就是先确定有类似的08-15 23:54:38.412 3482 3482 W DozeScreenState: applyScreenState: setDozeScreenState 4,如果没有则可以先让AOD排查下逻辑是否正常;
如果有指令下发,则需要排查是否底层和上层应用的一些状态没有对上。
还有一种情况就是有一些机制Google和底层并没有打成一致的协议,如底层要求DOZE_SUSPEND下不去绘制,但Google并没有任何机制去保证,对于某些屏也没有做限制.
当然更多的还是具体问题具体分析,这里也只能按照往常分析fw问题的方式,先确定下到底是哪里的问题(即有没有对应的log),不同的问题还要根据不同团队的能力给出不同的解决方案。