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

最近分析一个问题,在锁屏窗口中启动应用会出现卡顿5秒,比如拨打电话,启动Google日历等。

拿拨打电话来举例,启动的action为 android.intent.action.CALL,对应处理的Activity在Telecom中:

  1. //packages/services/Telecomm/AndroidManifest.xml:
  2. <activity android:name=".components.UserCallActivity"
  3. android:label="@string/userCallActivityLabel"
  4. android:theme="@style/Theme.Telecomm.Transparent"
  5. android:permission="android.permission.CALL_PHONE"
  6. android:excludeFromRecents="true"
  7. android:process=":ui">
  8. <!-- CALL action intent filters for the various ways of initiating an outgoing call. -->
  9. <intent-filter>
  10. <action android:name="android.intent.action.CALL" />
  11. <category android:name="android.intent.category.DEFAULT" />
  12. <data android:scheme="tel" />
  13. </intent-filter>
  14. </activity>

出现卡屏的UserCallActivity比较特别,没有界面,启动之后就会在onCreate里面直接finish,其他的没有出现这个问题快捷方式都是在启动的Activity直接显示。

WMS出现卡屏5秒的原因分析

启动Activity com.android.server.telecom/.components.UserCallActivity的时候调用AMS的方法:

  1. com/android/server/am/ActivityStackSupervisor.java
  2. final boolean realStartActivityLocked(ActivityRecord r, ProcessRecord app,
  3. boolean andResume, boolean checkConfig) throws RemoteException {
  4. //…
  5. if (mKeyguardController.isKeyguardLocked()) {//锁屏状态下调用
  6. r.notifyUnknownVisibilityLaunched();
  7. }
  8. //…
  9. }

接着调用ActivityRecord# notifyUnknownVisibilityLaunched方法:

  1. com/android/server/am/ActivityRecord.java
  2. void notifyUnknownVisibilityLaunched() {
  3. // No display activities never add a window, so there is no point in waiting them for
  4. // relayout.
  5. if (!noDisplay) {
  6. mWindowContainerController.notifyUnknownVisibilityLaunched();
  7. }
  8. }

最终调用到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的超时被强制显示。

  1. com/android/server/wm/UnknownAppVisibilityController.java
  2. /**
  3. * Manages the set of {@link AppWindowToken}s for which we don't know yet whether it's visible or
  4. * not. This happens when starting an activity while the lockscreen is showing. In that case, the
  5. * keyguard flags an app might set influence it's visibility, so we wait until this is resolved to
  6. * start the transition to avoid flickers.(防止闪烁先显示Launcer,然后又快速地切换到显示目标Activity)
  7. */
  8. class UnknownAppVisibilityController {
  9. /**
  10. * 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.
  11. * Keyguard状态下启动Activity的时候调用
  12. */
  13. void notifyLaunched(@NonNull AppWindowToken appWindow) {
  14. if (DEBUG_UNKNOWN_APP_VISIBILITY) {
  15. Slog.d(TAG, "App launched appWindow=" + appWindow);
  16. }
  17. mUnknownApps.put(appWindow, UNKNOWN_STATE_WAITING_RESUME);
  18. }
  19. /**
  20. * Notifies that {@param appWindow} has finished resuming. Acitivty#onResume完成调用
  21. */
  22. void notifyAppResumedFinished(@NonNull AppWindowToken appWindow) {
  23. if (mUnknownApps.containsKey(appWindow)
  24. && mUnknownApps.get(appWindow) == UNKNOWN_STATE_WAITING_RESUME) {
  25. if (DEBUG_UNKNOWN_APP_VISIBILITY) {
  26. Slog.d(TAG, "App resume finished appWindow=" + appWindow);
  27. }
  28. mUnknownApps.put(appWindow, UNKNOWN_STATE_WAITING_RELAYOUT);
  29. }
  30. }
  31. /**
  32. * Notifies that {@param appWindow} has relaid out.
  33. * layout完成
  34. */
  35. void notifyRelayouted(@NonNull AppWindowToken appWindow) {
  36. if (!mUnknownApps.containsKey(appWindow)) {
  37. return;
  38. }
  39. if (DEBUG_UNKNOWN_APP_VISIBILITY) {
  40. Slog.d(TAG, "App relayouted appWindow=" + appWindow);
  41. }
  42. int state = mUnknownApps.get(appWindow);
  43. if (state == UNKNOWN_STATE_WAITING_RELAYOUT) { //当layout完成并且可见,也会从集合里移除
  44. mUnknownApps.put(appWindow, UNKNOWN_STATE_WAITING_VISIBILITY_UPDATE);
  45. mService.notifyKeyguardFlagsChanged(this::notifyVisibilitiesUpdated);
  46. }
  47. }
  48. //在Activity#onDestroy方法执行完成通知AMS的时候才会调用,mUnknownApps移除对应的appWindow
  49. void appRemovedOrHidden(@NonNull AppWindowToken appWindow) {
  50. if (DEBUG_UNKNOWN_APP_VISIBILITY) {
  51. Slog.d(TAG, "App removed or hidden appWindow=" + appWindow);
  52. }
  53. mUnknownApps.remove(appWindow);
  54. }
  55. //这个方法在AppTrasition的时候后判断是否有mUnknownApps存在
  56. boolean allResolved() {
  57. return mUnknownApps.isEmpty();
  58. }

下面是UnknownAppVisibility的日志,可以看到UserCallActivity的AppWindowToken被放到了mUnknownApps里面,过了5s之后才执行UserCallActivity#onDestory方法,从mUnknownApps里面移除。

  1. LOG:
  2. 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}}}
  3. 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}}}
  4. 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的超时机制触发]
  5. 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:

  1. com/android/server/am/ActivityStarter.java:
  2. private int startActivity(final ActivityRecord r, ActivityRecord sourceRecord,
  3. IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
  4. int startFlags, boolean doResume, ActivityOptions options, TaskRecord inTask, ActivityRecord[] outActivity) {
  5. //…
  6. try {
  7. mService.mWindowManager.deferSurfaceLayout();
  8. result = startActivityUnchecked(r, sourceRecord, voiceSession, voiceInteractor,
  9. startFlags, doResume, options, inTask, outActivity);
  10. } finally {
  11. mService.mWindowManager.continueSurfaceLayout();
  12. }
  13. return result;
  14. }

最终会调用到WindowSurfacePlacer # transitionGoodToGo这个方法,判断是否准备好可以执行transition:

  1. com/android/server/wm/WindowSurfacePlacer.java
  2. int handleAppTransitionReadyLocked() {
  3. int appsCount = mService.mOpeningApps.size();
  4. if (!transitionGoodToGo(appsCount, mTempTransitionReasons)) {
  5. return 0;
  6. //…
  7. if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "**** GOOD TO GO");
  8. mService.mH.removeMessages(H.APP_TRANSITION_TIMEOUT);//如果成功,从handler移除AppTransition超时处理
  9. //…
  10. }
  11. //下面是个关键的地方, UnknownAppVisibilityController里面保存了在锁屏情况下启动Activity的AppWindowToken,导致不能正常的transition
  12. private boolean transitionGoodToGo(int appsCount, SparseIntArray outReasons) {
  13. //...
  14. if (!mService.mAppTransition.isTimeout()) {
  15. //...
  16. if (!mService.mUnknownAppVisibilityController.allResolved()) {
  17. if (DEBUG_APP_TRANSITIONS) {
  18. Slog.v(TAG, "unknownApps is not empty: "
  19. + mService.mUnknownAppVisibilityController.getDebugMessage());
  20. }
  21. return false;
  22. }
  23. //....
  24. return false;
  25. }
  26. return true;
  27. }
  1. LOG
  2. 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
  3. //….
  4. 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
  5. //…
  6. //private static final int UNKNOWN_STATE_WAITING_RESUME = 1;
  7. //private static final int UNKNOWN_STATE_WAITING_RELAYOUT = 2;

可以看到日志UserCallActivity的state一直处于1和2的状态,阻塞了不能正常的AppTrasition

尝试在加上条件测试看是否还会出现卡屏5s的现象:

  1. com/android/server/wm/WindowSurfacePlacer.java
  2. if (!mService.mUnknownAppVisibilityController.allResolved()&&
  3. !mService.mUnknownAppVisibilityController.getDebugMessage()
  4. .contains(“com.android.server.telecom/.components.UserCallActivity”)) {
  5. //…
  6. return false;
  7. }

加上这个测试条件后就没有了卡屏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移除。然而锁屏的的情况有区别:

  1. com/android/server/am/ActivityManagerService.java
  2. @Override
  3. public final void activityPaused(IBinder token) {
  4. //…
  5. ActivityStack stack = ActivityRecord.getStackLocked(token);
  6. if (stack != null) {
  7. stack.activityPausedLocked(token, false);
  8. }
  9. }
  1. com/android/server/am/ActivityStack.java:
  2. final void activityPausedLocked(IBinder token, boolean timeout) {
  3. final ActivityRecord r = isInStackLocked(token);
  4. mHandler.removeMessages(PAUSE_TIMEOUT_MSG, r);
  5. //…
  6. completePauseLocked(true /* resumeNext */, null /* resumingActivity */);
  7. //…
  8. }
  9. private void completePauseLocked(boolean resumeNext, ActivityRecord resuming) {
  10. ActivityRecord prev = mPausingActivity;
  11. if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "Complete pause: " + prev);
  12. if (prev != null) {
  13. final boolean wasStopping = prev.state == STOPPING;
  14. prev.state = ActivityState.PAUSED;
  15. if (prev.finishing) {
  16. if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "Executing finish of activity: " + prev);
  17. prev = finishCurrentActivityLocked(prev, FINISH_AFTER_VISIBLE, false);
  18. } else{//...}
  19. }
  20. }
  21. final ActivityRecord finishCurrentActivityLocked(ActivityRecord r, int mode, boolean oomAdj) {
  22. // First things first: if this activity is currently visible,
  23. // and the resumed activity is not yet visible, then hold off on
  24. // finishing until the resumed one becomes visible.
  25. //这里的next是Launcher, r为UserCallActivity
  26. final ActivityRecord next = mStackSupervisor.topRunningActivityLocked();
  27. //可以看到上面的注释就是我们的这种场景
  28. //在锁屏的情况下会走到分支mode为FINISH_AFTER_VISIBLE,r.visible为true
  29. //关键的是next.nowVisible为false,即Launcher现在也是不可见的
  30. //所以就会将UserCallActivity放到 mStoppingActivities里面
  31. if (mode == FINISH_AFTER_VISIBLE && (r.visible || r.nowVisible)
  32. && next != null && !next.nowVisible) {
  33. if (!mStackSupervisor.mStoppingActivities.contains(r)) {
  34. addToStopping(r, false /* scheduleIdle */, false /* idleDelayed */);
  35. }
  36. if (DEBUG_STATES) Slog.v(TAG_STATES,
  37. "Moving to STOPPING: "+ r + " (finish requested)");
  38. r.state = STOPPING;
  39. return r;
  40. }
  41. //…
  42. //非锁屏下走这个分支,这里就会scheduleDestroyActivity
  43. if (mode == FINISH_IMMEDIATELY
  44. || (prevState == ActivityState.PAUSED
  45. && (mode == FINISH_AFTER_PAUSE || mStackId == PINNED_STACK_ID))
  46. || finishingActivityInNonFocusedStack
  47. || prevState == STOPPING
  48. || prevState == STOPPED
  49. || prevState == ActivityState.INITIALIZING) {
  50. boolean activityRemoved = destroyActivityLocked(r, true, "finish-imm");
  51. //....
  52. return activityRemoved ? null : r;
  53. }
  54. }

这个时候dumpsys activity的信息如下,UserCallActivity被加到了waittingToStop列表里,没有执行scheduleDestroyActivity方法:

  1. adb shell dumpsys activity:
  2. Activities waiting to stop:
  3. TaskRecord{28306a9 #433 A=com.android.server.telecom U=0 StackId=1 sz=1}
  4. Stop #0: ActivityRecord{f676a30 u0 com.android.server.telecom/.components.UserCallActivity t433 f}
  5. Activities waiting for another to become visible:
  6. TaskRecord{28306a9 #433 A=com.android.server.telecom U=0 StackId=1 sz=1}
  7. Wait #0: ActivityRecord{f676a30 u0 com.android.server.telecom/.components.UserCallActivity t433 f}
  8. // InCallActivity已经Resume等待显示
  9. ResumedActivity: ActivityRecord{4cd5d55 u0 com.tct.dialer/com.android.incallui.InCallActivity t432}

之后的finish操作在ActivityManagerService#activityIdle方法中处理: (每次有Activity Resume完成,就会在主线程MessageQueue的Idler中调用,也有可能会在SystemServer中闲时调用)

  1. com/android/server/am/ActivityManagerService.java:
  2. @Override
  3. public final void activityIdle(IBinder token, Configuration config, boolean stopProfiling) {
  4. synchronized (this) {
  5. ActivityStack stack = ActivityRecord.getStackLocked(token);
  6. if (stack != null) {
  7. ActivityRecord r =
  8. mStackSupervisor.activityIdleInternalLocked(token, false /* fromTimeout */,
  9. false /* processPausingActivities */, config);
  10. }//….
  11. }
  12. }

接着调用ActivityStackSupervisor# activityIdleInternalLocked

  1. final ActivityRecord activityIdleInternalLocked(final IBinder token, boolean fromTimeout,
  2. boolean processPausingActivities, Configuration config) {
  3. if (DEBUG_ALL) Slog.v(TAG, "Activity idle: " + token);
  4. // Atomically retrieve all of the other things to do.
  5. //查找waitingToStop的Activity,如果这里能够找出来,那么在后面finish,调用Activity#onDestory
  6. final ArrayList<ActivityRecord> stops = processStoppingActivitiesLocked(r,
  7. true /* remove */, processPausingActivities);
  8. NS = stops != null ? stops.size() : 0;
  9. //…
  10. // Stop any activities that are scheduled to do so but have been
  11. // waiting for the next one to start.
  12. for (int i = 0; i < NS; i++) {
  13. r = stops.get(i);
  14. final ActivityStack stack = r.getStack();
  15. if (stack != null) {
  16. if (r.finishing) {
  17. stack.finishCurrentActivityLocked(r, ActivityStack.FINISH_IMMEDIATELY, false);
  18. } else {
  19. stack.stopActivityLocked(r);
  20. }
  21. }
  22. }
  23. //...
  24. return r;
  25. }
  26. //查找操作waitingStop符合条件finish的
  27. final ArrayList<ActivityRecord> processStoppingActivitiesLocked(ActivityRecord idleActivity,
  28. boolean remove, boolean processPausingActivities) {
  29. ArrayList<ActivityRecord> stops = null;
  30. //这个时候没有Activity可见,nowVisible为false
  31. final boolean nowVisible = allResumedActivitiesVisible();
  32. for (int activityNdx = mStoppingActivities.size() - 1; activityNdx >= 0; --activityNdx) {
  33. ActivityRecord s = mStoppingActivities.get(activityNdx);//找到UserCallActivity
  34. //waitingVisible为true,一直等待显示
  35. boolean waitingVisible = mActivitiesWaitingForVisibleActivity.contains(s);
  36. if (DEBUG_STATES) Slog.v(TAG, "Stopping " + s + ": nowVisible=" + nowVisible
  37. + " waitingVisible=" + waitingVisible + " finishing=" + s.finishing);
  38. if (waitingVisible && nowVisible) {//不会进入这个分支
  39. mActivitiesWaitingForVisibleActivity.remove(s);
  40. waitingVisible = false;//当InCallActivity show出来,置false,
  41. if (s.finishing) {
  42. //...
  43. s.setVisibility(false);
  44. }
  45. }
  46. if (remove) {
  47. final ActivityStack stack = s.getStack();
  48. final boolean shouldSleepOrShutDown = stack != null
  49. ? stack.shouldSleepOrShutDownActivities()
  50. : mService.isSleepingOrShuttingDownLocked();
  51. //waitingVisible为true,进入不了这个分支
  52. if (!waitingVisible || shouldSleepOrShutDown) {
  53. if (!processPausingActivities && s.state == PAUSING) {
  54. //...
  55. continue;
  56. }
  57. if (DEBUG_STATES) Slog.v(TAG, "Ready to stop: " + s);
  58. if (stops == null) {
  59. stops = new ArrayList<>();
  60. }
  61. stops.add(s);
  62. mStoppingActivities.remove(activityNdx);
  63. }
  64. }
  65. }
  66. return stops;
  67. }

由于此时的topActivity (InCallActivity)一直没有show出来,就会导致UserCallActivity一直处于waitingToStop的状态,直到InCallActivity show出来之后,才会destroy UserCallActivity。

下面是日志,验证上面的分析:

  1. LOG
  2. 06-02 09:54:44.464 V/ActivityStackSupervisor( 777): Stopping ActivityRecord{5db03d9 u0 com.android.server.telecom/.components.UserCal
  3. lActivity t288}: nowVisible=false waitingVisible=false finishing=false
  4. 06-02 09:54:45.073 V/ActivityStack_States( 777): Moving to STOPPING: ActivityRecord{5db03d9 u0 com.android.server.telecom/.components.
  5. UserCallActivity t288 f} (finish requested)
  6. 06-02 09:54:45.283 V/ActivityStackSupervisor( 777): Stopping ActivityRecord{5db03d9 u0 com.android.server.telecom/.components.
  7. UserCallActivity t288 f}: nowVisible=false waitingVisible=true finishing=true
  8. 06-02 09:54:47.607 V/ActivityStackSupervisor( 777): Stopping ActivityRecord{5db03d9 u0 com.android.server.telecom/.components.
  9. UserCallActivity t288 f}: nowVisible=false waitingVisible=true finishing=true
  10. 06-02 09:54:51.186 V/ActivityStackSupervisor( 777): Stopping ActivityRecord{5db03d9 u0 com.android.server.telecom/.components.
  11. UserCallActivity t288 f}: nowVisible=true waitingVisible=false finishing=true
  12. 06-02 09:54:51.187 V/ActivityStackSupervisor( 777): Ready to stop: ActivityRecord{5db03d9 u0 com.android.server.telecom/.components.
  13. UserCallActivity t288 f}
  14. 06-02 09:54:51.287 V/ActivityStack_States( 777): Moving to FINISHING: ActivityRecord{5db03d9 u0 com.android.server.telecom/.components.
  15. 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移除。