web-dev-qa-db-ja.com

onPause、onStop、onDestroyメソッドでスーパークラスメソッドを呼び出す正しい順序は何ですか?なぜ?

私はAndroid開発者サイトを通過して、アクティビティライフサイクルを更新しました。各コード例には、常にスーパークラスメソッドを最初に呼び出すというスーパークラスメソッドの横にコメントがあります。 「。

これは、作成の半サイクル(onCreate、onStart、およびonResume)では理にかなっていますが、破壊の半サイクル(onPause、onStop、onDestroy)での正しい手順については少し混乱しています。

インスタンス固有のリソースを最初に破棄してから、インスタンス固有のリソースが依存する可能性のあるスーパークラスリソースを破棄するのは理にかなっていますが、逆ではありません。私は何が欠けていますか?

編集:質問の意図に関して人々が混乱しているように見えるので、私が知りたいのは次のうちどれが正しいかです。 そしてなぜ?

1.Googleが提案する

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

      //my implementation here
    }

2。その他の方法

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

       super.onStop();  
    }
82
Anudeep Bulla

インスタンス固有のリソースが依存している可能性のあるスーパークラスリソースを破棄する前に、まずインスタンス固有のリソースを破棄します。しかし、コメントはそうでないことを示唆しています。 何が欠けていますか?

私の意見では:単一のものではありません。

Mark(SOのCommonsWare)からのこの回答は、問題に光を当てます。 リンク-スーパークラスメソッドの呼び出しを最初のステートメントにする必要がありますか? 。しかし、その後、あなたは彼の答えに次のコメントを残すことができます:

しかし、なぜ公式ドキュメントには、onPause()で「常にスーパークラスメソッドを最初に呼び出す」と書かれているのでしょうか。

振り出しに戻って。さて、これを別の角度から見てみましょう。 Java Language Specificationでは、super.overridenMethod()への呼び出しの順序(または呼び出しを行う必要がある場合)を指定しませんすべて)。

クラスActivityの場合、super.overridenMethod()呼び出しが必要であり、強制

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

Activity.onStop()mCalledがtrueに設定されます。

さて、議論の余地があるのは順序だけです。

I also know that both work

確かに。 Activity.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.onPause()への呼び出しを挟んだとしても、大丈夫です。 Activity.onStop()には同様のメソッド本体があります。ただし、Activity.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.onDestroy()の呼び出しが後続のコードに干渉するかどうかに依存します。

最後の言葉として、ステートメントAlways call the superclass method firstにはそれを裏付ける証拠があまりないようです。さらに悪いことに(ステートメントの場合)、次のコードがAndroid.app.ListActivityから取得されています。

public class ListActivity extends Activity {

    ....

    @Override
    protected void onDestroy() {
        mHandler.removeCallbacks(mRequestFocus);
        super.onDestroy();
    }
    ....    
}

そして、Android SDKに含まれるLunarLanderサンプルアプリケーションから:

public class LunarLander extends Activity {

    ....

    @Override
    protected void onPause() {
        mLunarView.getThread().pause(); // pause game when Activity pauses
        super.onPause();
    }
    ....
}

要約と価値のある言及:

ユーザーPhilip Sheardsuper.onPause()を使用してアクティビティを開始した場合、startActivityForResult(Intent)への呼び出しを遅延させる必要があるシナリオを提供します。 setResult(...)を使用して結果を設定するaftersuper.onPause()は機能しません。彼は後で、彼の答えに対するコメントでこれを明確にします。

User Sherif elKhatib:ロジックからスーパークラスが最初にリソースを初期化し、最後にリソースを破棄させる理由を説明します。

ロケーションを提供するgetLocation()関数を含むLocationActivityを含む、ダウンロードしたライブラリを考えてみましょう。ほとんどの場合、このアクティビティでは、onCreate()でその内容を初期化する必要があります。これにより、super.onCreate firstの呼び出しが強制されます。理にかなっていると思うので、あなたはすでにそれをしています。ここで、onDestroyで、SharedPreferencesのどこかにLocationを保存することにします。最初にsuper.onDestroyを呼び出すと、LocationActivityの実装がonDestroyの位置値を無効にするため、この呼び出し後にgetLocationがnull値を返す可能性がある程度あります。アイデアは、これが起こってもあなたはそれを責めないだろうということです。 したがって、独自のonDestroyが完了したら、最後にsuper.onDestroyを呼び出します。

彼はさらに指摘します:子クラスが親クラスから(リソース依存関係の観点から)適切に分離されている場合、super.X()呼び出しは順序指定に従う必要はありません。

super.onDestroy()呼び出しの配置doesがプログラムロジックに影響するシナリオを読むには、このページの彼の答えをご覧ください。

Markの回答から:

コンポーネント作成の一部であるオーバーライドするメソッド(onCreate()、onStart()、onResume()など)、最初のステートメントとしてスーパークラスにチェーンする必要があります Androidは、その作業が完了したことに依存する何かを実行しようとする前に、その作業を実行する機会があります。

コンポーネント破壊の一部であるオーバーライドするメソッド(onPause()、onStop()、onDestroy()など)、最初に作業を行い、最後にスーパークラスにチェーンする必要があります。そうすれば、Androidが作業に依存する何かをクリーンアップする場合、最初に作業を完了できます。

Void以外の何かを返すメソッド(onCreateOptionsMenu()など)では、特定の戻り値を強制する必要のあることを特に行っていない場合、returnステートメントでスーパークラスにチェーンすることがあります。

OnActivityResult()などの他のすべては、全体的にあなた次第です。私は最初にスーパークラスにチェーンする傾向がありますが、問題が発生しない限り、後でチェーンすることは問題ありません。

ボブ・カーンズfrom このスレッド

これは良いパターンです((上でマークが示唆しているパターン))が、いくつかの例外を見つけました。たとえば、PreferenceActivityに適用したいテーマは、スーパークラスのonCreate()の前に配置しない限り有効になりません。

ユーザーSteve Benettもこれに注意を促します。

スーパーコールのタイミングが必要な状況を1つだけ知っています。 onCreateでテーマやディスプレイなどの標準的な動作を変更する場合は、superを呼び出して効果を確認する前に変更する必要があります。それ以外の場合は、いつ呼び出しても違いはありません。

ユーザーSunil Mishraは、Activityクラスのメソッドを呼び出すときに順序(ほとんどの場合)が役割を果たさないことを確認します。彼はまた、最初にスーパークラスメソッドを呼び出すことはベストプラクティスと見なされると主張しています。しかし、私はこれを裏付けることができませんでした。

ユーザーLOG_TAG:スーパークラスconstructorへの呼び出しが他のすべての前にある必要がある理由を説明します。私の意見では、この説明は質問に追加されるものではありません。

End note:信頼、しかし確認。このページの回答のほとんどは、ステートメントAlways call the superclass method firstに論理的な裏付けがあるかどうかを確認するために、このアプローチに従います。結局のところ、そうではありません。少なくとも、クラスActivityの場合はそうではありません。通常、スーパークラスのソースコードを読んで、スーパーのメソッドへの呼び出しの順序が必要かどうかを判断する必要があります。

95
Vikram

(言う)最初にsuper onCreateを呼び出すのは理にかなっているので、考えてみてください。

作成したいとき、スーパーがそのリソースを作成します>リソースを作成します。

逆:(スタックの並べ替え)

破壊したいときは、リソースを破壊します>私のスーパーは彼のリソースを破壊します。


この意味で、これはいくつかの関数(onCreate/onDestroy、onResume/onPause、onStart/onStop)に適用されます。当然、onCreateはリソースを作成し、onDestroyはこれらのリソースを解放します。ところで、他のカップルにも同じ証明が適用されます。

ロケーションを提供するgetLocation()関数を含むLocationActivityを含む、ダウンロードしたライブラリを考えてみましょう。おそらく、このアクティビティはonCreate()でその内容を初期化する必要があり、最初にsuper.onCreateを呼び出す必要があります。理にかなっていると思うので、あなたはすでにそれをしています。ここで、onDestroyで、SharedPreferencesのどこかにLocationを保存することにします。最初にsuper.onDestroyを呼び出すと、LocationActivityの実装がonDestroyの位置値を無効にするため、この呼び出し後にgetLocationがnull値を返す可能性がある程度あります。アイデアは、これが起こってもあなたはそれを責めないだろうということです。したがって、独自のonDestroyが完了したら、最後にsuper.onDestroyを呼び出します。これが少し理にかなっていることを願っています。

上記が理にかなっている場合は、いつでも上記の概念に従うアクティビティがあることを考慮してください。このアクティビティを拡張したい場合は、引数がまったく同じであるため、おそらく同じように感じ、同じ順序に従います。

誘導によって、どのアクティビティでも同じことを行う必要があります。これらのルールに従うことを強制されるアクティビティの優れた抽象クラスを次に示します。

package mobi.sherif.base;

import Android.app.Activity;
import Android.os.Bundle;

public abstract class BaseActivity extends Activity {
    protected abstract void doCreate(Bundle savedInstanceState);
    protected abstract void doDestroy();
    protected abstract void doResume();
    protected abstract void doPause();
    protected abstract void doStart();
    protected abstract void doStop();
    protected abstract void doSaveInstanceState(Bundle outState);
    @Override
    protected final void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        doCreate(savedInstanceState);
    }
    @Override
    protected final void onDestroy() {
        doDestroy();
        super.onDestroy();
    }
    @Override
    protected final void onResume() {
        super.onResume();
        doResume();
    }
    @Override
    protected final void onPause() {
        doPause();
        super.onPause();
    }
    @Override
    protected final void onStop() {
        doStop();
        super.onStop();
    }
    @Override
    protected final void onStart() {
        super.onStart();
        doStart();
    }
    @Override
    protected final void onSaveInstanceState(Bundle outState) {
        doSaveInstanceState(outState);
        super.onSaveInstanceState(outState);
    }
}

最後に、AnudeepBullaActivityというアクティビティがBaseActivityを拡張し、後でアクティビティを拡張するSherifElKhatibActivityを作成したい場合はどうすればよいですか? super.do関数をどの順序で呼び出す必要がありますか?最終的には同じことです。


あなたの質問に関して:

Googleの意図は私たちに伝えることだと思います:どこにいてもスーパーに電話してください。もちろん一般的な慣行として、最初にそれを呼び出します。もちろん、Googleには最も優秀なエンジニアと開発者がいるため、おそらくスーパーコールを分離し、子のコールに干渉することなく、良い仕事をしたでしょう。

私は少し試してみましたが、いつ呼び出されるのかが原因で単純にクラッシュするアクティビティを作成するのはおそらく簡単ではありません(Googleが間違っていることを証明しようとしているためです)。

どうして?

これらの関数で行われることはすべて、Activityクラスに対して実際にプライベートであり、サブクラスとの競合を引き起こすことはありません。たとえば(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);
}

mManagedCursorsおよびmManagedDialogsおよびmSearchManagerはすべてプライベートフィールドです。また、パブリック/保護されたAPIのいずれも、ここで行われた操作の影響を受けません。

ただし、API 14では、アプリケーションに登録されたActivityLifecycleCallbacksにonActivityDestroyedをディスパッチするためにdispatchActivityDestroyedが追加されました。したがって、ActivityLifecycleCallbacksの一部のロジックに依存するコードは、スーパーを呼び出すタイミングに基づいて異なる結果になります。例えば:

現在実行中のアクティビティの数をカウントするアプリケーションクラスを作成します。

package mobi.shush;

import Android.app.Activity;
import Android.app.Application;
import Android.app.Application.ActivityLifecycleCallbacks;
import Android.os.Bundle;

public class SherifApplication extends Application implements ActivityLifecycleCallbacks {
    @Override
    public void onCreate() {
        super.onCreate();
        registerActivityLifecycleCallbacks(this);
    }
    public int getCount() {
        return count;
    }
    int count = 0;
    @Override
    public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
        count++;
    }
    @Override
    public void onActivityDestroyed(Activity activity) {
        count--;
    }
    @Override
    public void onActivityPaused(Activity activity) {}
    @Override
    public void onActivityResumed(Activity activity) {}
    @Override
    public void onActivitySaveInstanceState(Activity activity, Bundle outState)           {}
    @Override
    public void onActivityStarted(Activity activity) {}
    @Override
    public void onActivityStopped(Activity activity) {}
}

以下は意味をなさないか、良い習慣ではないかもしれませんが、それは単にポイントを証明することです(より現実的な状況を見つけるかもしれません)。 GoodByeアクティビティが終了し、最後のアクティビティになったときに、そのアクティビティに移動するはずのMainActivityを作成します。

@Override
protected void onDestroy() {
    super.onDestroy();
    if(((SherifApplication) getApplication()).getCount() == 0) {
        //i want to go to a certain activity when there are no other activities
        startActivity(new Intent(this, GoodBye.class));
    }
}

OnDestroyの最初にsuper.onDestroyを呼び出すと、GoodByeアクティビティが起動します。 onDestroyの最後にsuper.onDestroyを呼び出すと、GoodByeアクティビティは起動しません。

もちろん、これも最適な例ではありません。しかし、これはGoogleがここで少し台無しになったことを示しています。他の変数は、アプリの動作に影響しませんでした。ただし、これらのディスパッチをonDestroyに追加すると、スーパーが何らかの方法でサブクラスに干渉します。

私は彼らが別の理由でも台無しにしたと言います。 (api 14より前)彼らは、最終呼び出しやプライベート呼び出しをスーパーコールで行うだけでなく、実際にonPause ...関数をディスパッチするさまざまな内部関数(プライベート)を呼び出しました。

たとえば、performStop関数は、onStop関数を呼び出す関数です。

final void performStop() {
    if (mLoadersStarted) {
        mLoadersStarted = false;
        if (mLoaderManager != null) {
            if (!mChangingConfigurations) {
                mLoaderManager.doStop();
            } else {
                mLoaderManager.doRetain();
            }
        }
    }

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

        if (mToken != null && mParent == null) {
            WindowManagerGlobal.getInstance().setStoppedState(mToken, true);
        }

        mFragments.dispatchStop();

        mCalled = false;
        mInstrumentation.callActivityOnStop(this);
        if (!mCalled) {
            throw new SuperNotCalledException(
                    "Activity " + mComponent.toShortString() +
                    " did not call through to super.onStop()");
        }

        synchronized (mManagedCursors) {
            final int N = mManagedCursors.size();
            for (int i=0; i<N; i++) {
                ManagedCursor mc = mManagedCursors.get(i);
                if (!mc.mReleased) {
                    mc.mCursor.deactivate();
                    mc.mReleased = true;
                }
            }
        }

        mStopped = true;
    }
    mResumed = false;
}

この関数のどこかでアクティビティのonStopを呼び出すことに注意してください。そのため、onStopの呼び出しの前後にすべてのコード(super.onStopに含まれる)を配置し、空のonStopスーパー関数を使用して、SuperNotCalledExceptionを追加したり、呼び出されたものを確認したりすることなく、onStopについてサブクラスに通知することもできます。

このため、super.onDestroyの最後で呼び出すのではなく、performDestroyでActivityLifeCycleへのディスパッチを呼び出した場合、アクティビティの動作は、いつsuperを呼び出したかに関係なく同じでした。

とにかく、これは彼らが最初に行うことであり(少し間違っています)、API 14でのみです。

11
Sherif elKhatib

Javaの観点から、この混乱に対するいくつかの解決策があります。

なぜthis()とsuper()はコンストラクターの最初のステートメントである必要があるのですか?

親クラスのコンストラクターは、サブクラスのコンストラクターの前に呼び出す必要があります。これにより、コンストラクターで親クラスのメソッドを呼び出す場合、親クラスが既に正しく設定されていることが保証されます。

あなたがやろうとしていることは、引数をスーパーコンストラクタに渡すことは完全に合法です、あなたはやっている間にそれらの引数をインラインで構築するか、コンストラクタに渡してからスーパーに渡すだけです:

public MySubClassB extends MyClass {
        public MySubClassB(Object[] myArray) {
                super(myArray);
        }
}

コンパイラがこれを強制しない場合、これを行うことができます:

public MySubClassB extends MyClass {
        public MySubClassB(Object[] myArray) {
                someMethodOnSuper(); //ERROR super not yet constructed
                super(myArray);
        }
}

実際、サブフィールドはsupreclassの前に非行化される必要があることを示しています!その間、Java要件は、スーパーコンストラクターの引数を特殊化することにより、クラスを特殊化することから「守る」

親クラスにデフォルトのコンストラクターがある場合、コンパイラーによってsuperの呼び出しが自動的に挿入されます。 JavaのすべてのクラスはObjectから継承するため、オブジェクトコンストラクターを何らかの方法で呼び出して最初に実行する必要があります。コンパイラーによるsuper()の自動挿入によりこれが可能になります。コンストラクターの本体が正しい順序で実行されるようにします。正しい順序は、Object-> Parent-> Child-> ChildOfChild-> SoOnSoForthです。

(1)superが最初のステートメントであることを確認するだけでは、その問題を防ぐのに十分ではありません。たとえば、「super(someMethodInSuper());」と入力できます。あなたのコンストラクタで。これは、superが最初のステートメントであっても、構築される前にスーパークラスのメソッドにアクセスしようとします。

(2)コンパイラーは、この問題を防ぐのに十分な別のチェックを実装しているようです。メッセージは、「スーパータイプコンストラクターが呼び出される前にxxxを参照できません」です。したがって、superが最初のステートメントであることを確認する必要はありません。

これを実行してください http://valjok.blogspot.in/2012/09/super-constructor-must-be-first.html

1
LOG_TAG

Googleは方法1を提案すると言いますが、よく知られているAndroidフレームワークエンジニアがそうでなければ Googleフォーラムリンク を参照することをお勧めします。

インスタンスがdestroyingの場合、スーパークラスlastを呼び出すことは直感的ですonPause、onStopおよびonDestroyメソッドおよびfirstwhencreatingonCreateのメソッドでインスタンスを作成するonCreate 、onResumeおよびonStart

1
NickT

両方とも正しいIMO

ドキュメントによると

派生クラスは、このメソッドのスーパークラスの実装を呼び出す必要があります。そうでない場合、例外がスローされます。

Superメソッドは、ドキュメントで明示的に指示されている場合は常に呼び出す必要があります。

ただし、スーパーメソッドをいつ呼び出すかを選択できます。

onPauseのソースを見る

protected void onPause() {
    getApplication().dispatchActivityPaused(this);
    mCalled = true;
}

したがって、呼び出される前後に関係なく、あなたは良いはずです。

ただし、ベストプラクティスのために、最初に呼び出す必要があります。

主に保護メカニズムとしてお勧めします。例外がある場合は、superインスタンスメソッドが既に呼び出されています。

また、これらの呼び出しを最初の行に置くと、メソッドのコードを削除したり、スーパークラスの呼び出しを誤って削除したりするなど、将来ミスを避けるのに役立ちます。

1
Sunil Mishra

心に留めておくべき最も重要なことは、super.onPause()setResult(Activity.RESULT_CANCELED)を暗黙的に呼び出すことです。ただし、setResultは1回しか呼び出すことができず、以降の呼び出しはすべて無視されます。したがって、親アクティビティに何らかの結果をプッシュしたい場合は、setResultを自分で呼び出す必要があります。beforesuper.onPause()を呼び出します。私の知る限り、それは最大の落とし穴です。

1
Philip Sheard

システムのアクティビティを内部的に適切な状態にするには、コールバックのスーパーが必要です。

アクティビティを開始し、onCreateがシステムによって呼び出されたとします。これで上書きできます。レイアウトをロードします。しかし、システムフローのために、システムを標準手順で続行できるように、superを呼び出す必要があります。そのため、呼び出さないと例外がスローされます。

これは、onCreateでの実装とは無関係に発生します。これはシステムの重要なポイントです。 ANRがない場合、コールバックに無限ループが発生する可能性があり、アクティビティがそのループでキャッチされます。そのため、システムはコールバックがいつ終了したかを認識し、次のコールバックを呼び出します。

スーパーコールのタイミングが必要な状況を1つだけ知っています。 onCreateでテーマやディスプレイなどの標準的な動作を変更する場合は、superを呼び出して効果を確認する前に変更する必要があります。それ以外の場合は、いつ呼び出しても違いはありません。

しかし、システムを中断する正当な理由がない場合、コードに続くコールバックの最初の行にスーパーを配置する最善の方法をシステムに実行させることができます。

0
Steve Benett