【Android 14源码分析】Activity启动流程-2
忽然有一天,我想要做一件事:去代码中去验证那些曾经被“灌输”的理论。
– 服装学院的IT男
本篇已收录于Activity短暂的一生系列
欢迎一起学习讨论Android应用开发或者WMS
V:WJB6995
Q:707409815
正文
由于篇幅原因,整个启动流程分为以下3篇进行分析:
Activity启动流程-1
Activity启动流程-2
Activity启动流程-3
继续上一篇执行到 TaskFragment::resumeTopActivity 分析。
当前分析进度还处于阶段一的逻辑,阶段一最后是需要触发2个逻辑:
-
- 触发 SourceActivity 执行 pause
-
- 触发创建 TargetActivity 所在进程
1. 阶段一 : system_service 处理
1.1 显示顶层 Activity–TaskFragment::resumeTopActivity
TaskFragment::resumeTopActivity 方法是 Framework 中非常常见的一个方法,方法名和它的功能是一样的:显示顶层 Activity 。
抛开多窗口场景,设备上显示 Activity 的逻辑就是显示 DefaultTaskDisplayArea 下的顶层 Task 中的顶层 Activity ,代码流程有很多场景都可能会导致屏幕显示内容的修改,也就需要执行当前方法来确保屏幕上有一个正确的 Activity 显示。
以后会经常看到这个方法,当前还是只分析 Launcher 启动“电话”的场景来看看执行逻辑。
# TaskFragmentfinal boolean resumeTopActivity(ActivityRecord prev, ActivityOptions options,boolean deferPause) {// 这里的next返回的是电话的 MainActivity,表示下一个需要显示的ActivityActivityRecord next = topRunningActivity(true /* focusableOnly */);......// 如果跳过的这些逻辑都没执行return,则正在开始执行 resume 流程,打印关键日志。 // 前面流程如果返回也有日志的打印// 打印日志,需要显示哪个Activityif (DEBUG_SWITCH) Slog.v(TAG_SWITCH, "Resuming " + next);......// 重点* 1. 这里会将 Launcher 的Activity pause 。参数是电话的 ActivityRecord// 注意 1:如果有 Activity 被 pauseBackTasks 方法触发了 pause,则返回 true,表示有 Activity 正在 pausingboolean pausing = !deferPause && taskDisplayArea.pauseBackTasks(next);......if (pausing) {ProtoLog.v(WM_DEBUG_STATES, "resumeTopActivity: Skip resume: need to"+ " start pausing");if (next.attachedToProcess()) {......} else if (!next.isProcessRunning()) {// 重点*2. 进程没有运行,则触发异步创建进程。 当前逻辑肯定是执行这一条final boolean isTop = this == taskDisplayArea.getFocusedRootTask();mAtmService.startProcessAsync(next, false /* knownToBeDead */, isTop,isTop ? HostingRecord.HOSTING_TYPE_NEXT_TOP_ACTIVITY: HostingRecord.HOSTING_TYPE_NEXT_ACTIVITY);}......// 注意2:这里会returnreturn true;} else if ............//后面还有重要逻辑,当前可忽略 }
上面说过这个函数非常复杂,在当前逻辑有2个主线:
-
- pause 当前 Activity 也就是 Launcher
-
- 异步创建“电话”的进程
在第一步将 Launcher 的 Activity 执行 pause ,这一步执行到最后也会触发"电话"应用 MainActivity 的启动。
第二步创建“电话”进程,进程创建完肯定也会执行"电话"应用 MainActivity 的启动,这么看来就有2个地方触发了。
这是因为是异步创建进程,不知道谁先执行完,但是可以明确的是,"电话"应用 MainActivity 必须有2个条件:
-
- 前一个Activity执行完pause
-
- 进程创建完成
所以无论2个分支哪一个先执行完,都需要等后一个执行完的来触发后续电话"应用 MainActivity 的启动。
这个方法还有2个需要注意的地方:
-
- 变量 pausing 表示当前是否正在执行 Activity 的 pause 流程。这个值受2个因素影响,
先看 Launcher 的 pause 流程。
1.2 pause 流程–pauseBackTasks
需要显示新的 Activity 那之前的 Activity 肯定是要执行 pause ,参数 next 为“电话”的 ActivityRecord 。
这一步主要是触发 SourceActivity 的pause 逻辑。
# TaskDisplayArea// 可以看到“电话”ActivityRecord在这里就被称为resumingboolean pauseBackTasks(ActivityRecord resuming) {final int[] someActivityPaused = {0};forAllLeafTasks(leafTask -> {// Check if the direct child resumed activity in the leaf task needed to be paused if// the leaf task is not a leaf task fragment.if (!leafTask.isLeafTaskFragment()) {// 当前不会走这里......}leafTask.forAllLeafTaskFragments((taskFrag) -> {final ActivityRecord resumedActivity = taskFrag.getResumedActivity();if (resumedActivity != null && !taskFrag.canBeResumed(resuming)) {if (taskFrag.startPausing(false /* uiSleeping*/, resuming, "pauseBackTasks")) {someActivityPaused[0]++;}}}, true /* traverseTopToBottom */);}, true /* traverseTopToBottom */);return someActivityPaused[0] > 0;}
forAllLeafTasks 和 forAllLeafTaskFragments 在WMS/AMS 常见方法调用提取有解释,那么当前这段方法其实就是让 DefaultTaskDisplayArea 下的每个叶子 LeafTaskFragments 执行 startPausing 。
# TaskFragmentboolean startPausing(boolean userLeaving, boolean uiSleeping, ActivityRecord resuming,String reason) {......// 日志输出当前 TaskFragment 和 mResumedActivity 的关系。后面会贴上日志证明// 注意这里的是需要 pause 的,不是需要 resume 的ProtoLog.d(WM_DEBUG_STATES, "startPausing: taskFrag =%s " + "mResumedActivity=%s", this,mResumedActivity);......// 后面的prev就是launcher的ActivityRecord了ActivityRecord prev = mResumedActivity;......// 输出日志ProtoLog.v(WM_DEBUG_STATES, "Moving to PAUSING: %s", prev);mPausingActivity = prev;......// 设置window状态为PAUSINGprev.setState(PAUSING, "startPausingLocked");prev.getTask().touchActiveTime();......if (prev.attachedToProcess()) {// launcher的进程肯定是满足启动条件的if (shouldAutoPip) {// 当前场景与画中画模式无关,不走这boolean didAutoPip = mAtmService.enterPictureInPictureMode(prev, prev.pictureInPictureArgs);ProtoLog.d(WM_DEBUG_STATES, "Auto-PIP allowed, entering PIP mode "+ "directly: %s, didAutoPip: %b", prev, didAutoPip);} else {// 重点*1. 上面的PIP日志没输出,肯定走的是这schedulePauseActivity(prev, userLeaving, pauseImmediately,false /* autoEnteringPip */, reason);}}......}void schedulePauseActivity(ActivityRecord prev, boolean userLeaving,boolean pauseImmediately, boolean autoEnteringPip, String reason) {// 输出日志ProtoLog.v(WM_DEBUG_STATES, "Enqueueing pending pause: %s", prev);try {// 输出events 日志EventLogTags.writeWmPauseActivity(prev.mUserId, System.identityHashCode(prev),prev.shortComponentName, "userLeaving=" + userLeaving, reason);// 重点* 构建并执行PauseActivityItemmAtmService.getLifecycleManager().scheduleTransaction(prev.app.getThread(),prev.token, PauseActivityItem.obtain(prev.finishing, userLeaving,prev.configChangeFlags, pauseImmediately, autoEnteringPip));} catch (Exception e) {......}}
最终在 TaskFragment::schedulePauseActivity 构建 PauseActivityItem 并执行了 Launcher 的 pause 事件。
这块相关的日志输入如图:
因为是后面补充的所以对象不一样,但是能确定这里处理的 TaskFragment 和 mResumedActivity 都是 Launcher 对象的.
State movement 这段的输出是在 ActivityRecord::setState 只要状态改变都会输出
1.2.1 PauseActivityItem
触发 pause 流程就是通过 PauseActivityItem 这个触发的。
# PauseActivityItem@Overridepublic void execute(ClientTransactionHandler client, ActivityClientRecord r,PendingTransactionActions pendingActions) {Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityPause");// 重点*1. 触发 handlePauseActivity 流程client.handlePauseActivity(r, mFinished, mUserLeaving, mConfigChanges, pendingActions,"PAUSE_ACTIVITY_ITEM");Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);}@Overridepublic int getTargetState() {return ON_PAUSE;}// pauser执行后调用 postExecute@Overridepublic void postExecute(ClientTransactionHandler client, IBinder token,PendingTransactionActions pendingActions) {if (mDontReport) {return;}// 重点*2. 触发启动新的ActivityActivityClient.getInstance().activityPaused(token);}
这里先执行 execute 方法也就是 Launcher 的 pause 流程,这个不是主线。
然后执行 postExecute,这里会触发新 Activity 的启动,这个流程就是阶段二。
# ActivityClientpublic void activityPaused(IBinder token) {try {getActivityClientController().activityPaused(token);} catch (RemoteException e) {e.rethrowFromSystemServer();}}
1.3 启动进程
这一步就是 TaskFragment::resumeTopActivity 触发的 TargetActivity 所在的进程创建,这一步的后续逻辑是阶段三。
2. 第一阶段总结
本篇对整个 Activity 启动流程做了介绍,然后重点分析第一阶段的事情,这一阶段的二级框图如下:
-
- 在 Launcher 进程构建了启动参数放在了 ActivityOption 中,然后通过 Bundle 传递给 system_service 端
-
- AMS 先解析参数,放在了 Request 这个类中保存
-
- AMS 构建出一个 ActivityRecord ,这个类在 system_service 端就代表着 Activity ,同时也是一个窗口容器
-
- 再构建出一个 Task 挂载到窗口树上
-
- 将 ActivityRecord 挂载到 Task 中,这样 ActivityRecord 也就挂载到窗口层级树中了
-
- 触发 Launcher 执行 pause 逻辑,也就是阶段二
-
- 触发 TargetActivity 所在的进程创建,也就是阶段三
这里再补充 system_service 处理的完整堆栈:
ActivityTaskManagerService::startActivityActivityTaskManagerService::startActivityAsUserActivityTaskManagerService::startActivityAsUserActivityStartController::obtainStarterActivityStarter::executeActivityStarter$Request::resolveActivity -- 解析启动请求参数ActivityTaskSupervisor::resolveIntentActivityTaskSupervisor::resolveActivityActivityStarter::executeRequest -- 3.3 创建ActivityRecordActivityStarter::startActivityUncheckedActivityStarter::startActivityInner -- 3.4 关键函数startActivityInnerActivityStarter::getOrCreateRootTask -- 3.4.1 创建或者拿到TaskRootWindowContainer::getOrCreateRootTaskRootWindowContainer::getOrCreateRootTaskTaskDisplayArea::getOrCreateRootTaskTaskDisplayArea::getOrCreateRootTaskTask::Build ---创建Task ActivityStarter::setNewTask -- 3.4.2 将Task与activityRecord 绑定ActivityStarer::addOrReparentStartingActivityTask::moveToFront --3.4.3 移动Task到栈顶Task::moveToFrontInnerTaskDisplayArea::positionChildAtTaskDisplayArea::positionChildTaskAtActivityTaskSupervisor::updateTopResumedActivityIfNeededActivityRecord::onTopResumedActivityChanged --触发TopResumedActivityChangeItemRootWindowContainer::resumeFocusedTasksTopActivities --3.4.4 显示ActivityTask::resumeTopActivityUncheckedLockedTask::resumeTopActivityInnerLockedTaskFragment::resumeTopActivity -- 1. 显示顶层ActivityTaskDisplayArea::pauseBackTasks -- 1.2 pause LauncherActivity WindowContainer::forAllLeafTaskTaskFragment::forAllLeafTaskFragmentsTaskFragment::startPausingTaskFragment::startPausingTaskFragment::schedulePauseActivity --构建 PauseActivityItem,这里是触发暂停launchActivityTaskManagerService::startProcessAsync -- 1.3 创建“电话”进程
2.1 容器变化
之前看过窗口层级树在冷启动前面的对比:
其中 WindowState 部分和其他 Task 和启动流程无关,先不管。
在桌面的时候 DefaultTaskDisplayArea 下只有 Launcher 所在的 Task 有元素,如下图:
- 忽略掉其他的空 Task 所有 DefaultTaskDisplayArea 下的顶层 Task 就是 Launcher 所在的 Task 1
- Task 1 下是一个 Task 8 然后内部才是 Launcher 的 ActivityRecord ,这个 ActivityRecord 目前也是整个手机系统里的顶层 Activity
- 这个时候用户角度看到的 Activity 也是 Launcher 的 Activity
在阶段一处理完后,主要操作集中在 ActivityStarter::startActivityInner 方法对窗口容器的操作,操作完后如下图:
经过阶段一的处理,已经创建了 TargetActivity 所在的 Task 和对应的 ActivityRecord ,并也挂载到 DefaultTaskDisplayArea 下了,而且还将其移到了顶部,所以得到上面主要的一个关系。
但是这个时候用户还是只能看到 Launcher 的 Activity 。这是因为 TargetActivity 都还没启动,甚至连它的进程这会都没创建。
阶段一 DefaultTaskDisplayArea 下结构已经如图了,为了能让用户的实际视觉也这样,所以要让 TargetActivity 启动起来,所以触发了 RootWindowContainer::resumeFocusedTasksTopActivities 方法,但是在执行到 TaskFragment::resumeTopActivity 方法的时候发现想显示 TargetActivity 的条件2个都没满足,所以就需要触发 Launcher 的pause 和 TargetActivity 所在进程的创建。也就是后面要看的阶段 二,三 。
2.1 后续阶段预览
按照之前说的整个启动流程分为4个阶段,目前已经分析完了第一阶段。也知道为啥要执行二,三阶段了,后面要分析的二,三阶段由于提过多次是异步的,取决执行速度,这个是不确定的。也是因为这个原因所以网上有很多博客关于 Activity 启动流程的分析都不太一样,其实也许都没有错,只是各自分析的具体执行顺序不同。
经过二,三阶段后,就要触发应用进程启动 TargetActivity 了,第一阶段和第四阶段的流程是固定的,第二,三阶段还是有点不确定性的。
既然从触发顺序上是先执行 pause 再触发进程创建,那么后面的逻辑就以先完成 completePause 再完成进程创建的执行循序分析,当然在后面的分析中,一些关键的分歧点也会着重标记,尽量让读者能区分各个场景的执行逻辑。
3. 阶段二–completePause
3.1 流程概述
应用完成 Pause 会执行 ActivityClientController::activityPaused 来通知 system_service 当前分析主流程继续看 completePause 的后续逻辑,这部分调用链如下:
调用堆栈如下:
ActivityClientController::activityPausedActivityRecord::activityPausedTaskFragment::completePauseRootWindowContainer::resumeFocusedTasksTopActivities -- 显示顶层ActivityRootWindowContainer::resumeFocusedTasksTopActivitiesTask::resumeTopActivityUncheckedLockedTask::resumeTopActivityInnerLockedTaskFragment::resumeTopActivityActivityTaskSupervisor::startSpecificActivity -- 试图启动 ActivityRootWindowContainer::ensureActivitiesVisible -- 确保设备上 Activity 的可见性RootWindowContainer::ensureActivitiesVisibleDisplayContent::ensureActivitiesVisible WindowContainer::forAllRootTasks --忽略固定逻辑Task::ensureActivitiesVisibleTask::forAllLeafTasks --忽略固定逻辑TaskFragment::updateActivityVisibilitiesEnsureActivitiesVisibleHelper::processEnsureActivitiesVisibleHelper::setActivityVisibilityStateEnsureActivitiesVisibleHelper::makeVisibleAndRestartIfNeededActivityTaskSupervisor::startSpecificActivity -- 试图启动 Activity
对应的时序图如下:
根据堆栈信息发现有2个地方都会触发 ActivityTaskSupervisor::startSpecificActivity 。
-
- resumeFocusedTasksTopActivities 流程。这个方法在阶段一看过,目的就是要显示一个顶层 Activity,但是第一阶段因为目标进程没起来,SourceActivity 也没 pause 所以最终也没触发 TargetActivity 的启动。
-
- ensureActivitiesVisible 流程。这个流程执行的概率很高,界面有个风吹草动可能影响 Activity 显示的都会执行这个方法,这个方法的目的就是确保当前设备上的 Activity 正确显示。
比如当前这个场景,一个 Activity 完成了 completePause ,那肯定要有新的 Activity 显示,那整个手机系统的 Activity 规则肯定发生了改变,所以需要执行 ensureActivitiesVisible 流程来确保正确的显示。
- ensureActivitiesVisible 流程。这个流程执行的概率很高,界面有个风吹草动可能影响 Activity 显示的都会执行这个方法,这个方法的目的就是确保当前设备上的 Activity 正确显示。
注意:ensureActivitiesVisible 流程并不是针对1个 Activity 而是整个设备上所有的 Activity ,让他们该显示显示,该隐藏隐藏。
之前看了第一阶段的窗口图:
冷启动的目标肯定是希望最后显示的是 TargetActivity ,也就是下面这图的状态:
其实在 system_service 这边的 DefaultTaskDisplayArea 并没有变化,只是用户看到的 Activity 变成了 TargetActivity 。
要实现这个最终状态就是需要目标应用端创建 TargetActivity 。而要触发这一步骤是2个条件就是:
-
- 其他 Activity 完成 pause
-
- TargetActivity 进程在运行
所以现在看到的阶段二就一直在试图来启动 TargetActivity ,这一阶段的大概流程图如下:
可以看到对应代码 TaskFragment::completePause 后有2个流程都试图执行 ActivityTaskSupervisor::startSpecificActivity 来启动 TargetActivity。
-
如果在 resumeFocusedTasksTopActivities 流程的时候进程启动了,则会走完触发 TargetActivity 的启动逻辑。如果进程还未启动,则会在 startSpecificActivity 方法里触发一下创建进程
-
resumeFocusedTasksTopActivities 流程执行完后会执行 ensureActivitiesVisible 流程来遍历整个手机系统的所有 Activity 处理他们的可见性,如果这个时候发现顶层 Activity 的ActivityRecord::attachedToProcess 返回false,则又会执行到 startSpecificActivity 。否则会根据具体情况处理一下其可见性。
这里其实还有一个可能的逻辑,那就是在 resumeFocusedTasksTopActivities 流程下进程没创建好,但是在走的 ensureActivitiesVisible 的时候进程创建好了,并触发了启动TargetActivity 的启动逻辑,那这个时候 ensureActivitiesVisible 则就“处理一下其可见性”。
有个注意点,这个图里提到的ActivityRecord::attachedToProcess 方法的返回值,并不严格等于“进程是否创建”,详细区别在 3.1.2 小节分析
理论知识建设完毕,下面来说跟代码,从 ActivityRecord::activityPaused 方法开始。
# ActivityRecordvoid activityPaused(boolean timeout) {......if (pausingActivity == this) {// 打印日志ProtoLog.v(WM_DEBUG_STATES, "Moving to PAUSED: %s %s", this,(timeout ? "(due to timeout)" : " (pause complete)"));mAtmService.deferWindowLayout();try {// 注意2个产出taskFragment.completePause(true /* resumeNext */, null /* resumingActivity */);} finally {mAtmService.continueWindowLayout();}return;} else ............}
打印如下:
WindowManager: Moving to PAUSED: ActivityRecord{1f58beb u0 com.android.launcher3/.uioverrides.QuickstepLauncher} t8} (pause complete)
流程来到 TaskFragment::completePause ,注意传递过来的参数分别为 true 和 null 。
# TaskFragment@VisibleForTestingvoid completePause(boolean resumeNext, ActivityRecord resuming) {// 拿到之前的Activity,也就是需要 pause 的ActivityRecord prev = mPausingActivity;ProtoLog.v(WM_DEBUG_STATES, "Complete pause: %s", prev);if (prev != null) {......// 设置 SourceActivity 对应的ActivityRecord 状态为PAUSEDprev.setState(PAUSED, "completePausedLocked");if (prev.finishing) {...... 如果已经finish的处理,上面还在设置 pause,那正常应该是还没finish} else if (prev.hasProcess()) {// 打印状态日志ProtoLog.v(WM_DEBUG_STATES, "Enqueue pending stop if needed: %s "+ "wasStopping=%b visibleRequested=%b", prev, wasStopping,prev.mVisibleRequested);......}else {......}if (resumeNext) {......// 重点*1. 第1个分支mRootWindowContainer.resumeFocusedTasksTopActivities(topRootTask, prev,null /* targetOptions */);......} else {......}......// 重点*2. 确保设置所有Activity正确的可见性,注意是3个参数的,且第一个参数为nullmRootWindowContainer.ensureActivitiesVisible(resuming, 0, !PRESERVE_WINDOWS);......}}
这里的 topRootTask 就是 SourceActivity “电话”的 Activity, prev 是 launcher 的 ActivityRecord , resuming 为null。
这里又分为2步走了。
3.1 第一步–resumeFocusedTasksTopActivities
执行过来的流程是上一个 Activity 完成了 pause ,那说明需要显示一个新的 Activity ,显示哪个呢?
自然是最前面的 Task 下的 最前面的 Activity ,下面看看代码中是如何实现这一步的。
直接的调用链之前看过了,所有直接看 TaskFragment::resumeTopActivity 方法,这个方法之前也看过,但是这次的逻辑肯定不一样了。
# TaskFragmentfinal boolean resumeTopActivity(ActivityRecord prev, ActivityOptions options,boolean deferPause) {......// 这一次没有要 pause 的Activity了,所以为falseboolean pausing = !deferPause && taskDisplayArea.pauseBackTasks(next);......if (pausing) {......return true;}......// ActivityRecord 下的 app 变量是否有值,并且进程已经执行if (next.attachedToProcess()) {if (DEBUG_SWITCH) {// 日志Slog.v(TAG_SWITCH, "Resume running: " + next + " stopped=" + next.mAppStopped+ " visibleRequested=" + next.isVisibleRequested());}......} else {......// 打印logProtoLog.d(WM_DEBUG_STATES, "resumeTopActivity: Restarting %s", next);// 重点* 执行startSpecificActivity mTaskSupervisor.startSpecificActivity(next, true, true);......}return true;}
这一次执行 TaskFragment::resumeTopActivity 方法和之前还说有区别的,首先由于这一次没有要 pause 的 Activity 了,所以 pausing = false ,也就不会走 “if (pausing)” 内部逻辑。
走的是下面的 ActivityTaskSupervisor::startSpecificActivity 试图启动 TargetActivity ,至于最后是不是真的能启动,还是要看进程起来没。
至于"next.attachedToProcess()"这个条件,冷启动是不会走进去的,这里的逻辑我是有疑问的,放在后面 3.1.2 讲,先看主流程。
这一步会打印日志:
WindowManager: resumeTopActivity: Restarting ActivityRecord{1cf0e8 u0 com.google.android.apps.messaging/.ui.ConversationListActivity t30}
3.1.1 关键方法–startSpecificActivity
这个方法已经提过多次了,简单说目的是想去让应用端启动 Activity ,但是如果可能应用端进程还没创建,就会触发进程创建。
# ActivityTaskSupervisorfinal ActivityTaskManagerService mService;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;// 重点* 1. 进程是否存在,且主线程已执行if (wpc != null && wpc.hasThread()) {try {// 进程存在 则执行 realStartActivityLocked 流程realStartActivityLocked(r, wpc, andResume, checkConfig);// 重点* 会返回return;} catch (RemoteException e) {Slog.w(TAG, "Exception when starting activity "+ r.intent.getComponent().flattenToShortString(), e);1111111111}......}......// 重点* 2. 触发启动进程mService.startProcessAsync(r, knownToBeDead, isTop,isTop ? HostingRecord.HOSTING_TYPE_TOP_ACTIVITY: HostingRecord.HOSTING_TYPE_ACTIVITY);}
以当前分析的条件,肯定又是触发进程创建了,所以 ActivityTaskSupervisor::realStartActivityLocked 暂时就先不看了。
3.1.2 疑问 – ActivityRecord::attachedToProcess
这一小节是一个小细节,倒也不影响主流程分析,如果进程启动的快,当前这个分析的流程是可以直接进入 realStartActivityLocked 方法启动 Activity 的。比如下面这个堆栈
07-10 23:22:32.960 18666 18944 E biubiubiu: at com.android.server.wm.ActivityTaskSupervisor.realStartActivityLocked(ActivityTaskSupervisor.java:787)
07-10 23:22:32.960 18666 18944 E biubiubiu: at com.android.server.wm.ActivityTaskSupervisor.startSpecificActivity(ActivityTaskSupervisor.java:1074)
07-10 23:22:32.960 18666 18944 E biubiubiu: at com.android.server.wm.TaskFragment.resumeTopActivity(TaskFragment.java:1551)
07-10 23:22:32.960 18666 18944 E biubiubiu: at com.android.server.wm.Task.resumeTopActivityInnerLocked(Task.java:5050)
07-10 23:22:32.960 18666 18944 E biubiubiu: at com.android.server.wm.Task.resumeTopActivityUncheckedLocked(Task.java:4980)
07-10 23:22:32.960 18666 18944 E biubiubiu: at com.android.server.wm.RootWindowContainer.resumeFocusedTasksTopActivities(RootWindowContainer.java:2296)
07-10 23:22:32.960 18666 18944 E biubiubiu: at com.android.server.wm.RootWindowContainer.resumeFocusedTasksTopActivities(RootWindowContainer.java:2282)
07-10 23:22:32.960 18666 18944 E biubiubiu: at com.android.server.wm.TaskFragment.completePause(TaskFragment.java:1816)
07-10 23:22:32.960 18666 18944 E biubiubiu: at com.android.server.wm.ActivityRecord.activityPaused(ActivityRecord.java:6399)
07-10 23:22:32.960 18666 18944 E biubiubiu: at com.android.server.wm.ActivityClientController.activityPaused(ActivityClientController.java:219)
07-10 23:22:32.960 18666 18944 E biubiubiu: at android.app.IActivityClientController$Stub.onTransact(IActivityClientController.java:663)
07-10 23:22:32.960 18666 18944 E biubiubiu: at com.android.server.wm.ActivityClientController.onTransact(ActivityClientController.java:153)
07-10 23:22:32.960 18666 18944 E biubiubiu: at android.os.Binder.execTransactInternal(Binder.java:1344)
07-10 23:22:32.960 18666 18944 E biubiubiu: at android.os.Binder.execTransact(Binder.java:1275)
但是在 TaskFragment::resumeTopActivity 方法有其实有"next.attachedToProcess()"这个条件判断进程是不是启动了,这个日志是直接走了 startSpecificActivity ,然后在 startSpecificActivity 方法发现进程启动了,所以就走进了 realStartActivityLocked 。
我的疑问点就是:
resumeTopActivity 到 realStartActivityLocked 中间的调用链就1层,执行时间完全可以忽略不计,难倒在这么短时间内进程刚好就启动了?
所以问题点就在于2个地方对于“进程是否启动的判断”条件。
# TaskFragmentfinal boolean resumeTopActivity(ActivityRecord prev, ActivityOptions options,boolean deferPause) {......// ActivityRecord 下的 app 变量是否有值,并且进程已经执行if (next.attachedToProcess()) {......} ......}
# ActivityTaskSupervisorfinal ActivityTaskManagerService mService;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;//进程是否存在,且主线程已执行if (wpc != null && wpc.hasThread()) {......}......}
2个地方都是拿到 WindowProcessController 对象,判断下是不是不为空并且 hasThread 返回 true ,那么区别就在于拿到 WindowProcessController 对象的方式不一样。
后者 startSpecificActivity 方法是通过 ATMS::startSpecificActivity ,代码如下:
# ActivityTaskManagerServiceWindowProcessController getProcessController(int pid, int uid) {final WindowProcessController proc = mProcessMap.getProcess(pid);if (proc == null) return null;if (UserHandle.isApp(uid) && proc.mUid == uid) {return proc;}return null;}
这种方式拿到的进程信息,是靠谱的,没啥问题,再看看 ActivityRecord 的。
# ActivityRecordpublic WindowProcessController app; // if non-null, hosting applicationvoid setProcess(WindowProcessController proc) {// 赋值app = proc;......}boolean hasProcess() {return app != null;}boolean attachedToProcess() {return hasProcess() && app.hasThread();}
ActivityRecord::attachedToProcess 方法没问题,但是发现 app 这边变量是通过 ActivityRecord::setProcess 方法设置的,而这个方法的调用在 ActivityTaskSupervisor::realStartActivityLocked 里,当在这里执行 ActivityRecord::setProcess 时,应用进程肯定是真的创建了,并且 ActivityRecord 下的 app 变量也有值了。
所以冷启动没有执行到 ActivityTaskSupervisor::realStartActivityLocked 方法之前 ActivityRecord::attachedToProcess 方法 肯定是返回 false 的。
3.2 第二步–ensureActivitiesVisible 流程
前面看了 resumeFocusedTasksTopActivities 的流程,挺简单的,目前暂且假设因为进程没启动,所以没有触发 TargetActivity 的启动,现在继续看 ensureActivitiesVisible 流程。
这个方法的目的之前也说过了,就是确保一下手机上所有的 Activity 的正确可见性,也就是遍历所有 Task 下的所有 ActivityRecord 然后根据对应的条件处理想要的可见性。
# RootWindowContainer// starting = null configChanges = 0 preserveWindows = falsevoid ensureActivitiesVisible(ActivityRecord starting, int configChanges,boolean preserveWindows) {ensureActivitiesVisible(starting, configChanges, preserveWindows, true /* notifyClients */);}void ensureActivitiesVisible(ActivityRecord starting, int configChanges,boolean preserveWindows, boolean notifyClients) {// 已经正常处理可见性就不重复执行了if (mTaskSupervisor.inActivityVisibilityUpdate()|| mTaskSupervisor.isRootVisibilityUpdateDeferred()) {// Don't do recursive work.return;}try {// 开始处理mTaskSupervisor.beginActivityVisibilityUpdate();// First the front root tasks. In case any are not fullscreen and are in front of home.// 遍历屏幕for (int displayNdx = getChildCount() - 1; displayNdx >= 0; --displayNdx) {final DisplayContent display = getChildAt(displayNdx);display.ensureActivitiesVisible(starting, configChanges, preserveWindows,notifyClients);}} finally {// 处理结束mTaskSupervisor.endActivityVisibilityUpdate();}}
- 遍历所有屏幕
# DisplayContentvoid ensureActivitiesVisible(ActivityRecord starting, int configChanges,boolean preserveWindows, boolean notifyClients) {......mInEnsureActivitiesVisible = true;mAtmService.mTaskSupervisor.beginActivityVisibilityUpdate();try {forAllRootTasks(rootTask -> {// 遍历所有根 Task rootTask.ensureActivitiesVisible(starting, configChanges, preserveWindows,notifyClients);});......} finally {mAtmService.mTaskSupervisor.endActivityVisibilityUpdate();mInEnsureActivitiesVisible = false;}}
- 遍历所有根 Task ,使其执行 ensureActivitiesVisible 方法
# Taskvoid ensureActivitiesVisible(@Nullable ActivityRecord starting, int configChanges,boolean preserveWindows, boolean notifyClients) {mTaskSupervisor.beginActivityVisibilityUpdate();try {// 遍历每个叶子TaskforAllLeafTasks(task -> {task.updateActivityVisibilities(starting, configChanges, preserveWindows,notifyClients);}, true /* traverseTopToBottom */);......} finally {mTaskSupervisor.endActivityVisibilityUpdate();}}
- 遍历所有叶子 Task ,使其执行 updateActivityVisibilities 方法,这个方法定义在父类 TaskFragmet 中。
# TaskFragmet// 帮助类private final EnsureActivitiesVisibleHelper mEnsureActivitiesVisibleHelper =new EnsureActivitiesVisibleHelper(this);final void updateActivityVisibilities(@Nullable ActivityRecord starting, int configChanges,boolean preserveWindows, boolean notifyClients) {mTaskSupervisor.beginActivityVisibilityUpdate();try {// 重点处理mEnsureActivitiesVisibleHelper.process(starting, configChanges, preserveWindows, notifyClients);} finally {mTaskSupervisor.endActivityVisibilityUpdate();}}
我们知道这一条线都是为了处理Activity可见的。 在这定义了一个专门的类来处理。
不过需要注意的是,这个方法会执行多次,因为他是遍历每一个符合条件的子容器,从上到下遍历。
3.2.1 EnsureActivitiesVisibleHelper::process
这个方法是调用 EnsureActivitiesVisibleHelper 的的方法,Task 通过这个方法来处理当前容器下的元素可见性。
# EnsureActivitiesVisibleHelpervoid process(@Nullable ActivityRecord starting, int configChanges, boolean preserveWindows, boolean notifyClients) {// 1. 对参数进行重置处理reset(starting, configChanges, preserveWindows, notifyClients);......final boolean resumeTopActivity = mTopRunningActivity != null&& !mTopRunningActivity.mLaunchTaskBehind&& mTaskFragment.canBeResumed(starting)&& (starting == null || !starting.isDescendantOf(mTaskFragment));// 遍历容器下所有元素for (int i = mTaskFragment.mChildren.size() - 1; i >= 0; --i) {// 获取当前子元素final WindowContainer child = mTaskFragment.mChildren.get(i);// 将当前子元素转换为TaskFragment(只有TaskFragment重写了asTaskFragment()方法),Task或ActivityRecord为null// 而Task的孩子一般就是ActivityRecord或者Task。 当前分析逻辑(大部分场景)Task下面是ActivityRecord// 所以这里childTaskFragment 为 nullfinal TaskFragment childTaskFragment = child.asTaskFragment();if (childTaskFragment != null&& childTaskFragment.getTopNonFinishingActivity() != null) {......} else {// 只有ActivityRecord重写了 asActivityRecord()setActivityVisibilityState(child.asActivityRecord(), starting, resumeTopActivity);}}}
执行到这里对当前的 Task 遍历了他下面的所有元素,我们目前关系的是 Activity 所以看对每个 ActivityRecord 做了什么 。
3.2.2 EnsureActivitiesVisibleHelper::setActivityVisibilityState
# EnsureActivitiesVisibleHelperprivate void setActivityVisibilityState(ActivityRecord r, ActivityRecord starting,final boolean resumeTopActivity) {......if (reallyVisible) {.......if (!r.attachedToProcess()) {// 主流程makeVisibleAndRestartIfNeeded(mStarting, mConfigChanges, isTop,resumeTopActivity && isTop, r);} else {......}} else {......// 不可见调用makeInvisibler.makeInvisible();}}
- 这里的 r 表示当前Task 下的 ActivityRecord 。
- 所有 ActivityRecord 都会执行这个方法,目前关心的是 SourceActivity 和 TargetActivity 。
- SourceActivity 也就是 Launcher 走到这里会执行 “r.makeInvisible();” 进而触发 onStop 逻辑
- TargetActivity 也就是 “电话” 走到这里则是去走可见逻辑
SourceActivity 的就不管了,不是主线,继续看 TargetActivity 在 makeVisibleAndRestartIfNeeded 方法是怎么处理的。
# EnsureActivitiesVisibleHelperprivate void makeVisibleAndRestartIfNeeded(ActivityRecord starting, int configChanges,boolean isTop, boolean andResume, ActivityRecord r) {......if (!r.mVisibleRequested || r.mLaunchTaskBehind) {if (DEBUG_VISIBILITY) {Slog.v(TAG_VISIBILITY, "Starting and making visible: " + r);}// 1. 设置Visibilityr.setVisibility(true);}if (r != starting) {// 2. 试图启动 ActivitymTaskFragment.mTaskSupervisor.startSpecificActivity(r, andResume,true /* checkConfig */);}}
-
- 设置可见。 这个是 ensureActivitiesVisible 流程最主要做的事,这个流程就是为了确保所有 Activity 正确的可见性 。
-
- 试图启动 Activity 。 发现当前的显示的 Activity 和正在处理的 Activity 不是一个,则视图启动。(starting 这个参数之前注释了为null)
后面的 ActivityTaskSupervisor::startSpecificActivity 方法就不看了,刚看过了,如果进程启动了才会执行后续逻辑启动 TargetActivity。
4. 阶段二总结
阶段二的逻辑其实比较简单,只是我分析的比较细节。
阶段二其实就是 SourceActivity 完成 pause 后,执行 ActivityRecord::activityPaused 流程,AMS 需要显示当前顶层 Activity 所以执行了 RootWindowContainer::resumeFocusedTasksTopActivities 方法,但是这一次是不是真的能显示顶层 Activity 还是要看其进程是否已经创建好了。
在操作了 Activity 的显示逻辑后,为了确保系统 Activity 正常的可见性,所以又执行了一次 ensureActivitiesVisible 流程。
流程相对简单,就是下面这个图:
相关文章:

【Android 14源码分析】Activity启动流程-2
忽然有一天,我想要做一件事:去代码中去验证那些曾经被“灌输”的理论。 – 服装…...

ubuntu 18.04 cuda 11.01 gpgpu-sim 裸机编译
1,环境 ubuntu 18.04 x86_64 cuda 11.01 gpgpu-sim master commit 90ec3399763d7c8512cfe7dc193473086c38ca38 2,预备环境 一个比较新的 ubuntu 18.04,为了迎合 cuda 11.01 的版本需求 安装如下软件: sudo apt-get instal…...

【Kubernetes】常见面试题汇总(五十二)
目录 116. K8S 集群服务暴露失败? 117.外网无法访问 K8S 集群提供的服务? 特别说明: 题目 1-68 属于【Kubernetes】的常规概念题,即 “ 汇总(一)~(二十二)” 。 题目 69-…...

o1-preview 在 IMO 2024 第一题的实测表现
相关博客:Learning to Reason with LLMs 以及 Introducing OpenAI o1-preview 测试了 IMO 2024 的第一题,OpenAI-o1-preview 的解题过程包括两部分:思考和推理。 正确答案是全体偶数,o1-preview 的 **思考方向正确,推…...
iOS--RunLoop原理
前言 曾经在写项目的时候遇到过这么一个问题。: 项目中添加了一个tableview,然后还有一个计时器,当滑动tableview的时候会阻塞计时器,你得执行这么一段代码后,计时器才能正常运行。 RunLoop.current.add(timer, for…...

并查集——从LeetCode题海中总结常见套路
目录 并查集定义 LeetCode128.最长连续序列 先去重再sort: 改进去重的方法: 参考: 并查集定义 在计算机科学中,并查集是一种树型的数据结构,用于处理一些不交集(Disjoint Sets)的合并及查…...
深入理解作用域【JavaScript】
一、作用域的内部原理 JavaScript 的作用域机制是理解变量如何被访问和存储的重要概念。下面详细介绍作用域的内部原理,包括编译、执行、查询、嵌套和异常处理这五个步骤。 1. 编译 在 JavaScript 的执行过程中,首要的步骤是编译。尽管JavaScript是解…...
微信小程序实战教程:如何使用map组件实现地图功能
在微信小程序中,map组件是一个非常实用的功能,它可以帮助我们快速实现地图展示、定位、标注等操作。本文将详细介绍如何在微信小程序中使用map组件,带你轻松掌握地图开发技能。 一、map组件概述 map组件是微信小程序官方提供的一个地图组件…...

张雪峰谈人工智能技术应用专业的就业前景!
一、张雪峰谈人工智能技术应用专业 在教育咨询领域,张雪峰老师以其深入浅出的讲解和前瞻性的视角,为广大学子提供了宝贵的专业选择建议。对于人工智能技术应用专业,张雪峰老师通常给予高度评价,认为这是一个充满无限可能且就业前…...

机器学习课程学习周报十五
机器学习课程学习周报十五 文章目录 机器学习课程学习周报十五摘要Abstract一、机器学习部分1. 统计推断与贝叶斯推断2. GMM和EM算法补充3. 马尔可夫链蒙特卡罗法3.1 蒙特卡罗法3.2 马尔可夫链3.3 Diffusion模型中的马尔可夫链 总结 摘要 本周的学习涵盖了统计推断和贝叶斯推断…...
rabbitMq------客户端模块
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言消费者模块信道管理模块管理的字段提供的接口 信道内存管理连接管理类 前言 在RabbitMQ中,提供服务的是信道,因此在客⼾端的实现中&…...

地理定位营销与开源AI智能名片O2O商城小程序的融合与发展
摘要:本文阐述地理定位营销的概念、手段及其在商业中的应用,探讨开源AI智能名片O2O商城小程序如何与地理定位营销相结合,为企业营销带来新的机遇与挑战。 一、引言 在当今数字化营销的时代,地理定位营销已成为一种重要的营销手段…...
解决Vue应用中遇到路由刷新后出现 404 错误
解释: Vue 应用中遇到路由刷新后出现 404 错误,通常是因为 Vue 应用是个单页应用(SPA),它通过 Vue Router 管理路由,通过 HTML5 History Mode 实现页面导航无需重新加载页面。当直接访问非首页的路由或者刷…...
在window10下使用directml加速phi-3模型的一些记录
1.安装anaconda,安装python 安装torch等参考网上资料非常多 不细描述 2.参考微软官网【在windows上通过DirectML启用Pytorch文档,检查系统版本 检查gpu版本 3.参考微软官网【在windows上通过DirectML启用Pytorch】文档,安装torch_directml模…...

通信工程学习:什么是OSPF开放式最短路径优先
OSPF:开放式最短路径优先 OSPF(Open Shortest Path First,开放式最短路径优先)是一种内部网关协议(IGP),被广泛应用于计算机网络中,特别是在构建大型和复杂的网络时。以下是对OSPF的…...

《中国电子报》报道: 安宝特AR为产线作业者的“秘密武器
近日,中国电子报在其文章《下一代工业智能终端重新定义制造业》中对安宝特的增强现实(AR)解决方案给予了高度评价,称其为产线作业者的“秘密武器”。这一创新技术改变了传统制造业的作业方式,使得操作人员能够在生产过…...

【Android】Handler消息机制
文章目录 前言概述核心组件概述Android消息机制概述 Android消息机制分析ThreadLocal的工作原理ThreadLocal基础ThreadLocal实现原理 MessageQueueLooperHandler的工作原理总结 前言 本文用于记录Android的消息机制,主要是指Handler的运行机制。部分内容参考自《An…...
大数据必懂知识点:Parquet、ORC还是Avro作为数据存储格式,哪种在性能和压缩率上更优
目录 第一章 相关理论 1.1 Parquet格式介绍 1.1.1 起源与发展 1.1.2 特点与优势 1.2 ORC格式介绍 1.3 Avro格式介绍 1.3.1 跨语言支持 1.3.2 动态映射 1.3.3 丰富的数据模式 1.3.4 数据模式灵活性 第二章 种格式性能比较 2.1 读写性能对比 2.2 查询性能对比 2.3 压…...
P1387 最大正方形
题目描述 在一个nm 的只包含 0 和 1 的矩阵里找出一个不包含 0 的最大正方形,输出边长。 输入格式 输入文件第一行为两个整数n,m(1≤n,m≤100),接下来 n 行,每行 m 个数字,用空格隔开,0 或 1。 输出格式 一个整数…...
Python知识点:如何使用Multiprocessing进行并行任务管理
开篇,先说一个好消息,截止到2025年1月1日前,翻到文末找到我,赠送定制版的开题报告和任务书,先到先得!过期不候! 如何在Python中使用Multiprocessing进行并行任务管理 在现代编程中,…...
Vim 调用外部命令学习笔记
Vim 外部命令集成完全指南 文章目录 Vim 外部命令集成完全指南核心概念理解命令语法解析语法对比 常用外部命令详解文本排序与去重文本筛选与搜索高级 grep 搜索技巧文本替换与编辑字符处理高级文本处理编程语言处理其他实用命令 范围操作示例指定行范围处理复合命令示例 实用技…...
CVPR 2025 MIMO: 支持视觉指代和像素grounding 的医学视觉语言模型
CVPR 2025 | MIMO:支持视觉指代和像素对齐的医学视觉语言模型 论文信息 标题:MIMO: A medical vision language model with visual referring multimodal input and pixel grounding multimodal output作者:Yanyuan Chen, Dexuan Xu, Yu Hu…...

剑指offer20_链表中环的入口节点
链表中环的入口节点 给定一个链表,若其中包含环,则输出环的入口节点。 若其中不包含环,则输出null。 数据范围 节点 val 值取值范围 [ 1 , 1000 ] [1,1000] [1,1000]。 节点 val 值各不相同。 链表长度 [ 0 , 500 ] [0,500] [0,500]。 …...
oracle与MySQL数据库之间数据同步的技术要点
Oracle与MySQL数据库之间的数据同步是一个涉及多个技术要点的复杂任务。由于Oracle和MySQL的架构差异,它们的数据同步要求既要保持数据的准确性和一致性,又要处理好性能问题。以下是一些主要的技术要点: 数据结构差异 数据类型差异ÿ…...
【RockeMQ】第2节|RocketMQ快速实战以及核⼼概念详解(二)
升级Dledger高可用集群 一、主从架构的不足与Dledger的定位 主从架构缺陷 数据备份依赖Slave节点,但无自动故障转移能力,Master宕机后需人工切换,期间消息可能无法读取。Slave仅存储数据,无法主动升级为Master响应请求ÿ…...

宇树科技,改名了!
提到国内具身智能和机器人领域的代表企业,那宇树科技(Unitree)必须名列其榜。 最近,宇树科技的一项新变动消息在业界引发了不少关注和讨论,即: 宇树向其合作伙伴发布了一封公司名称变更函称,因…...
Kafka主题运维全指南:从基础配置到故障处理
#作者:张桐瑞 文章目录 主题日常管理1. 修改主题分区。2. 修改主题级别参数。3. 变更副本数。4. 修改主题限速。5.主题分区迁移。6. 常见主题错误处理常见错误1:主题删除失败。常见错误2:__consumer_offsets占用太多的磁盘。 主题日常管理 …...
Oracle11g安装包
Oracle 11g安装包 适用于windows系统,64位 下载路径 oracle 11g 安装包...

Linux部署私有文件管理系统MinIO
最近需要用到一个文件管理服务,但是又不想花钱,所以就想着自己搭建一个,刚好我们用的一个开源框架已经集成了MinIO,所以就选了这个 我这边对文件服务性能要求不是太高,单机版就可以 安装非常简单,几个命令就…...

Elastic 获得 AWS 教育 ISV 合作伙伴资质,进一步增强教育解决方案产品组合
作者:来自 Elastic Udayasimha Theepireddy (Uday), Brian Bergholm, Marianna Jonsdottir 通过搜索 AI 和云创新推动教育领域的数字化转型。 我们非常高兴地宣布,Elastic 已获得 AWS 教育 ISV 合作伙伴资质。这一重要认证表明,Elastic 作为 …...