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

StartActivity 호출 시 코드 진행 순서 3

냥냥냥냥냥냥 2022. 4. 30. 23:50

이번에는 activity 관점에서 한 번 보려고 한다

2번 포스팅에서 시작했던 ActivityStarter를 다시 한 번 보도록 하자

 

바쁘신 분들을 위해 sequence diagram 첨부합니다

오늘 분석해볼 과정에 대한 sequence

 

먼저 시작점은 startActivityUnchecked를 할 때 doResume 파라미터에 true를 넣어주고 있다

mLastStartActivityResult = startActivityUnchecked(r, sourceRecord, voiceSession,
        request.voiceInteractor, startFlags, true /* doResume */, checkedOptions,
        inTask, inTaskFragment, restrictedBgActivity, intentGrants);

 

 

ActivityStarter.java 


...
    /**
     * 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;
        ...
        
        try {
            mService.deferWindowLayout();
            Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "startActivityInner");
            result = startActivityInner(r, sourceRecord, voiceSession, voiceInteractor,   // startActivityInner 호출
                    startFlags, doResume, options, inTask, inTaskFragment, restrictedBgActivity,
                    intentGrants);
            startResultSuccessful = ActivityManager.isStartResultSuccessful(result);
            
 ...
 ...
 
     /**
     * 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);  // 여기서 mDoResume true 변경
                
           ...
           
        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.                    
                    ...
            } 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");  // 여기
                }

 

private void setInitialState(ActivityRecord r, ActivityOptions options, Task inTask,
        TaskFragment inTaskFragment, boolean doResume, int startFlags,
        ActivityRecord sourceRecord, IVoiceInteractionSession voiceSession,
        IVoiceInteractor voiceInteractor, boolean restrictedBgActivity) {
    reset(false /* clearRequest */);
    
   ...
   
        // If the caller has asked not to resume at this point, we make note
        // of this in the record so that we can skip it when trying to find
        // the top running activity.
        mDoResume = doResume;
        ...

 

복잡해보이지만 하나 하나 차근차근 보자

바로 위에 있는 setInitialState 메서드 내에서 mDoResume을 doResume으로 변경해주고 있다

(startActivityUnchecked를 할 때 doResume 파라미터에 true를 넣어주고 있다 맨 위 참고)

 

 

...
        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.                    
                    ...
            } 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");  // 여기
                }

보기에 복잡하겠지만 주석도 적어뒀다 (사실 주석 안읽어보면 히스토리 잘 모르겠어서 ㅎㅎ)

 

우리가 결론적으로 볼 부분은 아래 else 부분이긴 한데 왜 위는 아닐까?

주석과 코드를 보면 결국 Top activity가 focusable한 상황이 아니거나 혹은 topActivity가 taskOverlay인 경우 인데,
이 경우가 바로 pip 상황인 것이다
(유투브 프리미엄 사용 중에 영상 시작하고 있다가 home 이나 recent 가게 되면 조그만하게 창 떠서 실행되고 있는 상황)

 

자 그럼 다시 else를 보게 되면 

targetRootTask를 moveToFront 해준다

 

Task.java 


...
    void moveToFront(String reason) {
        moveToFront(reason, null);
    }

    void moveToFront(String reason, Task task) {
    ..
    
        moveToFrontInner(reason, task);
    }  
       
    /**
     * @param reason The reason for moving the root task to the front.
     * @param task If non-null, the task will be moved to the top of the root task.
     */
    @VisibleForTesting
    void moveToFrontInner(String reason, Task task) {
        if (!isAttached()) {
            return;
        }  
        
        ...

        final Task lastFocusedTask = isRootTask() ? taskDisplayArea.getFocusedRootTask() : null;
        if (task == null) {
            task = this;
        }
        task.getParent().positionChildAt(POSITION_TOP, task, true /* includingParents */);
        taskDisplayArea.updateLastFocusedRootTask(lastFocusedTask, reason);
    }

여기서 부터 또 피곤한 일이 시작이 되는데..

task의 getParent를 불게 되고 거기서 positionChildAt을 호출한다

이제 그러면 task이 호적 조사를 좀 해볼건데.. (jyp의 어머님이 누구니가 떠오른다..)

 

/**
 * {@link Task} is a TaskFragment that can contain a group of activities to perform a certain job.
 * Activities of the same task affinities usually group in the same {@link Task}. A {@link Task}
 * can also be an entity that showing in the Recents Screen for a job that user interacted with.
 * A {@link Task} can also contain other {@link Task}s.
 */
class Task extends TaskFragment 




/**
 * A basic container that can be used to contain activities or other {@link TaskFragment}, which
 * also able to manage the activity lifecycle and updates the visibilities of the activities in it.
 */
class TaskFragment extends WindowContainer<WindowContainer>




/**
 * Defines common functionality for classes that can hold windows directly or through their
 * children in a hierarchy form.
 * The test class is {@link WindowContainerTests} which must be kept up-to-date and ran anytime
 * changes are made to this class.
 */
class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<E>




/**
 * Container for grouping WindowContainer below DisplayContent.
 *
 * DisplayAreas are managed by a {@link DisplayAreaPolicy}, and can override configurations and
 * can be leashed.
 *
 * DisplayAreas can contain nested DisplayAreas.
 *
 * DisplayAreas come in three flavors, to ensure that windows have the right Z-Order:
 * - BELOW_TASKS: Can only contain BELOW_TASK DisplayAreas and WindowTokens that go below tasks.
 * - ABOVE_TASKS: Can only contain ABOVE_TASK DisplayAreas and WindowTokens that go above tasks.
 * - ANY: Can contain any kind of DisplayArea, and any kind of WindowToken or the Task container.
 *
 * @param <T> type of the children of the DisplayArea.
 */
public class DisplayArea<T extends WindowContainer> extends WindowContainer<T> {




/**
 * {@link DisplayArea} that represents a section of a screen that contains app window containers.
 *
 * The children can be either {@link Task} or {@link TaskDisplayArea}.
 */
final class TaskDisplayArea extends DisplayArea<WindowContainer> {

이런 식으로 되어 있다

 

Task -> TaskFragment -> WindowContainer -> DisplayArea -> TaskDisplayArea 형식이라고 그냥 간단히 보자

 

그럼 결국 아까 저 위 코드는

task.getParent().positionChildAt(POSITION_TOP, task, true /* includingParents */);

task의 parent의 postionChildAt 메서드가 불리게 될 것이다

 

 

 

 

TaskDisplayArea.java 


    @Override
    void positionChildAt(int position, WindowContainer child, boolean includingParents) {
        if (child.asTaskDisplayArea() != null) {
            super.positionChildAt(position, child, includingParents);
        } else if (child.asTask() != null) {
            positionChildTaskAt(position, child.asTask(), includingParents);  // 여기
        } else {
            throw new IllegalArgumentException(
                    "TaskDisplayArea can only position Task and TaskDisplayArea, but found "
                            + child);
        }
    }
    
    private void positionChildTaskAt(int position, Task child, boolean includingParents) {
        final boolean moveToTop = position >= getChildCount() - 1;
        final boolean moveToBottom = position <= 0;    
        
        ...
        
        // Update the top resumed activity because the preferred top focusable task may be changed.
        mAtmService.mTaskSupervisor.updateTopResumedActivityIfNeeded();

TaskSupervisor의 updateTopResumedActivityIfNeeded를 확인해보자

 

 

 

ActivityTaskSupervisor.java #updateTopResumedActivityIfNeeded


    /**
     * Updates the record of top resumed activity when it changes and handles reporting of the
     * state changes to previous and new top activities. It will immediately dispatch top resumed
     * state loss message to previous top activity (if haven't done it already). After the previous
     * activity releases the top state and reports back, message about acquiring top state will be
     * sent to the new top resumed activity.
     */
    void updateTopResumedActivityIfNeeded() {
        final ActivityRecord prevTopActivity = mTopResumedActivity;
        final Task topRootTask = mRootWindowContainer.getTopDisplayFocusedRootTask();
        if (topRootTask == null || topRootTask.getTopResumedActivity() == prevTopActivity) {
            if (mService.isSleepingLocked()) {
                // There won't be a next resumed activity. The top process should still be updated
                // according to the current top focused activity.
                mService.updateTopApp(null /* topResumedActivity */);
            }
            return;
        }
        
        ...
        
        scheduleTopResumedActivityStateIfNeeded();

        mService.updateTopApp(mTopResumedActivity);
    }

이 메서드 내에서는 scheduleTopResumedActivityStateIfNeeded 메서드를 확인 해보자

 

 

 

 

ActivityTaskSupervisor.java  #scheduleTopResumedActivityStateIfNeeded


    /**
     * Cached value of the topmost resumed activity in the system. Updated when new activity is
     * resumed.
     */
    private ActivityRecord mTopResumedActivity;

    /** Schedule top resumed state change if previous top activity already reported back. */
    private void scheduleTopResumedActivityStateIfNeeded() {
        if (mTopResumedActivity != null && !mTopResumedActivityWaitingForPrev) {
            mTopResumedActivity.scheduleTopResumedActivityChanged(true /* onTop */);
        }
    }

결국 ActivityRecord의 scheduleTopResumedActivityChanged를 봐야한다~

 

 

 

 

ActivityRecord.java  #scheduleTopResumedActivityChanged


    final ActivityTaskManagerService mAtmService;
    
    ...
    
	boolean scheduleTopResumedActivityChanged(boolean onTop) {
        if (!attachedToProcess()) {
            ProtoLog.w(WM_DEBUG_STATES,
                    "Can't report activity position update - client not running, "
                            + "activityRecord=%s", this);
            return false;
        }
        try {
            ProtoLog.v(WM_DEBUG_STATES, "Sending position change to %s, onTop: %b",
                    this, onTop);

            mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), appToken,
                    TopResumedActivityChangeItem.obtain(onTop));  // 여기 
        } catch (RemoteException e) {
            // If process died, whatever.
            Slog.w(TAG, "Failed to send top-resumed=" + onTop + " to " + this, e);
            return false;
        }
        return true;
    }

결국 여기선 ClientLifeCycleManager의 메서드를 봐야한다

 

 

 

 

ClientLifeCycleManager.java  #scheduleTransaction


...

    /**
     * Schedule a transaction, which may consist of multiple callbacks and a lifecycle request.
     * @param transaction A sequence of client transaction items.
     * @throws RemoteException
     *
     * @see ClientTransaction
     */
    void scheduleTransaction(ClientTransaction transaction) throws RemoteException {
        final IApplicationThread client = transaction.getClient();
        transaction.schedule();
        if (!(client instanceof Binder)) {
            // If client is not an instance of Binder - it's a remote call and at this point it is
            // safe to recycle the object. All objects used for local calls will be recycled after
            // the transaction is executed on client in ActivityThread.
            transaction.recycle();
        }
    }

...

    /**
     * Schedule a single lifecycle request or callback to client activity.
     * @param client Target client.
     * @param activityToken Target activity token.
     * @param stateRequest A request to move target activity to a desired lifecycle state.
     * @throws RemoteException
     *
     * @see ClientTransactionItem
     */
    void scheduleTransaction(@NonNull IApplicationThread client, @NonNull IBinder activityToken,
            @NonNull ActivityLifecycleItem stateRequest) throws RemoteException {
        final ClientTransaction clientTransaction = transactionWithState(client, activityToken,
                stateRequest);
        scheduleTransaction(clientTransaction);
    }

자 그럼 transaction.schedule을 보러 가보자

 

 

 

ClientTranaction.java  #scheduleTransaction


...
    /** Target client. */
    private IApplicationThread mClient;
    
    ...
    
     /**
     * Schedule the transaction after it was initialized. It will be send to client and all its
     * individual parts will be applied in the following sequence:
     * 1. The client calls {@link #preExecute(ClientTransactionHandler)}, which triggers all work
     *    that needs to be done before actually scheduling the transaction for callbacks and
     *    lifecycle state request.
     * 2. The transaction message is scheduled.
     * 3. The client calls {@link TransactionExecutor#execute(ClientTransaction)}, which executes
     *    all callbacks and necessary lifecycle transitions.
     */
    public void schedule() throws RemoteException {
        mClient.scheduleTransaction(this);
    }

mClient는 IApplicationThread이다

이런 형식의 aidl 파일은IApplicationThread.Stub 형태로 쓰이는 부분을 찾아야 한다 

찾아보면 아래처럼 ApplicationThread가 실제 구현체 부분임

 

private class ApplicationThread extends IApplicationThread.Stub {
    private static final String DB_INFO_FORMAT = "  %8s %8s %14s %14s  %s";

 

 

ApplicationThread.java  #scheduleTransaction


/**
 * This manages the execution of the main thread in an
 * application process, scheduling and executing activities,
 * broadcasts, and other operations on it as the activity
 * manager requests.
 *
 * {@hide}
 */
public final class ActivityThread extends ClientTransactionHandler
        implements ActivityThreadInternal {
        
        ...

    private class ApplicationThread extends IApplicationThread.Stub {
        private static final String DB_INFO_FORMAT = "  %8s %8s %14s %14s  %s";
        
        ...


        @Override
        public void scheduleTransaction(ClientTransaction transaction) throws RemoteException {
            ActivityThread.this.scheduleTransaction(transaction);
        }
        
        ...
        
    }

결국 위의 코드는 IApplicationThread.Stub을 상속받은 ApplicationThread의 schduleTransaction을 호출 하면

ActivityThread에 선언되어 있는 schduleTransaction를 호출하게 되는데 ActivityThread 내부에는 해당 메서드가 없다

따라서 결국 그 부모인 ClientTransactionHandler를 봐야하는 것이다

 

/**
 * Defines operations that a {@link android.app.servertransaction.ClientTransaction} or its items
 * can perform on client.
 * @hide
 */
public abstract class ClientTransactionHandler {

    // Schedule phase related logic and handlers.

    /** Prepare and schedule transaction for execution. */
    void scheduleTransaction(ClientTransaction transaction) {
        transaction.preExecute(this);
        sendMessage(ActivityThread.H.EXECUTE_TRANSACTION, transaction);
    }
    
    ...
    
    /**
     * Get the {@link TransactionExecutor} that will be performing lifecycle transitions and
     * callbacks for activities.
     */
    abstract TransactionExecutor getTransactionExecutor();

    abstract void sendMessage(int what, Object obj);

이 코드에서는 결국 아래의 what을 메세지에 던저 주고 그것을 ActivityThread내부에서 처리해주게 된다

 

ActivityThread.H.EXECUTE_TRANSACTION

 

 

 

ApplicationThread.java 


    // An executor that performs multi-step transactions.
    private final TransactionExecutor mTransactionExecutor = new TransactionExecutor(this);
    
   class H extends Handler {
    ...
    
            public static final int EXECUTE_TRANSACTION = 159;
            
            ...
            
        public void handleMessage(Message msg) {
            if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
            switch (msg.what) {
            
            ...
            
                case EXECUTE_TRANSACTION:
                    final ClientTransaction transaction = (ClientTransaction) msg.obj;
                    mTransactionExecutor.execute(transaction);
                    if (isSystem()) {
                        // Client transactions inside system process are recycled on the client side
                        // instead of ClientLifecycleManager to avoid being cleared before this
                        // message is handled.
                        transaction.recycle();
                    }
                    // TODO(lifecycler): Recycle locally scheduled transactions.
                    break;

이제 거의 다왔다 

TransactionExecutor의 execute 코드를 한 번 보자

 

 

/**
 * Resolve transaction.
 * First all callbacks will be executed in the order they appear in the list. If a callback
 * requires a certain pre- or post-execution state, the client will be transitioned accordingly.
 * Then the client will cycle to the final lifecycle state if provided. Otherwise, it will
 * either remain in the initial state, or last state needed by a callback.
 */
public void execute(ClientTransaction transaction) {
    if (DEBUG_RESOLVER) Slog.d(TAG, tId(transaction) + "Start resolving transaction");

    final IBinder token = transaction.getActivityToken();
    
    ...
    
        executeCallbacks(transaction);

        executeLifecycleState(transaction);
        mPendingActions.clear();
        if (DEBUG_RESOLVER) Slog.d(TAG, tId(transaction) + "End resolving transaction");
    }

executeCallbacks 부분을 확인해보면

 

 

    /** Cycle through all states requested by callbacks and execute them at proper times. */
    @VisibleForTesting
    public void executeCallbacks(ClientTransaction transaction) {
        final List<ClientTransactionItem> callbacks = transaction.getCallbacks();
        if (callbacks == null || callbacks.isEmpty()) {
            // No callbacks to execute, return early.
            return;
        }
        
        ...
        
        final int size = callbacks.size();
        for (int i = 0; i < size; ++i) {
            final ClientTransactionItem item = callbacks.get(i);
            if (DEBUG_RESOLVER) Slog.d(TAG, tId(transaction) + "Resolving callback: " + item);
            final int postExecutionState = item.getPostExecutionState();
            final int closestPreExecutionState = mHelper.getClosestPreExecutionState(r,
                    item.getPostExecutionState());
            if (closestPreExecutionState != UNDEFINED) {
                cycleToPath(r, closestPreExecutionState, transaction);
            }
            
            item.execute(mTransactionHandler, token, mPendingActions);
            item.postExecute(mTransactionHandler, token, mPendingActions);

 

 

/**
 * Transition the client between states with an option not to perform the last hop in the
 * sequence. This is used when resolving lifecycle state request, when the last transition must
 * be performed with some specific parameters.
 */
private void cycleToPath(ActivityClientRecord r, int finish, boolean excludeLastState,
        ClientTransaction transaction) {
    final int start = r.getLifecycleState();
    if (DEBUG_RESOLVER) {
        Slog.d(TAG, tId(transaction) + "Cycle activity: "
                + getShortActivityName(r.token, mTransactionHandler)
                + " from: " + getStateName(start) + " to: " + getStateName(finish)
                + " excludeLastState: " + excludeLastState);
    }
    final IntArray path = mHelper.getLifecyclePath(start, finish, excludeLastState);
    performLifecycleSequence(r, path, transaction);
}

/** Transition the client through previously initialized state sequence. */
private void performLifecycleSequence(ActivityClientRecord r, IntArray path,
        ClientTransaction transaction) {
    final int size = path.size();
    for (int i = 0, state; i < size; i++) {
        state = path.get(i);
        if (DEBUG_RESOLVER) {
            Slog.d(TAG, tId(transaction) + "Transitioning activity: "
                    + getShortActivityName(r.token, mTransactionHandler)
                    + " to state: " + getStateName(state));
        }
        switch (state) {
            case ON_CREATE:
                mTransactionHandler.handleLaunchActivity(r, mPendingActions,
                        null /* customIntent */);
                break;
            case ON_START:
                mTransactionHandler.handleStartActivity(r, mPendingActions,
                        null /* activityOptions */);
                break;
            case ON_RESUME:
                mTransactionHandler.handleResumeActivity(r, false /* finalStateRequest */,
                        r.isForward, "LIFECYCLER_RESUME_ACTIVITY");
                break;
            case ON_PAUSE:
                mTransactionHandler.handlePauseActivity(r, false /* finished */,
                        false /* userLeaving */, 0 /* configChanges */, mPendingActions,
                        "LIFECYCLER_PAUSE_ACTIVITY");
                break;
            case ON_STOP:
                mTransactionHandler.handleStopActivity(r, 0 /* configChanges */,
                        mPendingActions, false /* finalStateRequest */,
                        "LIFECYCLER_STOP_ACTIVITY");
                break;
            case ON_DESTROY:
                mTransactionHandler.handleDestroyActivity(r, false /* finishing */,
                        0 /* configChanges */, false /* getNonConfigInstance */,
                        "performLifecycleSequence. cycling to:" + path.get(size - 1));
                break;
            case ON_RESTART:
                mTransactionHandler.performRestartActivity(r, false /* start */);
                break;
            default:
                throw new IllegalArgumentException("Unexpected lifecycle state: " + state);
        }
    }
}

각 state별로 처리를 해주게 된다