본문 바로가기

Android/Tech

Activity Lifecycle 의 각 콜백 메서드에서는 어떤 처리를 하는가?

Unsplash, Mike L.

동기

최근 면접 준비를 위해 안드로이드 지식에 대해 다시금 탐구하고 있습니다. 가장 최초로 탐구하려고 했던 주제는 Activity Lifecycle 입니다. 물론 어떠한 순서로 호출이 되고, 특정한 상황에는 또 어떻게 호출되는지에 대해서도 알고 있지만, 호출 되는 시점에 어떠한 일이 발생하는지, 화면은 언제 보여지고 또 언제 숨겨지는지, 숨겨지는 것과 별개로 또 언제 제거되는지 등에 대해서는 잘 모르고 있어 학습하고 포스팅하게 되었습니다.

 


상속 구조

 

Activity 는 기본적으로 위와 같은 구조를 지닙니다. 이후 AppCompatActivity 의 하위 타입인지, 아니면 ComponentActivity  의 하위 타입인지에 따라 다르긴 합니다만, 일단 Activity 부터는 기본적으로 같습니다.

그럼, 차근 차근 알아볼까요.

 

Activity 는 어떻게 생성되는가?

Activity 가 생성되기 위해서는 Instrumentation 객체가 필요합니다. 해당 객체는 Activity 생성의 책임을 갖고 있는 ActivityThread 가 직접 생성하여 보유하고 있습니다. Activity 객체의 반환은 performLaunchActivity() 메서드에서 이루어집니다.

 

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
	...
}

 

우리가 안드로이드 프로그래밍을 하면서 자주 만나게 되는 Context 도 여기서 생성되어 Activity 객체에 attach() 되는 형태입니다.

 

내부에서는 Instrumentation 객체의 newActivity() 메서드를 실행하여 Activity 객체를 반환 받습니다. 이후 생성된 Activity 객체의 performActivity() 메서드를 호출합니다.

 

if (r.isPersistable()) {
    mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
} else {
    mInstrumentation.callActivityOnCreate(activity, r.state);
}

 

r.isPersistable() 메서드는 PersistableMode 와 관련이 있는 요소인데, 이번 설명과는 거리가 조금 있으니 자세히 다루지 않습니다. 기본적으로 Persistable 하지 않기 때문에 else 문의 내용이 실행됩니다.

 

public void callActivityOnCreate(Activity activity, Bundle icicle) {
    prePerformCreate(activity);
    activity.performCreate(icicle);
    postPerformCreate(activity);
}

 

세 줄의 코드가 전부인데, Activity.performCreate() 메서드를 알아보면 되겠습니다. 전, 후로 호출되는 메서드는 prePerfromCreate() 메서드와 postPerformCreate() 메서드로 시간 순서대로 진행되는 것을 보여주는 직관적 구조입니다. 전, 후에 호출되는 메서드들은 현재 처리 중인 Activity 가 해당 Activity 라는 것을 마킹하는 역할을 수행합니다.

onCreate()

public void onCreate(@Nullable Bundle savedInstanceState,
        @Nullable PersistableBundle persistentState) {
    onCreate(savedInstanceState);
}

 

우리가 알고 있는 onCreate() 의 기본 명세입니다. 이 메서드의 콜 사이트를 찾아보면 되겠습니다.

onCreate() 뿐만 아니라, 모든 Lifecycle Callback 메서드는 perform${Lifecycle Callback Method name}() 의 형태를 따릅니다. 즉, onCreate() 의 경우, performCreate() 메서드입니다.

 

@UnsupportedAppUsage
final void performCreate(Bundle icicle, PersistableBundle persistentState) {
    dispatchActivityPreCreated(icicle);
    mCanEnterPictureInPicture = true;
    // initialize mIsInMultiWindowMode and mIsInPictureInPictureMode before onCreate
    final int windowingMode = getResources().getConfiguration().windowConfiguration
            .getWindowingMode();
    mIsInMultiWindowMode = inMultiWindowMode(windowingMode);
    mIsInPictureInPictureMode = windowingMode == WINDOWING_MODE_PINNED;
    restoreHasCurrentPermissionRequest(icicle);
    if (persistentState != null) {
        onCreate(icicle, persistentState);
    } else {
        onCreate(icicle);
    }
    EventLogTags.writeWmOnCreateCalled(mIdent, getComponentName().getClassName(),
            "performCreate");
    mActivityTransitionState.readState(icicle);

    mVisibleFromClient = !mWindow.getWindowStyle().getBoolean(
            com.android.internal.R.styleable.Window_windowNoDisplay, false);
    mFragments.dispatchActivityCreated();
    mActivityTransitionState.setEnterActivityOptions(this, getActivityOptions());
    dispatchActivityPostCreated(icicle);
}

 

dispatchActivityPreCreated() 부터  onCreate() 되기 전까지의 대부분의 코드는, Activity 에 적용될 컬러 템플릿이나 분할 화면 등을 위한 전처리를 진행하게끔 합니다.

 

이후 persistentState 의 값에 따라 서로 다른 onCreate() 메서드를 호출하는데요. persistentState 가 null 이 아닌 경우는 이전에 서술했던 PersistableMode 관련 내용이며, 디스크에 간단한 값을 저장하기 위해 사용되는 객체입니다. Manifest 에 Activity 를 선언할 때 관련한 설정을 수행할 수 있으며, 별도로 이에 관한 설정을 하지 않는다면 else 문의 내용이 실행될 것입니다.

 

즉, onCreate() 는 우리가 잘 알고 있듯이 Activity 생성 직후에 호출됩니다. 단, 여기서 '생성' 은 객체 그 자체의 생성도 맞지만, '객체 생성 이후 사용을 위한 처리' 에 더 가까운 의미로 보입니다.

onStart()

이제 어느 정도 구조를 파악하였으니, 각 perform{Lifecycle Callback Method name}() 메서드만 파악하면 될 것입니다.

 

final void performStart(String reason) {
    if (Trace.isTagEnabled(Trace.TRACE_TAG_WINDOW_MANAGER)) {
        Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "performStart:"
                + mComponent.getClassName());
    }
    dispatchActivityPreStarted();
    mActivityTransitionState.setEnterActivityOptions(this, getActivityOptions());
    mFragments.noteStateNotSaved();
    mCalled = false;
    mFragments.execPendingActions();
    final long startTime = SystemClock.uptimeMillis();
    mInstrumentation.callActivityOnStart(this);
    final long duration = SystemClock.uptimeMillis() - startTime;
    EventLogTags.writeWmOnStartCalled(mIdent, getComponentName().getClassName(), reason,
            duration);

    if (!mCalled) {
        throw new SuperNotCalledException(
            "Activity " + mComponent.toShortString() +
            " did not call through to super.onStart()");
    }
    mFragments.dispatchStart();
    mFragments.reportLoaderStart();

    // Warn app developers if the dynamic linker logged anything during startup.
    boolean isAppDebuggable =
            (mApplication.getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
    if (isAppDebuggable) {
        String dlwarning = getDlWarning();
        if (dlwarning != null) {
            String appName = getApplicationInfo().loadLabel(getPackageManager())
                    .toString();
            String warning = "Detected problems with app native libraries\n" +
                             "(please consult log for detail):\n" + dlwarning;
            if (isAppDebuggable) {
                  new AlertDialog.Builder(this).
                      setTitle(appName).
                      setMessage(warning).
                      setPositiveButton(android.R.string.ok, null).
                      setCancelable(false).
                      show();
            } else {
                Toast.makeText(this, appName + "\n" + warning, Toast.LENGTH_LONG).show();
            }
        }
    }

    GraphicsEnvironment.getInstance().showAngleInUseDialogBox(this);

    mActivityTransitionState.enterReady(this);
    dispatchActivityPostStarted();
    Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
}

 

파라미터로 넘겨지는 reason  해당 메서드가 어느 시점에 호출됐는지에 대한 정보를 의미합니다. Activity 가 생성되고, 그에 따라 이어져 발생한 performStart() 인지, 아니면 onRestart() 에서 발생한 performStart() 인지 말입니다. 그렇다고 해서 이것이 유의미한 정보는 아닙니다. 단순히 이벤트 로그를 위해 사용되니까요.

 

onCreate() 메서드에 비해 그다지 많은 작업이 이루어지지는 않습니다. 다만, Fragment와 관련된 작업들이 조금 진행됩니다. Fragment 에 대한 작업을 수행할 수 있도록 준비하는 작업들입니다. 

 

또 재미있는 점은, onCreate() 메서드의 경우 Instrumentation 객체가 생성 및 콜백 메서드 호출의 역할을 했지만 onStart() 는 ActivityThread 에서 과정을 진행하고, Instrumentation 객체는 콜백 메서드 호출만 담당합니다. 이러한 구조는 onPerformDestroy() 까지 이어지고요.

 

onCreate() 메서드와 똑같이 dispatchActivitiyPreStarted(), dispatcherActivityPostStarted() 메서드가 실행되는데, 해당 작업에는 별도의 추가 작업이 거의 없습니다. 이러한 메서드들은 ActivityLifecycleCallbacks 인터페이스 구현체가 실행합니다만, 다른 콜백 메서드에 대한 정의는 있어도 onStarted() 메서드와 관련된 메서드들에 대한 정의는 존재하지 않습니다. 단 하나 있는데, Activity 의 재생성의 역할을 담당하는 ActivityRecreator 내에서 onActivityPaused() 메서드에서 사용할 플래그를 위한 값 할당 뿐입니다.

 

여기서부터, Activity 와는 조금 다르게 진행되어야만 합니다. 우리는 onStart() 메서드가 호출된 시점에는 화면이 눈에 보인다는 것을 알고 있거든요. performStart() 메서드가 호출되기 전에, ActivityThread 클래스의 handleStartActivity() 메서드가 호출됩니다.

 

@Override
public void handleStartActivity(IBinder token, PendingTransactionActions pendingActions) {
	...
}

 

코드가 너무 길어서 따로 가져오지는 않았습니다. 내부에서는 다음과 같은 작업들을 수행합니다.

  1. 예정된 GC 가 있다면 동작하지 않도록 강제
  2. onPostCreate() 메서드 호출하여 타이틀 표시
  3. updateVisibility() 메서드 호출하여 화면 표시

onResume()

onStart() 콜백 메서드에서 handleStartActivity() 메서드가 호출되는 것을 알았으니, 앞으로는 handle${Lifecycle Callback Method Name}() 메서드를 먼저 살펴보아야겠지요.

 

handleResumeActivity() 메서드에서는 다음과 같은 작업들을 수행합니다.

    1. 예정된 GC 가 있다면 동작하지 않도록 강제
  1. Window 가 WindowManager 에 추가되지 않았다면 추가
  2. 해당 Activity 를 제외하고 화면에 표시되는 Window 객체가 있다면 제거
  3. 그 외의 가시화 처리를 진행

즉, 실제로 화면에 보이도록 수행하는 부분입니다. 이후에는 performResume() 메서드가 호출됩니다.

 

final void performResume(boolean followedByPause, String reason) {
    dispatchActivityPreResumed();
    performRestart(true /* start */, reason);

    mFragments.execPendingActions();

    mLastNonConfigurationInstances = null;

    if (mAutoFillResetNeeded) {
        // When Activity is destroyed in paused state, and relaunch activity, there will be
        // extra onResume and onPause event,  ignore the first onResume and onPause.
        // see ActivityThread.handleRelaunchActivity()
        mAutoFillIgnoreFirstResumePause = followedByPause;
        if (mAutoFillIgnoreFirstResumePause && DEBUG_LIFECYCLE) {
            Slog.v(TAG, "autofill will ignore first pause when relaunching " + this);
        }
    }

    mCalled = false;
    // mResumed is set by the instrumentation
    mInstrumentation.callActivityOnResume(this);
    EventLogTags.writeWmOnResumeCalled(mIdent, getComponentName().getClassName(), reason);
    if (!mCalled) {
        throw new SuperNotCalledException(
            "Activity " + mComponent.toShortString() +
            " did not call through to super.onResume()");
    }

    // invisible activities must be finished before onResume() completes
    if (!mVisibleFromClient && !mFinished) {
        Log.w(TAG, "An activity without a UI must call finish() before onResume() completes");
        if (getApplicationInfo().targetSdkVersion
                > android.os.Build.VERSION_CODES.LOLLIPOP_MR1) {
            throw new IllegalStateException(
                    "Activity " + mComponent.toShortString() +
                    " did not call finish() prior to onResume() completing");
        }
    }

    // Now really resume, and install the current status bar and menu.
    mCalled = false;

    mFragments.dispatchResume();
    mFragments.execPendingActions();

    onPostResume();
    if (!mCalled) {
        throw new SuperNotCalledException(
            "Activity " + mComponent.toShortString() +
            " did not call through to super.onPostResume()");
    }
    dispatchActivityPostResumed();
}

 

다짜고짜 performRestart() 메서드를 실행합니다. 해당 메서드는 당연히 Activity 가 재시작될 때 호출되는 onRestart() 콜백 메서드와 관련이 있는 메서드고요. 내부를 살펴보니, mStopped 변수가 true 일 때에만 특정 작업을 수행하므로, 크게 신경 쓸 부분은 아닙니다.

 

onPostResume() 메서드에서는 Window 객체를 얻어 와 최소한의 검증을 진행한 후, 액션바를 표시해야 한다면 표시합니다.

 

이후에는 역시 Instrumentation 객체에 의해 콜백 메서드가 호출됩니다. onActivityResumed() 메서드 역시 onStart() 메서드와 비슷하게 ActivityLifecycleCallbacks 구현체들도 별도의 작업을 수행하지 않습니다. 

onPause()

@Override
public void handlePauseActivity(IBinder token, boolean finished, boolean userLeaving,
        int configChanges, PendingTransactionActions pendingActions, String reason) {
    ActivityClientRecord r = mActivities.get(token);
    if (r != null) {
        if (userLeaving) {
            performUserLeavingActivity(r);
        }

        r.activity.mConfigChangeFlags |= configChanges;
        performPauseActivity(r, finished, reason, pendingActions);

        // Make sure any pending writes are now committed.
        if (r.isPreHoneycomb()) {
            QueuedWork.waitToFinish();
        }
        mSomeActivitiesChanged = true;
    }
}

 

먼저, handlePauseActivity() 메서드입니다. userLeaving 파라미터는 PIP(Picutre In Picture) 모드를 진행하기 위한 플래그입니다. PIP 모드는 화면 속 화면 기능인데, 해당 메서드가 Activity 를 종료하기 위해 호출된 것인지, 아니면 PIP 모드 사용을 위해 호출된 것인지 판별하기 위함입니다. Manifest 에 PIP 모드 사용을 위한 속성을 추가하고 onPause() 가 호출되도록 하면 해당 변수가 true 로 할당되어 넘어갑니다.

 

 

즉, 끝내는 것이 아닌 떠나는 것이라고 표시하는 겁니다. 이후에는 performResume() 메서드가 호출됩니다.

 

final void performPause() {
    dispatchActivityPrePaused();
    mDoReportFullyDrawn = false;
    mFragments.dispatchPause();
    mCalled = false;
    onPause();
    EventLogTags.writeWmOnPausedCalled(mIdent, getComponentName().getClassName(),
            "performPause");
    mResumed = false;
    if (!mCalled && getApplicationInfo().targetSdkVersion
            >= android.os.Build.VERSION_CODES.GINGERBREAD) {
        throw new SuperNotCalledException(
                "Activity " + mComponent.toShortString() +
                " did not call through to super.onPause()");
    }
    dispatchActivityPostPaused();
}

 

이전과 비슷한 형태이고, 의외로 onPause() 메서드는 별 것 없습니다. 화면 가시성에 대한 처리도 딱히 존재하지 않습니다.

onStop()

@Override
public void handleStopActivity(IBinder token, int configChanges,
        PendingTransactionActions pendingActions, boolean finalStateRequest, String reason) {
    final ActivityClientRecord r = mActivities.get(token);
    r.activity.mConfigChangeFlags |= configChanges;

    final StopInfo stopInfo = new StopInfo();
    performStopActivityInner(r, stopInfo, true /* saveState */, finalStateRequest,
            reason);

    if (localLOGV) Slog.v(
        TAG, "Finishing stop of " + r + ": win=" + r.window);

    updateVisibility(r, false);

    // Make sure any pending writes are now committed.
    if (!r.isPreHoneycomb()) {
        QueuedWork.waitToFinish();
    }

    stopInfo.setActivity(r);
    stopInfo.setState(r.state);
    stopInfo.setPersistentState(r.persistentState);
    pendingActions.setStopInfo(stopInfo);
    mSomeActivitiesChanged = true;
}

 

onStop() 콜백 메서드의 경우 조금 특이하게 Runnable 객체가 생성되어 인자로 사용됩니다. StopInfo 는 Runnable 구현체이고, 구현하고 있는 run() 메서드에는 ActivityManager 에 해당 Activity 가 멈추었다는 것을 통지하는 작업이 예약되어 있습니다.

 

이후, updateVisibility() 메서드를 호출하여 레이아웃 표시를 멈춥니다.

 

r.activity.mVisibleFromServer = false;
mNumVisibleActivities--;
v.setVisibility(View.INVISIBLE);

 

재미있는 점은, 여기서 v 는 DecorView 를 의미하고, 해당 DecorViewVisibility View.GONE 이 아닌 View.INVISIBLE 로 지정한다는 점입니다. 이후에도 View.GONE 을 처리하지 않는데, Window 와 DecorView 를 onDestroy() 가 호출된 후에 제거해버리기 때문입니다.

 

ViewVisibility 에 대한 조정이 있기 전에 performStop() 메서드가 실행됩니다.

 

final void performStop(boolean preserveWindow, String reason) {
    mDoReportFullyDrawn = false;
    mFragments.doLoaderStop(mChangingConfigurations /*retain*/);

    // Disallow entering picture-in-picture after the activity has been stopped
    mCanEnterPictureInPicture = false;

    if (!mStopped) {
        dispatchActivityPreStopped();
        if (mWindow != null) {
            mWindow.closeAllPanels();
        }

        // If we're preserving the window, don't setStoppedState to true, since we
        // need the window started immediately again. Stopping the window will
        // destroys hardware resources and causes flicker.
        if (!preserveWindow && mToken != null && mParent == null) {
            WindowManagerGlobal.getInstance().setStoppedState(mToken, true);
        }

        mFragments.dispatchStop();

        mCalled = false;
        mInstrumentation.callActivityOnStop(this);
        ...

        mStopped = true;
        dispatchActivityPostStopped();
    }
    mResumed = false;
}

 

onStop() 부터는 PIP 모드를 사용할 수 없습니다. Window.closeAllPanels() 메서드는 Activity 와 Dialog 등 화면에 표시되는 모든 요소를 제거합니다. 해당 메서드와 관련해서는 최적화도 되어 있습니다.

 

즉, onStop() 콜백 메서드가 호출될 때는 모든 표시 요소를 제거한 상태입니다. 단, DecorView 는 계속해서 표시되는 상황입니다. onStop() 콜백 메서드가 호출된 이후에 DecorView 의 Visibility 가 변경됩니다.

onDestroy()

@Override
public void handleDestroyActivity(IBinder token, boolean finishing, int configChanges,
        boolean getNonConfigInstance, String reason) {
    ActivityClientRecord r = performDestroyActivity(token, finishing,
            configChanges, getNonConfigInstance, reason);
    if (r != null) {
        ...
        Context c = r.activity.getBaseContext();
        if (c instanceof ContextImpl) {
            ((ContextImpl) c).scheduleFinalCleanup(
                    r.activity.getClass().getName(), "Activity");
        }
    }
    if (finishing) {
        try {
            ActivityTaskManager.getService().activityDestroyed(token);
        } catch (RemoteException ex) {
            throw ex.rethrowFromSystemServer();
        }
    }
    mSomeActivitiesChanged = true;
}

 

소스가 좀 긴 편이어서 생략했습니다. performDestroy() 메서드를 실행하게끔 하고, 이후 진행되는 (생략된) 코드에서는 Window 와 DecorView 를 얻어 와 지연된 UI 요소 제거 작업 등을 강제로 실행합니다.

 

final void performDestroy() {
    dispatchActivityPreDestroyed();
    mDestroyed = true;
    mWindow.destroy();
    mFragments.dispatchDestroy();
    onDestroy();
    EventLogTags.writeWmOnDestroyCalled(mIdent, getComponentName().getClassName(),
            "performDestroy");
    mFragments.doLoaderDestroy();
    if (mVoiceInteractor != null) {
        mVoiceInteractor.detachActivity();
    }
    dispatchActivityPostDestroyed();
}

 

performDestroy() 는 간략한 편인데요. onDestroy() 메서드 내에서는 해당 Activity 에서 관리하는 모든 Dialog 를 제거하고, 액션바, SearchView 등 Activity 가 제거됨에 따라 필요 없어지는 자원들을 해제합니다.

 


전체적으로 비슷한 형태를 보이고 있습니다. 조금씩 차이는 있지만요. 도식화하면 다음과 같을 겁니다.

 


 

확장성을 위해서 모든 Lifecycle Callback 에 대한 재정의가 구현되어 있는 객체들이 많아 파악하기 어려웠습니다. 헷갈리기도 많이 헷갈렸고요. 각 메서드마다 호출하거나 안 하는 메서드, 객체 등이 많아서 복잡했습니다. 다만, 확실하게 알게 된 건 화면이 어느 시점에 보여지고, 어느 시점에 안 보이는지, 또 어느 시점에 Window 와 DecorView 가 제거되는지 입니다. PIP 모드에 대한 약소한 지식은 덤이고요.

 

Activity Lifecycle 은 정말 그 무엇보다 중요합니다. 특별한 기능을 수행하는 건 아닌데, 모르고 개발하면 정말 여기 저기서 생각하지도 못한 에러가 펑펑 터지게 됩니다. 이번 기회에 조금 자세히 정리할 수 있어 좋았습니다.