안드로이드/안드로이드 프레임워크

StartActivity 호출 시 코드 진행 순서 2

냥냥냥냥냥냥 2022. 4. 29. 01:30

 

지난 번 포스팅에 이어서,

executeRequest 메서드 안도 매우 복잡하지만 startActivity가 이루어지는 부분을 보려면

startActivityUnchecked 부분을 파보도록 하겠습니다

 

오늘의 sequence

 

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년 뒤에 다시 내가 쓴 블로그를 보니 아주 ㅋㅋㅋ 분석 수준이 우습다... 

아마 나중에 다시 봐도 이런 기분이려나....