Analysis of the reason why the Android application is slow to start when the screen is locked

Recently, I encountered a requirement in my work to make a call directly under the lock screen. After the function was implemented, the test gave feedback that it took too long (about 5 seconds) to make the call after the operation. Hope it can be optimized. Later, after analysis, it was determined that it was not an application layer problem. Then, after a meal on Baidu and Google, I found an article dedicated to analyzing this problem. I also read what others have written before I am going to analyze the source code following the ideas in this article. First of all, I want to state that this article I wrote is to record a process of analyzing this problem. The source code is based on Android 11 (Api 30).

Not much to say, let's start now. . .

The action initiated by making a call is android.intent.action.CALL, the corresponding activity is com.android.server.telecom/.components.UserCallActivity, and the page for making a call is InCallActivity. Regarding this UserCallActivity, it seems to be quite special, let's take a look at its code first.

We see that finish() is done in the onCreate method.

In order to express the problem more clearly, let me post the log I caught.

// 在锁屏页面调用Intent.ACTION_CALL之后
03-10 18:22:57.220 1117 4537 I wm_create_activity: [0,106764365,78,com.android.server.telecom/.components.UserCallActivity,android.intent.action.CALL,NULL,tel:xxxxxxxxxxx,8388608]
// 省略应用层的log
03-10 18:22:57.258 1117 4537 I wm_restart_activity: [0,106764365,78,com.android.server.telecom/.components.UserCallActivity]
03-10 18:22:57.260 1117 4537 I wm_set_resumed_activity: [0,com.android.server.telecom/.components.UserCallActivity,minimalResumeActivityLocked]
03-10 18:22:57.260 1117 4537 I wm_add_to_stopping: [0,106764365,com.android.server.telecom/.components.UserCallActivity,makeInvisible]
03-10 18:22:57.261 1117 4537 I wm_pause_activity: [0,106764365,com.android.server.telecom/.components.UserCallActivity,userLeaving=false]
03-10 18:22:57.285 1117 4537 I wm_finish_activity: [0,106764365,78,com.android.server.telecom/.components.UserCallActivity,app-request]
03-10 18:22:57.291 21319 21319 I wm_on_create_called: [106764365,com.android.server.telecom.components.UserCallActivity,performCreate]
// 省略应用层的log
03-10 18:22:57.409 1117 4269 I wm_task_created: [82,-1]
03-10 18:22:57.409 1117 4269 I wm_stack_created: 82
03-10 18:22:57.415 1117 4269 I wm_create_task: [0,82]
03-10 18:22:57.415 1117 4269 I wm_create_activity: [0,240756465,82,com.android.dialer/com.android.incallui.InCallActivity,android.intent.action.MAIN,NULL,NULL,268697600]
// 省略应用层log
03-10 18:22:57.494 1117 3240 I wm_restart_activity: [0,240756465,82,com.android.dialer/com.android.incallui.InCallActivity]
03-10 18:22:57.495 1117 3240 I wm_set_resumed_activity: [0,com.android.dialer/com.android.incallui.InCallActivity,minimalResumeActivityLocked]
03-10 18:22:57.495 1117 3240 I wm_add_to_stopping: [0,240756465,com.android.dialer/com.android.incallui.InCallActivity,makeInvisible]
03-10 18:22:57.496 1117 3240 I wm_pause_activity: [0,240756465,com.android.dialer/com.android.incallui.InCallActivity,userLeaving=false]
03-10 18:22:57.507 1117 3240 I wm_set_resumed_activity: [0,com.android.dialer/com.android.incallui.InCallActivity,resumeTopActivityInnerLocked]
03-10 18:22:57.508 1117 3240 I wm_resume_activity: [0,240756465,82,com.android.dialer/com.android.incallui.InCallActivity]
03-10 18:22:57.527 21348 21348 I wm_on_create_called: [240756465,com.android.incallui.InCallActivity,performCreate]
03-10 18:22:57.573 21348 21348 I wm_on_start_called: [240756465,com.android.incallui.InCallActivity,handleStartActivity]
03-10 18:22:57.577 21348 21348 I wm_on_resume_called: [240756465,com.android.incallui.InCallActivity,RESUME_ACTIVITY]
03-10 18:22:57.584 21348 21348 I wm_on_top_resumed_gained_called: [240756465,com.android.incallui.InCallActivity,topStateChangedWhenResumed]
03-10 18:22:57.584 21348 21348 I wm_on_top_resumed_lost_called: [240756465,com.android.incallui.InCallActivity,topStateChangedWhenResumed]
03-10 18:22:57.587 21348 21348 I wm_on_paused_called: [240756465,com.android.incallui.InCallActivity,performPause]
03-10 18:22:57.588 1117 9217 I wm_failed_to_pause: [0,240756465,com.android.dialer/com.android.incallui.InCallActivity,(none)]
03-10 18:22:57.592 21348 21348 I wm_on_resume_called: [240756465,com.android.incallui.InCallActivity,RESUME_ACTIVITY]
03-10 18:22:57.634 21348 21348 I wm_on_top_resumed_gained_called: [240756465,com.android.incallui.InCallActivity,topStateChangedWhenResumed]

// 进入拨号的页面
03-10 18:23:02.527 1117 1204 I wm_destroy_activity: [0,106764365,78,com.android.server.telecom/.components.UserCallActivity,finish-imm:idle]
03-10 18:23:02.529 21319 21319 I wm_on_destroy_called: [106764365,com.android.server.telecom.components.UserCallActivity,performDestroy]
03-10 18:23:13.053 1117 4537 I wm_finish_activity: [0,240756465,82,com.android.dialer/com.android.incallui.InCallActivity,finish-activity]
03-10 18:23:13.059 1117 4537 I wm_focused_stack: [0,0,78,82,finish-top adjustFocusToNextFocusableStack]
03-10 18:23:13.083 1117 4537 I wm_pause_activity: [0,240756465,com.android.dialer/com.android.incallui.InCallActivity,userLeaving=false]
03-10 18:23:13.113 21348 21348 I wm_on_top_resumed_lost_called: [240756465,com.android.incallui.InCallActivity,topStateChangedWhenResumed]
03-10 18:23:13.116 21348 21348 I wm_on_paused_called: [240756465,com.android.incallui.InCallActivity,performPause]
// 省略应用层log
03-10 18:23:13.121 1117 6334 I wm_destroy_activity: [0,240756465,82,com.android.dialer/com.android.incallui.InCallActivity,finish-imm:completePausedLocked]
// 省略应用层log
03-10 18:23:13.139 21348 21348 I wm_on_destroy_called: [240756465,com.android.incallui.InCallActivity,performDestroy]
03-10 18:23:13.235 1117 1205 I wm_task_removed: [82,removeChild: last r=ActivityRecord{e59a6f1 u0 com.android.dialer/com.android.incallui.InCallActivity t-1 f}} in t=Task

{c384899 #82 visible=false type=standard mode=fullscreen translucent=true A=10113:com.android.incallui U=0 StackId=82 sz=0}
]
03-10 18:23:13.235 1117 1205 I wm_task_removed: [82,removeTask]
03-10 18:23:13.235 1117 1205 I wm_task_removed: [82,removeTask]
03-10 18:23:13.235 1117 1205 I wm_stack_removed: 82

It can be seen from the log that it took more than 5 seconds from the beginning of the operation to entering the dial page (IncallActivity). Now we begin to analyze what this process does and why it takes 5 seconds.

We know that when an Activity is started normally, AMS will execute the resumeTopActivityInnerLocked() method of ActivityStack. Will be called later

mStackSupervisor.startSpecificActivity(next, true, false);

At this point the logic comes to the ActivityStackSupervisor.startSpecificActivity method. code show as below:

void startSpecificActivity(ActivityRecord r, boolean andResume, boolean checkConfig) {
        // Is this activity's application already running?
        final WindowProcessController wpc =
                mService.getProcessController(r.processName, r.info.applicationInfo.uid);

        boolean knownToBeDead = false;
        // 无论此条件是否成立,下面的notifyUnknownVisibilityLaunchedForKeyguardTransition
           方法都会得到执行,因为realStartActivityLocked方法中也调用了次方法
        if (wpc != null && wpc.hasThread()) {
            try {
                realStartActivityLocked(r, wpc, andResume, checkConfig);
                return;
            } catch (RemoteException e) {
                Slog.w(TAG, "Exception when starting activity "
                        + r.intent.getComponent().flattenToShortString(), e);
            }

            // If a dead object exception was thrown -- fall through to
            // restart the application.
            knownToBeDead = true;
        }

        r.notifyUnknownVisibilityLaunchedForKeyguardTransition();

        final boolean isTop = andResume && r.isTopRunningActivity();
        mService.startProcessAsync(r, knownToBeDead, isTop, isTop ? "top-activity" : "activity");
    }

As analyzed above, the ActivityRecord.notifyUnknownVisibilityLaunchedForKeyguardTransition method will be executed. Let's take a look at the code for this method now.

ActivityRecord.notifyUnknownVisibilityLaunchedForKeyguardTransition()

/**
  * Suppress transition until the new activity becomes ready, otherwise the keyguard can appear
  * for a short amount of time before the new process with the new activity had the ability to
  * set its showWhenLocked flags.
  */
 void notifyUnknownVisibilityLaunchedForKeyguardTransition() {
     // No display activities never add a window, so there is no point in waiting them for
     // relayout.
     if (noDisplay || !mStackSupervisor.getKeyguardController().isKeyguardLocked()) {
         return;
     }

     mDisplayContent.mUnknownAppVisibilityController.notifyLaunched(this);
 }

The general meaning of the comment may be: you can suppress the transition before the new Activity is ready, otherwise the lock screen page may flash before the new process sets the showWhenLocked flag.

After this method, the code logic proceeds to the UnKnownAppVisibilityController.notifyLaunched() method.

/**
 * Manages the set of {@link ActivityRecord}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.
 */
class UnknownAppVisibilityController {    


    boolean allResolved() {
        return mUnknownApps.isEmpty();
    }

    void appRemovedOrHidden(@NonNull ActivityRecord activity) {
        if (DEBUG_UNKNOWN_APP_VISIBILITY) {
            Slog.d(TAG, "App removed or hidden activity=" + activity);
        }
        mUnknownApps.remove(activity);
    }

    /**
     * Notifies that {@param activity} has been launched behind Keyguard, and we need to wait until
     * it is resumed and relaid out to resolve the visibility.
     */
    // 在锁屏的状态下启动Activity的时候调用
    void notifyLaunched(@NonNull ActivityRecord activity) {
        if (DEBUG_UNKNOWN_APP_VISIBILITY) {
            Slog.d(TAG, "App launched activity=" + activity);
        }
        mUnknownApps.put(activity, UNKNOWN_STATE_WAITING_RESUME);
    }

    /**
     * Notifies that {@param activity} has finished resuming.
     */
    // Activity#onResume完成调用
    void notifyAppResumedFinished(@NonNull ActivityRecord activity) {
        if (mUnknownApps.containsKey(activity)
                && mUnknownApps.get(activity) == UNKNOWN_STATE_WAITING_RESUME) {
            if (DEBUG_UNKNOWN_APP_VISIBILITY) {
                Slog.d(TAG, "App resume finished activity=" + activity);
            }
            mUnknownApps.put(activity, UNKNOWN_STATE_WAITING_RELAYOUT);
        }
    }

    /**
     * Notifies that {@param activity} has relaid out.
     */
    // Activity layout完成
    void notifyRelayouted(@NonNull ActivityRecord activity) {
        if (!mUnknownApps.containsKey(activity)) {
            return;
        }
        if (DEBUG_UNKNOWN_APP_VISIBILITY) {
            Slog.d(TAG, "App relayouted appWindow=" + activity);
        }
        int state = mUnknownApps.get(activity);
        if (state == UNKNOWN_STATE_WAITING_RELAYOUT) {
            mUnknownApps.put(activity, UNKNOWN_STATE_WAITING_VISIBILITY_UPDATE);
            mService.notifyKeyguardFlagsChanged(this::notifyVisibilitiesUpdated,
                    activity.getDisplayContent().getDisplayId());
        }
    }

    private void notifyVisibilitiesUpdated() {
        if (DEBUG_UNKNOWN_APP_VISIBILITY) {
            Slog.d(TAG, "Visibility updated DONE");
        }
        boolean changed = false;
        for (int i = mUnknownApps.size() - 1; i >= 0; i--) {
            if (mUnknownApps.valueAt(i) == UNKNOWN_STATE_WAITING_VISIBILITY_UPDATE) {
                mUnknownApps.removeAt(i);
                changed = true;
            }
        }
        if (changed) {
            mService.mWindowPlacerLocked.performSurfacePlacement();
        }
    }
}

This kind of comment roughly means that UnKnownAppVisibilityController manages the collection of ActivityRecord, and we can't know whether the Activity is visible at this time. This happens when an Activity is started in the lock screen state. In this state, marking the App with keyguard may affect its visibility, so we need to wait for the visibility problem to be resolved so as to avoid the splash screen.

Although the translation is not accurate, at least we can understand that this type must be related to starting an Activity in the lock screen state. At least in order to clarify the final problem, we must analyze this first. As we mentioned above, the logic is executed to this kind of notifyLaunched method.

The general meaning of the annotation of this method is: to notify the Activity to be started, to start after the keyguard, we need to wait for the resume state of the Activity and the drawing has been completed at this time, which solves the problem of the visibility of the Activity. To put it bluntly, you need to wait on the lock screen page until the InCallActivity that UserCallActivity needs to process is visible.

The member variable mUnKnownApps of UnKnownAppVisibilityController records the state list of calling AppWindowToken in the locked state. It has the following states:

UNKNOWN_STATE_WATTING_RESUME wait for the execution of onResume state

UNKNOWN_STATE_WATTING_RELAYOUT wait for the layout to be executed

UNKNOWN_STATE_WATTING_VISIBILITY_UPDATE waiting for visibility update.

While waiting for the visibility update, WMS will be notified that the Flags of the keyguard has changed. Eventually, the notifyVisibilitiesUpdated method will be called. If the status of the Activity is waiting for visibility update, it will be removed from mUnKnownApps.

Eventually, if the visibility state changes, the WindowSurfacePlacer.performSurfacePlacement() method will be called. The general process after that is shown in the following sequence diagram.

Now let's focus on finding the basis for the five seconds, and what has been done in these five seconds. In the above figure, the logic has reached the AppTransistionController.transitionGoodToGo() method. Now let's take a look at what has been written in this method.

private boolean transitionGoodToGo(ArraySet<? extends WindowContainer> apps,
            ArrayMap<WindowContainer, Integer> outReasons) {
       
        // 代码省略。。。。。

        if (!mDisplayContent.mAppTransition.isTimeout()) {
            
            // 代码省略。。。。

            if (!mDisplayContent.mUnknownAppVisibilityController.allResolved()) {
                ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, "unknownApps is not empty: %s",
                            mDisplayContent.mUnknownAppVisibilityController.getDebugMessage());
                return false;
            }

            // 代码省略。。。。

        }
        return true;
    }

There is a lot of code, and only the most critical part is kept. First judge whether it has timed out, and call UnKnownAppVisibilityController.allResolved() when it has not timed out to determine whether mUnKnownApps is empty. If it is not empty, false is returned. There is such a piece of code at the beginning of the handleAppTransitionReady() function that calls the tankitionGoodToGo method.

if (!transitionGoodToGo(mDisplayContent.mOpeningApps, mTempTransitionReasons)
          || !transitionGoodToGo(mDisplayContent.mChangingContainers,
                  mTempTransitionReasons)) {
       return;
}

This means that if the transitionGoodToGo method returns false, the logic will return directly. Therefore, before the timeout period, the logic cannot be continued. And this timeout requires us to continue to explore. Now follow the source code to see if the timeout period is 5 seconds. Now the code comes to AppTransition.java.

public class AppTransition implements Dump {
    
    private static final long APP_TRANSITION_TIMEOUT_MS = 5000;

    private final static int APP_STATE_IDLE = 0;
    private final static int APP_STATE_READY = 1;
    private final static int APP_STATE_RUNNING = 2;
    private final static int APP_STATE_TIMEOUT = 3;
    private int mAppTransitionState = APP_STATE_IDLE;

    boolean isReady() {
        // 发现APP状态是READY或者TIMEOUT时,此方法为true
        return mAppTransitionState == APP_STATE_READY
                || mAppTransitionState == APP_STATE_TIMEOUT;
    }

    boolean isRunning() {
        return mAppTransitionState == APP_STATE_RUNNING;
    }

    boolean isTimeout() {
        return mAppTransitionState == APP_STATE_TIMEOUT;
    }

    /**
     * @return true if transition is not running and should not be skipped, false if transition is
     *         already running
     */
    boolean prepareAppTransitionLocked(@TransitionType int transit, boolean alwaysKeepCurrent,
            @TransitionFlags int flags, boolean forceOverride) {
        
        // 代码省略。。。        
        // 唯一出现的一次5000,就是在这里使用的。
        // mHandler延迟5秒发送了一条消息
        boolean prepared = prepare();
        if (isTransitionSet()) {
            removeAppTransitionTimeoutCallbacks();
            mHandler.postDelayed(mHandleAppTransitionTimeoutRunnable, APP_TRANSITION_TIMEOUT_MS);
        }
        return prepared;
    }

    final Runnable mHandleAppTransitionTimeoutRunnable = () -> handleAppTransitionTimeout();


    private void handleAppTransitionTimeout() {
        synchronized (mService.mGlobalLock) {
            final DisplayContent dc = mDisplayContent;
            if (dc == null) {
                return;
            }
            notifyAppTransitionTimeoutLocked();
            if (isTransitionSet() || !dc.mOpeningApps.isEmpty() || !dc.mClosingApps.isEmpty()
                    || !dc.mChangingContainers.isEmpty()) {
                ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
                            "*** APP TRANSITION TIMEOUT. displayId=%d isTransitionSet()=%b "
                                    + "mOpeningApps.size()=%d mClosingApps.size()=%d "
                                    + "mChangingApps.size()=%d",
                            dc.getDisplayId(), dc.mAppTransition.isTransitionSet(),
                            dc.mOpeningApps.size(), dc.mClosingApps.size(),
                            dc.mChangingContainers.size());

                setTimeout();
                // 超时之后,好像又调用到了WindowSurfacePlacer的performSurfacePlacement方法
                // 之后逻辑又如上面的时序图了,只不过再次调用到AppTransitionController的
                // transitionGoodToGo方法之时,返回的就是true了。之后就可以正常走                    startActivity等的逻辑了。
                mService.mWindowPlacerLocked.performSurfacePlacement();
            }
        }
    }


There is a lot of code in this category, I only picked out some important ones. The specific analysis is also written in the code comments. So far, it can be explained that the card screen is indeed the bottom layer to deal with this situation, and it is indeed 5 seconds. These 5 seconds are all App Transition related work, and I haven't understood this process yet. So I can't explain it. I hope everyone understands, or has a recommended article, and let me know. Thanks here! !

 

Guess you like

Origin blog.csdn.net/zhourui_1021/article/details/114664342