Android 锁屏状态下启动应用很慢的原因分析

最近在工作中遇到一个需求就是,在锁屏状态下直接拨打电话,功能实现之后测试给的反馈是操作之后到电话拨出的时间太长(大概5秒)。希望能优化一下。后来经过分析确定不是应用层的问题。然后就一顿百度、Google,发现了一篇文章专门在分析这个问题。我也是看了人家写的才准备照着这篇文章的思路分析一下源码。首先声明一下,我写的这篇文章是为了记录此问题分析的一个过程,其源码是基于Android 11(Api 30)。

话不多说,现在开始。。。

拨打电话启动的action为android.intent.action.CALL,对应处理的Activity是com.android.server.telecom/.components.UserCallActivity,而拨出电话的页面是InCallActivity。关于这个UserCallActivity,它好像挺特殊,我们不妨先看一下它的代码。

我们看到,在onCreate方法中就finish()了。

为了更清楚的表达问题我先贴一下我抓到的log,

// 在锁屏页面调用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

从log可以看出,从开始操作到进入到拨号页面(IncallActivity)大概是用了5秒多的时间。现在我们就开始分析这一过程到底做了什么,为什么耗时5秒之久。

我们知道在正常启动一个Activity时,AMS会执行到ActivityStack的resumeTopActivityInnerLocked()方法。之后会调用到

mStackSupervisor.startSpecificActivity(next, true, false);

此时逻辑就来到了ActivityStackSupervisor.startSpecificActivity方法。代码如下:

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");
    }

上面分析到,ActivityRecord.notifyUnknownVisibilityLaunchedForKeyguardTransition方法都会得到执行。现在就看一下此方法的代码吧。

ActivityRecord.notifyUnknownVisibilityLaunchedForKeyguardTransition()

扫描二维码关注公众号,回复: 13046411 查看本文章
/**
  * 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);
 }

注释的大致意思可能是:可以在新的Activity准备好之前,抑制过渡,否则锁屏页可能会在新的进程设置showWhenLocked标志之前闪现。

经过此方法之后,代码逻辑就进行到了UnKnownAppVisibilityController.notifyLaunched()方法。

/**
 * 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();
        }
    }
}

此类的注释大致意思是,UnKnownAppVisibilityController管理这ActivityRecord的集合,此时我们不能知道Activity是否可见。这发生在锁屏状态下启动一个Activity时。在这种状态下,用keyguard标志App可能会影响到它的可见性,因此我们需要一直等待知道这个可见性的问题得到解决,从而避免闪屏。

尽管翻译的不准确,但至少我们能明白,此类一定是与锁屏状态下启动一个Activity有关系的。至少为了搞清楚最终的问题,我们先分析这个肯定是没错的。上面我们提到,逻辑执行到了此类的notifyLaunched方法。

此方法的注释大致意思是:通知要启动的Activity,要在keyguard之后启动,我们需要等待Activity的resume状态并且此时已经绘制完成,解决了Activity的可见性的问题。说白了就是需要在锁屏页面一直等待到UserCallActivity所需要处理的InCallActivity处于可见状态。

UnKnownAppVisibilityController的成员变量mUnKnownApps记录了锁屏状态下调用AppWindowToken的状态列表,它有以下几个状态:

UNKNOWN_STATE_WATTING_RESUME 等待执行完onResume状态

UNKNOWN_STATE_WATTING_RELAYOUT 等待执行完layout

UNKNOWN_STATE_WATTING_VISIBILITY_UPDATE 等待可见性的更新。

在等待可见性更新的时候会通知WMS此时keyguard的Flags已经改变了。最终会调用到notifyVisibilitiesUpdated方法,如果Activity的状态是等待可见性更新,那么就从mUnKnownApps中移除。

最终如果可见性的状态发生改变就会调用WindowSurfacePlacer.performSurfacePlacement()方法。之后的大致过程如下时序图所示。

现在我们重点找一下耗时五秒的根据,以及这五秒中都做了点什么。上图中,逻辑进行到了AppTransistionController.transitionGoodToGo()方法,现在我们来看一下此方法中都做了写什么。

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;
    }

代码比较多,只保留了最关键的部分。首先判断是否超时,在未超时的时候才去调用UnKnownAppVisibilityController.allResolved(),去判断mUnKnownApps是否为空。如果不为空,返回了false。而在调用tansitionGoodToGo方法的handleAppTransitionReady()函数一开始有这样一段代码。

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

意思就是,transitionGoodToGo方法返回false,逻辑就直接return了。所以在未超时的时候,逻辑一直都是不能继续往下进行的。而这个超时就需要我们继续探索了。现在跟进源码看一下这个超时时长是否是5秒。现在代码就来到了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();
            }
        }
    }


这个类中代码很多,我只挑出了一些重要的。具体的分析也写在了代码的注释中。至此,已经能解释卡屏确实是底层对这种情况做了处理,并且也确实是5秒。这5秒钟的时间做的都是App Transition相关的工作,这一过程我还没弄懂。所以我解释不了。希望大家有明白的,或者有推荐的文章,告知一下。在此谢过!!

猜你喜欢

转载自blog.csdn.net/zhourui_1021/article/details/114664342