Android锁屏下启动应用卡屏5秒的原因分析

转载:https://www.jianshu.com/p/dff47bfd426b
最近分析一个问题,在锁屏窗口中启动应用会出现卡顿5秒,比如拨打电话,启动Google日历等。
拿拨打电话来举例,启动的action为 android.intent.action.CALL,对应处理的Activity在Telecom中:
//packages/services/Telecomm/AndroidManifest.xml:

android:label="@string/userCallActivityLabel"
android:theme="@style/Theme.Telecomm.Transparent"
android:permission="android.permission.CALL_PHONE"
android:excludeFromRecents="true"
android:process=":ui">






出现卡屏的UserCallActivity比较特别,没有界面,启动之后就会在onCreate里面直接finish,其他的没有出现这个问题快捷方式都是在启动的Activity直接显示。
WMS出现卡屏5秒的原因分析
启动Activity com.android.server.telecom/.components.UserCallActivity的时候调用AMS的方法:
com/android/server/am/ActivityStackSupervisor.java:

final boolean realStartActivityLocked(ActivityRecord r, ProcessRecord app,
boolean andResume, boolean checkConfig) throws RemoteException {
//…
if (mKeyguardController.isKeyguardLocked()) {//锁屏状态下调用
r.notifyUnknownVisibilityLaunched();
}
//…
}

接着调用ActivityRecord# notifyUnknownVisibilityLaunched方法:
com/android/server/am/ActivityRecord.java:

void notifyUnknownVisibilityLaunched() {

// No display activities never add a window, so there is no point in waiting them for
// relayout.
if (!noDisplay) {
mWindowContainerController.notifyUnknownVisibilityLaunched();
}
}

最终调用到UnknownAppVisibilityController#notifyLaunched方法,UnknownAppVisibilityController里面的成员变量mUnknownApps记录了锁屏状态下调用AppWindowToken的状态列表,它有几个状态:
UNKNOWN_STATE_WAITING_RESUME 等待执行完onResume
UNKNOWN_STATE_WAITING_RELAYOUT 等待执行完layout
UNKNOWN_STATE_WAITING_VISIBILITY_UPDATE 等待可见性的更新
目前看来这个类的目的就是控制锁屏界面下启动的Activity显示,要等待锁屏下开启的Activity完全显示以后才能显示其他的Activity,如果一直不显示也会其他被启动的Acitivity也有一个5s的超时被强制显示。
com/android/server/wm/UnknownAppVisibilityController.java

/**

  • Manages the set of {@link AppWindowToken}s for which we don't know yet whether it's visible or
  • not. This happens when starting an activity while the lockscreen is showing. In that case, the
  • keyguard flags an app might set influence it's visibility, so we wait until this is resolved to
  • start the transition to avoid flickers.(防止闪烁先显示Launcer,然后又快速地切换到显示目标Activity)
    /
    class UnknownAppVisibilityController {
    /
    *
    • 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.
    • Keyguard状态下启动Activity的时候调用
      */

void notifyLaunched(@NonNull AppWindowToken appWindow) {
if (DEBUG_UNKNOWN_APP_VISIBILITY) {
Slog.d(TAG, "App launched appWindow=" + appWindow);
}
mUnknownApps.put(appWindow, UNKNOWN_STATE_WAITING_RESUME);
}

/**

  • Notifies that {@param appWindow} has finished resuming. Acitivty#onResume完成调用
    */
    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);
    }
    }

    /**

    • Notifies that {@param appWindow} has relaid out.
    • layout完成
      */
      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) { //当layout完成并且可见,也会从集合里移除
      mUnknownApps.put(appWindow, UNKNOWN_STATE_WAITING_VISIBILITY_UPDATE);
      mService.notifyKeyguardFlagsChanged(this::notifyVisibilitiesUpdated);
      }
      }
      //在Activity#onDestroy方法执行完成通知AMS的时候才会调用,mUnknownApps移除对应的appWindow
      void appRemovedOrHidden(@NonNull AppWindowToken appWindow) {
      if (DEBUG_UNKNOWN_APP_VISIBILITY) {
      Slog.d(TAG, "App removed or hidden appWindow=" + appWindow);
      }
      mUnknownApps.remove(appWindow);
      }
      //这个方法在AppTrasition的时候后判断是否有mUnknownApps存在
      boolean allResolved() {
      return mUnknownApps.isEmpty();
      }

下面是UnknownAppVisibility的日志,可以看到UserCallActivity的AppWindowToken被放到了mUnknownApps里面,过了5s之后才执行UserCallActivity#onDestory方法,从mUnknownApps里面移除。
LOG:
06-02 09:54:44.352 D/UnknownAppVisibility( 777): App launched appWindow=AppWindowToken{99d9676 token=Token{85511 ActivityRecord{5db03d9 u0 com.android.server.telecom/.components.UserCallActivity t288}}}

06-02 09:54:45.263 D/UnknownAppVisibility( 777): App resume finished appWindow=AppWindowToken{99d9676 token=Token{85511 ActivityRecord{5db03d9 u0 com.android.server.telecom/.components.UserCallActivity t288}}}

06-02 09:54:51.455 V/ActivityManagerService_Switch( 777): ACTIVITY DESTROYED: Token{85511 ActivityRecord{5db03d9 u0 com.android.server.telecom/.components.UserCallActivity t288 f}} [这个地方的onDestroy不是正常finish后调用的,而是AMS的超时机制触发]

06-02 09:54:51.460 D/UnknownAppVisibility( 777): App removed or hidden appWindow=AppWindowToken{99d9676 token=Token{85511 ActivityRecord{5db03d9 u0 com.android.server.telecom/.components.UserCallActivity t288}}}

在启动Activity的时候,会调用continueSurfaceLayout:
com/android/server/am/ActivityStarter.java:

private int startActivity(final ActivityRecord r, ActivityRecord sourceRecord,
IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
int startFlags, boolean doResume, ActivityOptions options, TaskRecord inTask, ActivityRecord[] outActivity) {
//…
try {
mService.mWindowManager.deferSurfaceLayout();
result = startActivityUnchecked(r, sourceRecord, voiceSession, voiceInteractor,
startFlags, doResume, options, inTask, outActivity);
} finally {
mService.mWindowManager.continueSurfaceLayout();
}
return result;
}

最终会调用到WindowSurfacePlacer # transitionGoodToGo这个方法,判断是否准备好可以执行transition:
com/android/server/wm/WindowSurfacePlacer.java:

int handleAppTransitionReadyLocked() {
int appsCount = mService.mOpeningApps.size();
if (!transitionGoodToGo(appsCount, mTempTransitionReasons)) {
return 0;

//…
if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "**** GOOD TO GO");
mService.mH.removeMessages(H.APP_TRANSITION_TIMEOUT);//如果成功,从handler移除AppTransition超时处理
//…
}

//下面是个关键的地方, UnknownAppVisibilityController里面保存了在锁屏情况下启动Activity的AppWindowToken,导致不能正常的transition
private boolean transitionGoodToGo(int appsCount, SparseIntArray outReasons) {
//...
if (!mService.mAppTransition.isTimeout()) {
//...
if (!mService.mUnknownAppVisibilityController.allResolved()) {
if (DEBUG_APP_TRANSITIONS) {
Slog.v(TAG, "unknownApps is not empty: "
+ mService.mUnknownAppVisibilityController.getDebugMessage());
}
return false;
}
//....
return false;
}
return true;
}

LOG
06-02 09:54:45.229 V/WindowSurfacePlacer( 777): unknownApps is not empty: app=AppWindowToken{99d9676 token=Token{85511 ActivityRecord{5db03d9 u0 com.android.server.telecom/.components.UserCallActivity t288}}} state=1
//….
06-02 09:54:45.278 V/WindowSurfacePlacer( 777): unknownApps is not empty: app=AppWindowToken{99d9676 token=Token{85511 ActivityRecord{5db03d9 u0 com.android.server.telecom/.components.UserCallActivity t288}}} state=2
//…
//private static final int UNKNOWN_STATE_WAITING_RESUME = 1;
//private static final int UNKNOWN_STATE_WAITING_RELAYOUT = 2;

可以看到日志UserCallActivity的state一直处于1和2的状态,阻塞了不能正常的AppTrasition
尝试在加上条件测试看是否还会出现卡屏5s的现象:
com/android/server/wm/WindowSurfacePlacer.java:
if (!mService.mUnknownAppVisibilityController.allResolved()&&
!mService.mUnknownAppVisibilityController.getDebugMessage()
.contains(“com.android.server.telecom/.components.UserCallActivity”)) {
//…
return false;
}

加上这个测试条件后就没有了卡屏5秒的情况,但是有了新问题,会出现flickers(闪烁),先显示Launcer,然后又快速地切换到显示InCallActivity。如果FUNC先解锁操作再执行拨打电话,也会出现同样的问题。这正是UnknownAppVisibilityController解决的问题。下面看UserCallActivity finish不掉的原因。
AMS finish没有立即触发onDestroy的原因分析
正常的情况下,在Activity#onCreate方法中直接调用finish(),在之后的onPause方法调用AMS的activityPaused,就会直接调用IApplicationThread#scheduleDestroyActivity的方法通知Activity执行onDestroy,然后执行AMS的activityDestroyed,将UnknownAppVisibilityController里面的AppWindow移除。然而锁屏的的情况有区别:
com/android/server/am/ActivityManagerService.java:
@Override
public final void activityPaused(IBinder token) {
//…
ActivityStack stack = ActivityRecord.getStackLocked(token);
if (stack != null) {
stack.activityPausedLocked(token, false);
}
}

com/android/server/am/ActivityStack.java:

final void activityPausedLocked(IBinder token, boolean timeout) {
final ActivityRecord r = isInStackLocked(token);
mHandler.removeMessages(PAUSE_TIMEOUT_MSG, r);
//…
completePauseLocked(true /* resumeNext /, null / resumingActivity */);
//…
}

private void completePauseLocked(boolean resumeNext, ActivityRecord resuming) {
ActivityRecord prev = mPausingActivity;
if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "Complete pause: " + prev);

if (prev != null) {
    final boolean wasStopping = prev.state == STOPPING;
    prev.state = ActivityState.PAUSED;
    if (prev.finishing) {
        if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "Executing finish of activity: " + prev);
        prev = finishCurrentActivityLocked(prev, FINISH_AFTER_VISIBLE, false);
    } else{//...}
    
}

}

final ActivityRecord finishCurrentActivityLocked(ActivityRecord r, int mode, boolean oomAdj) {
// First things first: if this activity is currently visible,
// and the resumed activity is not yet visible, then hold off on
// finishing until the resumed one becomes visible.
//这里的next是Launcher, r为UserCallActivity
final ActivityRecord next = mStackSupervisor.topRunningActivityLocked();
//可以看到上面的注释就是我们的这种场景
//在锁屏的情况下会走到分支mode为FINISH_AFTER_VISIBLE,r.visible为true
//关键的是next.nowVisible为false,即Launcher现在也是不可见的
//所以就会将UserCallActivity放到 mStoppingActivities里面
if (mode == FINISH_AFTER_VISIBLE && (r.visible || r.nowVisible)
&& next != null && !next.nowVisible) {
if (!mStackSupervisor.mStoppingActivities.contains(r)) {
addToStopping(r, false /* scheduleIdle /, false / idleDelayed */);
}
if (DEBUG_STATES) Slog.v(TAG_STATES,
"Moving to STOPPING: "+ r + " (finish requested)");
r.state = STOPPING;
return r;
}
//…
//非锁屏下走这个分支,这里就会scheduleDestroyActivity

if (mode == FINISH_IMMEDIATELY
        || (prevState == ActivityState.PAUSED
        && (mode == FINISH_AFTER_PAUSE || mStackId == PINNED_STACK_ID))
        || finishingActivityInNonFocusedStack
        || prevState == STOPPING
        || prevState == STOPPED
        || prevState == ActivityState.INITIALIZING) {
         boolean activityRemoved = destroyActivityLocked(r, true, "finish-imm");
    //....
    return activityRemoved ? null : r;
}

}

这个时候dumpsys activity的信息如下,UserCallActivity被加到了waittingToStop列表里,没有执行scheduleDestroyActivity方法:
adb shell dumpsys activity:
Activities waiting to stop:
TaskRecord{28306a9 #433 A=com.android.server.telecom U=0 StackId=1 sz=1}
Stop #0: ActivityRecord{f676a30 u0 com.android.server.telecom/.components.UserCallActivity t433 f}

Activities waiting for another to become visible:
TaskRecord{28306a9 #433 A=com.android.server.telecom U=0 StackId=1 sz=1}
Wait #0: ActivityRecord{f676a30 u0 com.android.server.telecom/.components.UserCallActivity t433 f}
// InCallActivity已经Resume等待显示
ResumedActivity: ActivityRecord{4cd5d55 u0 com.tct.dialer/com.android.incallui.InCallActivity t432}

之后的finish操作在ActivityManagerService#activityIdle方法中处理: (每次有Activity Resume完成,就会在主线程MessageQueue的Idler中调用,也有可能会在SystemServer中闲时调用)
com/android/server/am/ActivityManagerService.java:
@Override
public final void activityIdle(IBinder token, Configuration config, boolean stopProfiling) {
synchronized (this) {
ActivityStack stack = ActivityRecord.getStackLocked(token);
if (stack != null) {
ActivityRecord r =
mStackSupervisor.activityIdleInternalLocked(token, false /* fromTimeout /,
false /
processPausingActivities */, config);
}//….
}
}

接着调用ActivityStackSupervisor# activityIdleInternalLocked
final ActivityRecord activityIdleInternalLocked(final IBinder token, boolean fromTimeout,
boolean processPausingActivities, Configuration config) {
if (DEBUG_ALL) Slog.v(TAG, "Activity idle: " + token);

    // Atomically retrieve all of the other things to do.
//查找waitingToStop的Activity,如果这里能够找出来,那么在后面finish,调用Activity#onDestory
final ArrayList stops = processStoppingActivitiesLocked(r,
        true /* remove */, processPausingActivities);
NS = stops != null ? stops.size() : 0;
//…
// Stop any activities that are scheduled to do so but have been
// waiting for the next one to start.
for (int i = 0; i < NS; i++) {
    r = stops.get(i);
    final ActivityStack stack = r.getStack();
    if (stack != null) {
        if (r.finishing) {
            stack.finishCurrentActivityLocked(r, ActivityStack.FINISH_IMMEDIATELY, false);
        } else {
            stack.stopActivityLocked(r);
        }
    }
}
//...
return r;

}

//查找操作waitingStop符合条件finish的
final ArrayList processStoppingActivitiesLocked(ActivityRecord idleActivity,
boolean remove, boolean processPausingActivities) {
ArrayList stops = null;
//这个时候没有Activity可见,nowVisible为false
final boolean nowVisible = allResumedActivitiesVisible();
for (int activityNdx = mStoppingActivities.size() - 1; activityNdx >= 0; --activityNdx) {
ActivityRecord s = mStoppingActivities.get(activityNdx);//找到UserCallActivity
//waitingVisible为true,一直等待显示
boolean waitingVisible = mActivitiesWaitingForVisibleActivity.contains(s);
if (DEBUG_STATES) Slog.v(TAG, "Stopping " + s + ": nowVisible=" + nowVisible
+ " waitingVisible=" + waitingVisible + " finishing=" + s.finishing);
if (waitingVisible && nowVisible) {//不会进入这个分支
mActivitiesWaitingForVisibleActivity.remove(s);
waitingVisible = false;//当InCallActivity show出来,置false,
if (s.finishing) {
//...
s.setVisibility(false);
}
}
if (remove) {
final ActivityStack stack = s.getStack();
final boolean shouldSleepOrShutDown = stack != null
? stack.shouldSleepOrShutDownActivities()
: mService.isSleepingOrShuttingDownLocked();
//waitingVisible为true,进入不了这个分支
if (!waitingVisible || shouldSleepOrShutDown) {
if (!processPausingActivities && s.state == PAUSING) {
//...
continue;
}
if (DEBUG_STATES) Slog.v(TAG, "Ready to stop: " + s);
if (stops == null) {
stops = new ArrayList<>();
}
stops.add(s);
mStoppingActivities.remove(activityNdx);
}
}
}
return stops;
}

由于此时的topActivity (InCallActivity)一直没有show出来,就会导致UserCallActivity一直处于waitingToStop的状态,直到InCallActivity show出来之后,才会destroy UserCallActivity。
下面是日志,验证上面的分析:
LOG
06-02 09:54:44.464 V/ActivityStackSupervisor( 777): Stopping ActivityRecord{5db03d9 u0 com.android.server.telecom/.components.UserCal
lActivity t288}: nowVisible=false waitingVisible=false finishing=false

06-02 09:54:45.073 V/ActivityStack_States( 777): Moving to STOPPING: ActivityRecord{5db03d9 u0 com.android.server.telecom/.components.
UserCallActivity t288 f} (finish requested)

06-02 09:54:45.283 V/ActivityStackSupervisor( 777): Stopping ActivityRecord{5db03d9 u0 com.android.server.telecom/.components.
UserCallActivity t288 f}: nowVisible=false waitingVisible=true finishing=true

06-02 09:54:47.607 V/ActivityStackSupervisor( 777): Stopping ActivityRecord{5db03d9 u0 com.android.server.telecom/.components.
UserCallActivity t288 f}: nowVisible=false waitingVisible=true finishing=true

06-02 09:54:51.186 V/ActivityStackSupervisor( 777): Stopping ActivityRecord{5db03d9 u0 com.android.server.telecom/.components.
UserCallActivity t288 f}: nowVisible=true waitingVisible=false finishing=true

06-02 09:54:51.187 V/ActivityStackSupervisor( 777): Ready to stop: ActivityRecord{5db03d9 u0 com.android.server.telecom/.components.
UserCallActivity t288 f}

06-02 09:54:51.287 V/ActivityStack_States( 777): Moving to FINISHING: ActivityRecord{5db03d9 u0 com.android.server.telecom/.components.
UserCallActivity t288 f}

结论
由于在锁屏界面启动Activity导致触发了WMS的 UnknownAppVisibilityController防止闪烁(显示Launcher之后又快速切换到目标Activity)的机制, UserCall Activity不能及时finish,就会导致一直处于卡屏状态,直到AppTransition超时机制才强制显示InCallActivity。
UserCallActivity是Telecomm服务里面的组件,但是它又不是真正显示UI的Acivity,针对这种情况可以使用Telecom的placeCall接口可以规避掉这种情况。但是,不能保证所有启动的Acitivty都直接显示,比如原生的Google日历也会出现卡屏5秒现象,目前看来是原生Android的bug,对于这种情况,finish之后没有将不显示的窗口及时地从UnknownAppVisibilityController移除。

作者:王路飞的故事
链接:https://www.jianshu.com/p/dff47bfd426b
来源:
著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

你可能感兴趣的:(Android锁屏下启动应用卡屏5秒的原因分析)