woshidan's blog

そんなことよりコードにダイブ。

ライフサイクルコールバックのsuperについて、自分の実装の先に呼ぶか、後に呼ぶか

ライフサイクルコールバックのsuperについて、自分の実装の先に呼ぶか、後に呼ぶかというのが気になっていて、

java - What is the correct order of calling superclass methods in onPause, onStop and onDestroy methods? and Why? - Stack Overflow

というStackOverflowを追いかけてみました。

まとめ

  • GoogleAlways call the superclass method first と書いているが、必ずしもそうではなさそう
  • onDestroy()など、コンポーネントを停止/解体するメソッドではActivityのリソースの解放などを行うので、自身の処理がそれらに依存する場合は、自身の処理を先に
  • onStart()など、コンポーネントを生成/起動するメソッドではActivityのリソースの準備を行うので、自身の処理がそれらに依存する場合は、自身の処理を後に
    • Themeの挙動などを標準のものと変えたい場合は、その変更処理をsuperの先に置く場合もあるのでは

メモ

和訳じゃないです。

上記のリンク先で質問者は、

    @Override
    protected void onStop() {
      super.onStop();  // Always call the superclass method first

      //my implementation here
    }
    @Override
    protected void onStop() {
       //my implementation here

       super.onStop();  
    }

の2つの書き方のどちらがしたらよいのかと聞いています。

一番投票を得ている人の回答を追ってみます。

  • Googleとしては、Always call the superclass method firstと書いています
  • Javaとしては、特にsuperclass methodを先に呼ばねばならない、とは決められていないし、呼ぶこと自体も決められていないようです
    • ただし、Activityのサブクラスの場合は、下記のコードにおいて、superclassのメソッドを呼ぶことを求められています
      • AndroidActivity.javaのコードで確認したら、performStart, performRestart, performResume, performPause, performStop,でSuperNotCalledExceptionを投げている行がありました。onCreateでもsuperを呼ばないと、setContentViewでぬるぽが発生します。onCreate/onDestroyでViewをwindowに足したり削除(detach)したり、といった処理を担当しているようです
    • しかし、このこと自体は順番の話には関係ないですね
if (!mCalled) {
    throw new SuperNotCalledException(
        "Activity " + mComponent.toShortString() +
            " did not call through to super.onStop()");
}
  • onPauseやonDestroyはどちらの順番でも動いているように見えますが...
    • onPauseの実装を見てみます
    • 下記の実装なら、どちらの順番で先に読んでも良さそうでしょうか?
protected void onPause() {
    if (DEBUG_LIFECYCLE) Slog.v(TAG, "onPause " + this);

    // This is to invoke 
    // Application.ActivityLifecyleCallbacks.onActivityPaused(Activity)
    getApplication().dispatchActivityPaused(this);

    // The flag to enforce calling of this method
    mCalled = true;
}

すこしだけ、super内で呼ばれてそうなメソッド名で検索かけた結果、state.resumed = false;の行だけFragment関係*1で気になりますが...まあ、大丈夫そうな気もします?

/android/app/Application.java:
   56          void onActivityStarted(Activity activity);
   57          void onActivityResumed(Activity activity);
   58:         void onActivityPaused(Activity activity);
   59          void onActivityStopped(Activity activity);
   60          void onActivitySaveInstanceState(Activity activity, Bundle outState);
   ..
  215          if (callbacks != null) {
  216              for (int i=0; i<callbacks.length; i++) {
  217:                 ((ActivityLifecycleCallbacks)callbacks[i]).onActivityPaused(activity);
  218              }
  219          }

/android/nfc/NfcActivityManager.java:
  440      /** Callback from Activity life-cycle, on main thread */
  441      @Override
  442:     public void onActivityPaused(Activity activity) {
  443          boolean readerModeFlagsSet;
  444          Binder token;
               synchronized (NfcActivityManager.this) {
                   NfcActivityState state = findActivityState(activity);
                   if (DBG) Log.d(TAG, "onPause() for " + activity + " " + state);
                   if (state == null) return;
                   state.resumed = false;
                   token = state.token;
                   readerModeFlagsSet = state.readerModeFlags != 0;
               }
               if (readerModeFlagsSet) {
                   // Restore default p2p modes
                   setReaderMode(token, 0, null);
               }
           }

/android/print/PrintManager.java:
  635  
  636          @Override
  637:         public void onActivityPaused(Activity activity) {
  638              /* do nothing */
  639          }
  • Activity.onStop()も同様の内容で...
    • むしろ、Activity.onStop()の方がActionBarなどのコールバック以外、上に乗っているコンポーネントに影響するような状態が変化するようなことはしてなさそうです。
    protected void onStop() {
        if (DEBUG_LIFECYCLE) Slog.v(TAG, "onStop " + this);
        if (mActionBar != null) mActionBar.setShowHideAnimationEnabled(false);
        mActivityTransitionState.onStop();
        getApplication().dispatchActivityStopped(this);
        mTranslucentCallback = null;
        mCalled = true;
    }
/android/app/Application.java:
   57          void onActivityResumed(Activity activity);
   58          void onActivityPaused(Activity activity);
   59:         void onActivityStopped(Activity activity);
   60          void onActivitySaveInstanceState(Activity activity, Bundle outState);
   61          void onActivityDestroyed(Activity activity);
   ..
  224          if (callbacks != null) {
  225              for (int i=0; i<callbacks.length; i++) {
  226:                 ((ActivityLifecycleCallbacks)callbacks[i]).onActivityStopped(activity);
  227              }
  228          }

/android/nfc/NfcActivityManager.java:
  459      /** Callback from Activity life-cycle, on main thread */
  460      @Override
  461:     public void onActivityStopped(Activity activity) { /* NO-OP */ }
  462  
  463      /** Callback from Activity life-cycle, on main thread */

/android/print/PrintManager.java:
  655  
  656          @Override
  657:         public void onActivityStopped(Activity activity) {
  658              /* do nothing */
  659          }
  • onDestroy()を見てみましょう
    • 利用しているリソースの解放などを行っているみたいです
    • Always call the superclass method first は必ずしも保証のあるものではないかもですね
    • そのリソースに関して、自分が書いた初期化処理を行いたいのであれば、super.onDestroy()の前に行うべきでは
protected void onDestroy() {
    if (DEBUG_LIFECYCLE) Slog.v(TAG, "onDestroy " + this);
    mCalled = true;

    // dismiss any dialogs we are managing.
    if (mManagedDialogs != null) {
        final int numDialogs = mManagedDialogs.size();
        for (int i = 0; i < numDialogs; i++) {
            final ManagedDialog md = mManagedDialogs.valueAt(i);
            if (md.mDialog.isShowing()) {
                md.mDialog.dismiss();
            }
        }
        mManagedDialogs = null;
    }

    // close any cursors we are managing.
    synchronized (mManagedCursors) {
        int numCursors = mManagedCursors.size();
        for (int i = 0; i < numCursors; i++) {
            ManagedCursor c = mManagedCursors.get(i);
            if (c != null) {
                c.mCursor.close();
            }
        }
        mManagedCursors.clear();
    }

    // Close any open search dialog
    if (mSearchManager != null) {
        mSearchManager.stopSearch();
    }

    getApplication().dispatchActivityDestroyed(this);
}

回答者がまとめている他の人の意見のまとめから一部個人的に重複していると思うものを除いて抜粋すると、

  • super.onPause()の後では、setResult(...)が動かないことがあるので、startActivityForResultを利用する場合は、super.onPause()を後回しに
  • Activityクラスで扱うリソースにロジックが依存していないのならば、superをどの順で扱っても良い
    • が、実際super.onDestroy()の場所はロジックに影響を与えるのでは
  • onCreate(), onStart(), onResume(), etc.などコンポーネントを作る方のメソッドではsuperを先に、onPause(), onStop(), onDestroy(), etc.など、コンポーネントを解体する方のメソッドでは、superを後に。
  • テーマの設定など、Activityの標準的な挙動に影響を与えたい場合は、superのメソッドを呼び出す前にする

でした。