Android 亮屏慢问题处理
问题:
【指纹】灭屏下使用正确的指纹解锁,解锁成功背光亮但屏幕没亮
【偶现】滑动解锁后只显示壁纸,图标在4S后加载出来
抖音有个activity在灭屏的时候都会启动,如果出现如下情况:
06-2513:08:14.4899802799I am_create_activity: [0,150997160,80,com.ss.android.ugc.aweme/com.ss.android.message.sswo.SswoActivity,NULL,NULL,NULL,276824064]
06-2513:08:14.5459802799I am_restart_activity: [0,150997160,80,com.ss.android.ugc.aweme/com.ss.android.message.sswo.SswoActivity]
06-2513:08:14.5539802799I am_set_resumed_activity: [0,com.ss.android.ugc.aweme/com.ss.android.message.sswo.SswoActivity,minimalResumeActivityLocked]
06-2513:08:14.5859802799I am_pause_activity: [0,150997160,com.ss.android.ugc.aweme/com.ss.android.message.sswo.SswoActivity]
06-2513:08:14.62698013170I am_set_resumed_activity: [0,com.ss.android.ugc.aweme/com.ss.android.message.sswo.SswoActivity,resumeTopActivityInnerLocked]
06-2513:08:14.64298013170I am_resume_activity: [0,150997160,80,com.ss.android.ugc.aweme/com.ss.android.message.sswo.SswoActivity]
06-2513:08:14.8329802973I am_finish_activity: [0,150997160,80,com.ss.android.ugc.aweme/com.ss.android.message.sswo.SswoActivity,app-request]
06-2513:08:14.8369802973I am_focused_stack: [0,0,1,finishActivity adjustFocus]
06-2513:08:14.8539802973I am_pause_activity: [0,150997160,com.ss.android.ugc.aweme/com.ss.android.message.sswo.SswoActivity]
06-2513:08:14.94898011678I am_set_resumed_activity: [0,com.android.launcher3/com.android.searchlauncher.SearchLauncher,resumeTopActivityInnerLocked]
06-2513:08:14.97298011678I am_resume_activity: [0,257364849,3,com.android.launcher3/com.android.searchlauncher.SearchLauncher]
06-2513:08:15.1359802973I am_failed_to_pause: [0,150997160,com.ss.android.ugc.aweme/com.ss.android.message.sswo.SswoActivity,(none)]
06-2513:08:15.27835713571I am_on_resume_called: [0,com.android.searchlauncher.SearchLauncher,RESUME_ACTIVITY]
06-2513:08:20.3799801055I am_destroy_activity: [0,150997160,80,com.ss.android.ugc.aweme/com.ss.android.message.sswo.SswoActivity,finish-imm]
06-2513:08:20.38983158315I am_on_stop_called: [0,com.ss.android.message.sswo.SswoActivity,destroy]
06-2513:08:28.8349801054I am_pss : [3571,10027,com.android.launcher3,106524672,89603072,5540864]
pause failed导致AMS这边会有一个超时等待的动作,需继续查找为啥会出现pause 超时。
导致问题流程如下:
1, 待机过程中,抖音会启动
com.ss.android.ugc.aweme/com.ss.android.message.sswo.SswoActivity,由于处于待机状态,该activity会立刻进入pause状态;后来得知SswoActivity这个类是抖音专门用来保活的一个透明类。
2,正常情况下,该activity在唤醒时会立刻finish(app request主动请求),然后在唤醒过程中Top Activity依然是Launcher,如果没有及时finish,则Top activity是该activity;
3,如果Top activity是 抖音activity,最终还是会主动请求activity finish,触发抖音activity--->Launcher activity的切换过程;
4,步骤3中的切换过程在 Keyguard lock情况下进行,这种情况下,在显示Launcher activity之前需要将将抖音的UI show过程走一次(WMS 在有keyguard locked情况下的APP 切换逻辑);
5,由于该抖音activity没有UI show过程(该activity没有UI),所以该等待会一直存在,直到5s超时,从而导致issue中的情况。
点亮解锁逻辑流程如下:
设备点亮流程
frameworks\base\services\core\java\com\android\server\power\PowerManagerService.java
唤醒源 -> updatePowerStateLocked ->
updateDisplayPowerStateLocked -> requestPowerState
requestPowerState的实现在DisplayPowerController.java中
在这里有一个blockScreen的逻辑动作,为的就是当screen内容准备好后,才点亮屏幕。
相关接口及主要代码:
animateScreenStateChange -> setScreenState -> blockScreenOn
private void blockScreenOn() {
if (mPendingScreenOnUnblocker == null) {
Trace.asyncTraceBegin(Trace.TRACE_TAG_POWER, SCREEN_ON_BLOCKED_TRACE_NAME, 0);
mPendingScreenOnUnblocker = new ScreenOnUnblocker();
mScreenOnBlockStartRealTime = SystemClock.elapsedRealtime();
Slog.i(TAG, "Blocking screen on until initial contents have been drawn.");
}
}
ScreenOnUnblocker的作用是在回调触发的时候,发送一个消息MSG_SCREEN_ON_UNBLOCKED出去,执行unblock的动作。
private final class ScreenOnUnblocker implements WindowManagerPolicy.ScreenOnListener {
@Override
public void onScreenOn() {
Message msg = mHandler.obtainMessage(MSG_SCREEN_ON_UNBLOCKED, this);
msg.setAsynchronous(true);
mHandler.sendMessage(msg);
}
}
回到setScreenState中,主要代码:
mWindowManagerPolicy.screenTurningOn(mPendingScreenOnUnblocker);
screenTurningOn的实现在PhoneWindowmanager中,screenTurningOn回调结束后,屏点亮,unblock之前的逻辑,打印相关消息。
private void unblockScreenOn() {
if (mPendingScreenOnUnblocker != null) {
mPendingScreenOnUnblocker = null;
long delay = SystemClock.elapsedRealtime() - mScreenOnBlockStartRealTime;
Slog.i(TAG, "Unblocked screen on after " + delay + " ms");
//所以可以根据这里来判断屏亮耗时
Trace.asyncTraceEnd(Trace.TRACE_TAG_POWER, SCREEN_ON_BLOCKED_TRACE_NAME, 0);
}
}
// Called on the DisplayManager's DisplayPowerController thread.
@Override
public void screenTurningOn(final ScreenOnListener screenOnListener) {
if (DEBUG_WAKEUP) Slog.i(TAG, "Screen turning on...");
//所以可以根据这里来判断屏亮开始触发点亮
。。。
//这里走有锁屏的流程
if (mKeyguardDelegate != null) {
mHandler.removeMessages(MSG_KEYGUARD_DRAWN_TIMEOUT);
mHandler.sendEmptyMessageDelayed(MSG_KEYGUARD_DRAWN_TIMEOUT,
getKeyguardDrawnTimeout());
mKeyguardDelegate.onScreenTurningOn(mKeyguardDrawnCallback);
} else {
if (DEBUG_WAKEUP) Slog.d(TAG,
"null mKeyguardDelegate: setting mKeyguardDrawComplete.");
finishKeyguardDrawn();
}
}
}
从主要代码可以看到,这里触发PhoneWindowManager的screenTurningOn接口,等待Keyguard绘制完成,并设置了一个1s的timeout机制,超时系统就不会继续等待,强制执行finishKeyguardDrawn(),当keyguard正常绘制完成,回调到mKeyguardDrawnCallback,继续执行finishKeyguardDrawn()
case MSG_KEYGUARD_DRAWN_COMPLETE:
if (DEBUG_WAKEUP) Slog.w(TAG, "Setting mKeyguardDrawComplete");
finishKeyguardDrawn();
break;
case MSG_KEYGUARD_DRAWN_TIMEOUT:
Slog.w(TAG, "Keyguard drawn timeout. Setting mKeyguardDrawComplete");
finishKeyguardDrawn();
当keyguard绘制完成的时候,接着会等待后台所有visible的window绘制完成
private void finishKeyguardDrawn() {
。。。
// ... eventually calls finishWindowsDrawn which will finalize our screen turn on
// as well as enabling the orientation change logic/sensor.
mWindowManagerInternal.waitForAllWindowsDrawn(mWindowManagerDrawCallback,
WAITING_FOR_DRAWN_TIMEOUT);
}
WAITING_FOR_DRAWN_TIMEOUT是一个1s的延时逻辑。
如果时间到了,还没有绘制完成,系统也不会一直等下去
@Override
public void waitForAllWindowsDrawn(Runnable callback, long timeout) {
boolean allWindowsDrawn = false;
synchronized (mWindowMap) {
mWaitingForDrawnCallback = callback;
getDefaultDisplayContentLocked().waitForAllWindowsDrawn();
mWindowPlacerLocked.requestTraversal();
mH.removeMessages(H.WAITING_FOR_DRAWN_TIMEOUT);
if (mWaitingForDrawn.isEmpty()) {
allWindowsDrawn = true;
} else {
mH.sendEmptyMessageDelayed(H.WAITING_FOR_DRAWN_TIMEOUT, timeout);
checkDrawnWindowsLocked();
}
}
if (allWindowsDrawn) {
callback.run();
}
}
超时后,会清掉mWaitingForDrawn,在执行回调的run方法
case WAITING_FOR_DRAWN_TIMEOUT: {
Runnable callback = null;
synchronized (mWindowMap) {
Slog.w(TAG_WM, "Timeout waiting for drawn: undrawn=" + mWaitingForDrawn);
mWaitingForDrawn.clear();
callback = mWaitingForDrawnCallback;
mWaitingForDrawnCallback = null;
}
if (callback != null) {
callback.run();
}
break;
}
在看callback的实现,发送一个消息触发window绘制完成finishWindowsDrawn使能屏幕:
final Runnable mWindowManagerDrawCallback = new Runnable() {
@Override
public void run() {
if (DEBUG_WAKEUP) Slog.i(TAG, "All windows ready for display!");
mHandler.sendEmptyMessage(MSG_WINDOW_MANAGER_DRAWN_COMPLETE);
}
};
private void finishWindowsDrawn() {
synchronized (mLock) {
if (!mScreenOnEarly || mWindowManagerDrawComplete) {
return; // Screen is not turned on or we did already handle this case earlier.
}
mWindowManagerDrawComplete = true;
}
finishScreenTurningOn();
}
所以,从这里可以看出,上层影响屏亮的因素:
锁屏绘制时长
Visible的window绘制的时长
到此,背光点亮。
Activity转场显示主要逻辑(锁屏状态下)
在锁屏显示状态下,当activityA->activityB切换的时候,会存在切换动画。
AMS startActivity会调用到ActivityStackSupervisor.java中的realStartActivityLocked,这里只看主要逻辑:
if (mKeyguardController.isKeyguardLocked()) {
r.notifyUnknownVisibilityLaunched();
}
notifyUnknownVisibilityLaunched的实现在ActivityRecord中
void notifyUnknownVisibilityLaunched() {
// No display activities never add a window, so there is no point in waiting them for
// relayout.
if (!noDisplay) {
mWindowContainerController.notifyUnknownVisibilityLaunched();
}
}
接着看AppWindowContainerController中的实现:
public void notifyUnknownVisibilityLaunched() {
synchronized(mWindowMap) {
if (mContainer != null) {
mService.mUnknownAppVisibilityController.notifyLaunched(mContainer);
}
}
}
最终实现在:
/**
* Notifies that {@param appWindow} has been launched behind Keyguard, and we need to wait until
* it is resumed and relaid out to resolve the visibility.
*/
void notifyLaunched(@NonNull AppWindowToken appWindow) {
if (DEBUG_UNKNOWN_APP_VISIBILITY) {
Slog.d(TAG, "App launched appWindow=" + appWindow);
}
mUnknownApps.put(appWindow, UNKNOWN_STATE_WAITING_RESUME);
}
同样在AMS执行activity Resume生命周期的时候,会触发:
/**
* Notifies that {@param appWindow} has finished resuming.
*/
void notifyAppResumedFinished(@NonNull AppWindowToken appWindow) {
if (mUnknownApps.containsKey(appWindow)
&& mUnknownApps.get(appWindow) == UNKNOWN_STATE_WAITING_RESUME) {
if (DEBUG_UNKNOWN_APP_VISIBILITY) {
Slog.d(TAG, "App resume finished appWindow=" + appWindow);
}
mUnknownApps.put(appWindow, UNKNOWN_STATE_WAITING_RELAYOUT);
}
}
当relayout完成时,会清掉mUnknownApps
/**
* Notifies that {@param appWindow} has relaid out.
*/
void notifyRelayouted(@NonNull AppWindowToken appWindow) {
if (!mUnknownApps.containsKey(appWindow)) {
return;
}
if (DEBUG_UNKNOWN_APP_VISIBILITY) {
Slog.d(TAG, "App relayouted appWindow=" + appWindow);
}
int state = mUnknownApps.get(appWindow);
if (state == UNKNOWN_STATE_WAITING_RELAYOUT) {
mUnknownApps.put(appWindow, UNKNOWN_STATE_WAITING_VISIBILITY_UPDATE);
mService.notifyKeyguardFlagsChanged(this::notifyVisibilitiesUpdated);
}
}
接着我们直接看AppTransition.java中,关于切换流程prepareAppTransitionLocked
mService.mH.removeMessages(H.APP_TRANSITION_TIMEOUT);
mService.mH.sendEmptyMessageDelayed(H.APP_TRANSITION_TIMEOUT, APP_TRANSITION_TIMEOUT_MS);
APP_TRANSITION_TIMEOUT_MS是一个5s的timeout机制,当超时了,系统也不会继续等待,而是强制继续执行。
case APP_TRANSITION_TIMEOUT: {
synchronized (mWindowMap) {
if (mAppTransition.isTransitionSet() || !mOpeningApps.isEmpty()
|| !mClosingApps.isEmpty()) {
if (DEBUG_APP_TRANSITIONS || mWindowManagerDebugger.WMS_DEBUG_USER)
Slog.v(TAG_WM, "*** APP TRANSITION TIMEOUT."
+ " isTransitionSet()=" + mAppTransition.isTransitionSet()
+ " mOpeningApps.size()=" + mOpeningApps.size()
+ " mClosingApps.size()=" + mClosingApps.size());
mAppTransition.setTimeout();//设置超时flag
mWindowPlacerLocked.performSurfacePlacement();//强制绘制,不等待
}
}
直接看performSurfacePlacement的主要逻辑
// If we are ready to perform an app transition, check through all of the app tokens to be
// shown and see if they are ready to go.
if (mService.mAppTransition.isReady()) {
defaultDisplay.pendingLayoutChanges |=
surfacePlacer.handleAppTransitionReadyLocked();
if (DEBUG_LAYOUT_REPEATS)
surfacePlacer.debugLayoutRepeats("after handleAppTransitionReadyLocked",
defaultDisplay.pendingLayoutChanges);
}
int handleAppTransitionReadyLocked() {
int appsCount = mService.mOpeningApps.size();
//如果这里return了,后面就不会触发,界面就没内容
if (!transitionGoodToGo(appsCount, mTempTransitionReasons)) {
return 0;
}
Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "AppTransitionReady");
if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "**** GOOD TO GO");
int transit = mService.mAppTransition.getAppTransition();
if (mService.mSkipAppTransitionAnimation && !isKeyguardGoingAwayTransit(transit)) {
transit = AppTransition.TRANSIT_UNSET;
}
mService.mSkipAppTransitionAnimation = false;
mService.mNoAnimationNotifyOnTransitionFinished.clear();
//界面显示成功,转场完成,remove掉之前的timeout消息
mService.mH.removeMessages(H.APP_TRANSITION_TIMEOUT);
}
transitionGoodToGo的实现,这里只看与该问题相关的逻辑代码:
private boolean transitionGoodToGo(int appsCount, SparseIntArray outReasons) {
if (DEBUG_APP_TRANSITIONS) Slog.v(TAG,
"Checking " + appsCount + " opening apps (frozen="
+ mService.mDisplayFrozen + " timeout="
+ mService.mAppTransition.isTimeout() + ")...");
if (!mService.mAppTransition.isTimeout()) {
...
//如果这里mUnknownApps不为空,那么说明relayout没有完成,直接return false
不会触发后面GOOD TO GO的逻辑显示
if (!mService.mUnknownAppVisibilityController.allResolved()) {
if (DEBUG_APP_TRANSITIONS) {
Slog.v(TAG, "unknownApps is not empty: "
+ mService.mUnknownAppVisibilityController.getDebugMessage());
}
return false;
}
...
}
return true;
}
此该问题的相关逻辑已经梳理完成。
目前针对这个问题在transitionGoodToGo做了一个规避操作
if(!mService.mUnknownAppVisibilityController.allResolved() &&
!mService.mUnknownAppVisibilityController.getDebugMessage().contains("com.ss.android.message.sswo.SswoActivity")) {
if(DEBUG_APP_TRANSITIONS) {
Slog.v(TAG,"fix unknownApps is not empty: "
+ mService.mUnknownAppVisibilityController.getDebugMessage());
}
returnfalse;
}