이번에는 activity 관점에서 한 번 보려고 한다
2번 포스팅에서 시작했던 ActivityStarter를 다시 한 번 보도록 하자
바쁘신 분들을 위해 sequence diagram 첨부합니다
먼저 시작점은 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별로 처리를 해주게 된다
'안드로이드 > 안드로이드 프레임워크' 카테고리의 다른 글
안드로이드에서 Zygote가 실행되는 순서 1 (0) | 2022.05.13 |
---|---|
Android framework StartActivity 호출 순서 (0) | 2022.05.09 |
StartActivity 호출 시 코드 진행 순서 2 (0) | 2022.04.29 |
StartActivity 호출 시 코드 진행 순서 1 (0) | 2022.04.24 |
Dagger (0) | 2022.04.13 |