지난 번 포스팅에 이어서,
executeRequest 메서드 안도 매우 복잡하지만 startActivity가 이루어지는 부분을 보려면
startActivityUnchecked 부분을 파보도록 하겠습니다
ActivityStarter.java #startActivityUnchecked
...
/**
* Start an activity while most of preliminary checks has been done and caller has been
* confirmed that holds necessary permissions to do so.
* Here also ensures that the starting activity is removed if the start wasn't successful.
*/
private int startActivityUnchecked(final ActivityRecord r, ActivityRecord sourceRecord,
IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
int startFlags, boolean doResume, ActivityOptions options, Task inTask,
TaskFragment inTaskFragment, boolean restrictedBgActivity,
NeededUriGrants intentGrants) {
int result = START_CANCELED;
boolean startResultSuccessful = false;
final Task startedActivityRootTask;
...
final boolean isTransient = r.getOptions() != null && r.getOptions().getTransientLaunch();
try {
mService.deferWindowLayout();
Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "startActivityInner");
result = startActivityInner(r, sourceRecord, voiceSession, voiceInteractor,
startFlags, doResume, options, inTask, inTaskFragment, restrictedBgActivity,
intentGrants);
startResultSuccessful = ActivityManager.isStartResultSuccessful(result);
final boolean taskAlwaysOnTop = options != null && options.getTaskAlwaysOnTop();
// Apply setAlwaysOnTop when starting an Activity is successful regardless of creating
// a new Activity or recycling the existing Activity.
if (taskAlwaysOnTop && startResultSuccessful) {
final Task targetRootTask =
mTargetRootTask != null ? mTargetRootTask : mTargetTask.getRootTask();
targetRootTask.setAlwaysOnTop(true);
}
} finally {
Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
startedActivityRootTask = handleStartResult(r, result);
mService.continueWindowLayout();
mSupervisor.mUserLeaving = false;
....
여기선 startActivityInner를 확인해보면 된다
ActivityStarter.java #startActivityInner
...
/**
* Start an activity and determine if the activity should be adding to the top of an existing
* task or delivered new intent to an existing activity. Also manipulating the activity task
* onto requested or valid root-task/display.
*
* Note: This method should only be called from {@link #startActivityUnchecked}.
*/
// TODO(b/152429287): Make it easier to exercise code paths through startActivityInner
@VisibleForTesting
int startActivityInner(final ActivityRecord r, ActivityRecord sourceRecord,
IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
int startFlags, boolean doResume, ActivityOptions options, Task inTask,
TaskFragment inTaskFragment, boolean restrictedBgActivity,
NeededUriGrants intentGrants) {
setInitialState(r, options, inTask, inTaskFragment, doResume, startFlags, sourceRecord,
voiceSession, voiceInteractor, restrictedBgActivity);
...
final boolean isTaskSwitch = startedTask != prevTopTask && !startedTask.isEmbedded();
mTargetRootTask.startActivityLocked(mStartActivity,
topRootTask != null ? topRootTask.getTopNonFinishingActivity() : null, newTask,
isTaskSwitch, mOptions, sourceRecord);
if (mDoResume) {
final ActivityRecord topTaskActivity = startedTask.topRunningActivityLocked();
if (!mTargetRootTask.isTopActivityFocusable()
|| (topTaskActivity != null && topTaskActivity.isTaskOverlay()
&& mStartActivity != topTaskActivity)) {
// If the activity is not focusable, we can't resume it, but still would like to
// make sure it becomes visible as it starts (this will also trigger entry
// animation). An example of this are PIP activities.
// Also, we don't want to resume activities in a task that currently has an overlay
// as the starting activity just needs to be in the visible paused state until the
// over is removed.
// Passing {@code null} as the start parameter ensures all activities are made
// visible.
mTargetRootTask.ensureActivitiesVisible(null /* starting */,
0 /* configChanges */, !PRESERVE_WINDOWS);
// Go ahead and tell window manager to execute app transition for this activity
// since the app transition will not be triggered through the resume channel.
mTargetRootTask.mDisplayContent.executeAppTransition();
} else {
// If the target root-task was not previously focusable (previous top running
// activity on that root-task was not visible) then any prior calls to move the
// root-task to the will not update the focused root-task. If starting the new
// activity now allows the task root-task to be focusable, then ensure that we
// now update the focused root-task accordingly.
if (mTargetRootTask.isTopActivityFocusable()
&& !mRootWindowContainer.isTopDisplayFocusedRootTask(mTargetRootTask)) {
mTargetRootTask.moveToFront("startActivityInner");
}
mRootWindowContainer.resumeFocusedTasksTopActivities(
mTargetRootTask, mStartActivity, mOptions, mTransientLaunch);
}
}
mRootWindowContainer.updateUserRootTask(mStartActivity.mUserId, mTargetRootTask);
// Update the recent tasks list immediately when the activity starts
mSupervisor.mRecentTasks.add(startedTask);
mSupervisor.handleNonResizableTaskIfNeeded(startedTask,
mPreferredWindowingMode, mPreferredTaskDisplayArea, mTargetRootTask);
return START_SUCCESS;
}
여기서 주의깊게 봐야할 부분은
mTargetRootTask.startActivityLocked(mStartActivity,
topRootTask != null ? topRootTask.getTopNonFinishingActivity() : null, newTask,
isTaskSwitch, mOptions, sourceRecord);
여기 입니다
Task.java #startActivityLocked
...
void startActivityLocked(ActivityRecord r, @Nullable ActivityRecord focusedTopActivity,
boolean newTask, boolean isTaskSwitch, ActivityOptions options,
@Nullable ActivityRecord sourceRecord) {
Task rTask = r.getTask();
final boolean allowMoveToFront = options == null || !options.getAvoidMoveToFront();
final boolean isOrhasTask = rTask == this || hasChild(rTask);
...
if (r.mLaunchTaskBehind) {
// Don't do a starting window for mLaunchTaskBehind. More importantly make sure we
// tell WindowManager that r is visible even though it is at the back of the root
// task.
r.setVisibility(true);
ensureActivitiesVisible(null, 0, !PRESERVE_WINDOWS);
// Go ahead to execute app transition for this activity since the app transition
// will not be triggered through the resume channel.
mDisplayContent.executeAppTransition();
} else if (SHOW_APP_STARTING_PREVIEW && doShow) {
// Figure out if we are transitioning from another activity that is
// "has the same starting icon" as the next one. This allows the
// window manager to keep the previous window it had previously
// created, if it still had one.
Task baseTask = r.getTask();
if (baseTask.isEmbedded()) {
// If the task is embedded in a task fragment, there may have an existing
// starting window in the parent task. This allows the embedded activities
// to share the starting window and make sure that the window can have top
// z-order by transferring to the top activity.
baseTask = baseTask.getParent().asTaskFragment().getTask();
}
final ActivityRecord prev = baseTask.getActivity(
a -> a.mStartingData != null && a.showToCurrentUser());
r.showStartingWindow(prev, newTask, isTaskSwitch,
true /* startActivity */, sourceRecord);
}
여기서 mDisplayContent.executeAppTransition을 따라가보겠습니다
DisplayContent.java #executeAppTransition
...
void executeAppTransition() {
mTransitionController.setReady(this);
if (mAppTransition.isTransitionSet()) {
ProtoLog.w(WM_DEBUG_APP_TRANSITIONS,
"Execute app transition: %s, displayId: %d Callers=%s",
mAppTransition, mDisplayId, Debug.getCallers(5));
mAppTransition.setReady();
mWmService.mWindowPlacerLocked.requestTraversal();
}
}
WindowSurfacePlacer.java #requestTraversal
class WindowSurfacePlacer {
private static final String TAG = TAG_WITH_CLASS_NAME ? "WindowSurfacePlacer" : TAG_WM;
private final WindowManagerService mService;
...
private class Traverser implements Runnable {
@Override
public void run() {
synchronized (mService.mGlobalLock) {
performSurfacePlacement();
}
}
}
private final Traverser mPerformSurfacePlacement = new Traverser();
void requestTraversal() {
if (mTraversalScheduled) {
return;
}
// Set as scheduled even the request will be deferred because mDeferredRequests is also
// increased, then the end of deferring will perform the request.
mTraversalScheduled = true;
if (mDeferDepth > 0) {
mDeferredRequests++;
if (DEBUG) Slog.i(TAG, "Defer requestTraversal " + Debug.getCallers(3));
return;
}
mService.mAnimationHandler.post(mPerformSurfacePlacement);
}
WindowManagerService의 AnimationHandler는 아래 설명 처럼
animation이나, animation tick, layout, starting window creation 등을 실행 할 때 사용하는 handler라고 한다
/**
* Handler for things to run that have direct impact on an animation, i.e. animation tick,
* layout, starting window creation, whereas {@link H} runs things that are still important, but
* not as critical.
*/
final Handler mAnimationHandler = new Handler(AnimationThread.getHandler().getLooper());
결론적으로 AnimationHandler에 post로 mPerformSurfacePlacement를 던져주게 되는데
나중에 handler queue에서 실행되는 건 performSurfacePlacement 메서드가 실행된다 (위 WindowSurfacePlacer참조)
-> 아래 부분 부터는 알고 보니 이후에... 분석한 페이지가 더 자세합니다
안드로이드 앱 프레임워크 학습(WindowManager) 1 (tistory.com)
WindowSurfacePlacer.java #performSurfacePlacement
...
final void performSurfacePlacement() {
performSurfacePlacement(false /* force */);
}
final void performSurfacePlacement(boolean force) {
if (mDeferDepth > 0 && !force) {
mDeferredRequests++;
return;
}
int loopCount = 6;
do {
mTraversalScheduled = false;
performSurfacePlacementLoop();
mService.mAnimationHandler.removeCallbacks(mPerformSurfacePlacement);
loopCount--;
} while (mTraversalScheduled && loopCount > 0);
mService.mRoot.mWallpaperActionPending = false;
}
여기서 조금 이해가 안되는 행동을 반복한다 (아마 뭔가 히스토리가 있겠지..?)
6번 루프를 돌면서 performSurfacePlacementLoop를 실행한다
WindowSurfacePlacer.java #performSurfacePlacementLoop
...
private void performSurfacePlacementLoop() {
if (mInLayout) {
if (DEBUG) {
throw new RuntimeException("Recursive call!");
}
Slog.w(TAG, "performLayoutAndPlaceSurfacesLocked called while in layout. Callers="
+ Debug.getCallers(3));
return;
}
...
if (!mService.mForceRemoves.isEmpty()) {
// Wait a little bit for things to settle down, and off we go.
while (!mService.mForceRemoves.isEmpty()) {
final WindowState ws = mService.mForceRemoves.remove(0);
Slog.i(TAG, "Force removing: " + ws);
ws.removeImmediately();
}
Slog.w(TAG, "Due to memory failure, waiting a bit for next layout");
Object tmp = new Object();
synchronized (tmp) {
try {
tmp.wait(250);
} catch (InterruptedException e) {
}
}
}
try {
mService.mRoot.performSurfacePlacement();
mInLayout = false;
if (mService.mRoot.isLayoutNeeded()) {
if (++mLayoutRepeatCount < 6) {
requestTraversal();
} else {
Slog.e(TAG, "Performed 6 layouts in a row. Skipping");
mLayoutRepeatCount = 0;
}
} else {
mLayoutRepeatCount = 0;
}
if (mService.mWindowsChanged && !mService.mWindowChangeListeners.isEmpty()) {
mService.mH.removeMessages(REPORT_WINDOWS_CHANGE);
mService.mH.sendEmptyMessage(REPORT_WINDOWS_CHANGE);
}
} catch (RuntimeException e) {
mInLayout = false;
Slog.wtf(TAG, "Unhandled exception while laying out windows", e);
}
뭔가 250ms 기다리는 코드도 있는 걸로 봐서 layout이 시작되기 까지 시간을 계속 기다리면서 체크하는 듯하다
그 후 REPORS_WINDOWS_CHANGE로 window change 되었음을 notify 해준다
저기서 requestTraversal을 통해 다시 performSurfacePlacement 가 불리도록 해준다
결론적으로 보면 여기선 windowChange가 될 떄까지 동작 반복하며 기다리고 있는 것이다
그렇다면 windowChange를 어떻게 해주는 지를 봐야할 텐데
그 부분은
mService.mRoot.performSurfacePlacement();
여기이다
private final WindowManagerService mService;
// The root of the device window hierarchy.
RootWindowContainer mRoot;
결국 저 메서드는 windowManager의 rootWindowContainer 의 performSurfacePlacement를 호출해주는 메서드다
RootWindowContainer.java #performSurfacePlacement
...
void performSurfacePlacement() {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "performSurfacePlacement");
try {
performSurfacePlacementNoTrace();
} finally {
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
}
// "Something has changed! Let's make it correct now."
// TODO: Super long method that should be broken down...
void performSurfacePlacementNoTrace() {
if (DEBUG_WINDOW_TRACE) {
Slog.v(TAG, "performSurfacePlacementInner: entry. Called by "
+ Debug.getCallers(3));
}
...
mWmService.openSurfaceTransaction();
try {
applySurfaceChangesTransaction();
} catch (RuntimeException e) {
Slog.wtf(TAG, "Unhandled exception in Window Manager", e);
} finally {
mWmService.closeSurfaceTransaction("performLayoutAndPlaceSurfaces");
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
if (SHOW_LIGHT_TRANSACTIONS) {
Slog.i(TAG,
"<<< CLOSE TRANSACTION performLayoutAndPlaceSurfaces");
}
}
...
// Check to see if we are now in a state where the screen should
// be enabled, because the window obscured flags have changed.
mWmService.enableScreenIfNeededLocked();
surface를 만들어주고 전환까지 해줘야한다..
여기는 코드를 좀 더 공부해보고 다음 포스팅에서 다시 올리도록 하겠다
-> 약 2년 뒤에 다시 내가 쓴 블로그를 보니 아주 ㅋㅋㅋ 분석 수준이 우습다...
아마 나중에 다시 봐도 이런 기분이려나....
'안드로이드 > 안드로이드 프레임워크' 카테고리의 다른 글
Android framework StartActivity 호출 순서 (0) | 2022.05.09 |
---|---|
StartActivity 호출 시 코드 진행 순서 3 (0) | 2022.04.30 |
StartActivity 호출 시 코드 진행 순서 1 (0) | 2022.04.24 |
Dagger (0) | 2022.04.13 |
안드로이드 4대 컴포넌트 (0) | 2022.04.10 |