最近在工作中遇到一个需求就是,在锁屏状态下直接拨打电话,功能实现之后测试给的反馈是操作之后到电话拨出的时间太长(大概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()
/**
* 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相关的工作,这一过程我还没弄懂。所以我解释不了。希望大家有明白的,或者有推荐的文章,告知一下。在此谢过!!