95

私はちょうど 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();  
    }
4

7 に答える 7

111

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

私の意見では、単一のことではありません。

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

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

振り出しに戻って。さて、これを別の角度から見てみましょう。Java 言語仕様では、 を呼び出す必要がある順序(または呼び出しを行う必要があるかどうか) が指定されていないことがわかっています。super.overridenMethod()

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

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

mCalledで true に設定されていますActivity.onStop()

ここで、議論すべき唯一の詳細は順序付けです。

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 Sheard : を使用して開始されたアクティビティの場合に への呼び出しをsuper.onPause()遅らせる必要があるシナリオを提供しますstartActivityForResult(Intent)setResult(...) after を使用して結果を設定しても機能しsuper.onPause()ません。彼は後で、彼の回答へのコメントでこれを明確にしています。

User Sherif elKhatib : スーパークラスが最初にそのリソースを初期化し、そのリソースを最後に破棄できるようにする理由を論理から説明します。

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

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

このページの彼の回答を参照して、super.onDestroy()呼び出しの配置プログラム ロジックに影響を与えるシナリオを読んでください。

マークによる回答から

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

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

void 以外のものを返すメソッド (onCreateOptionsMenu() など) では、return ステートメントでスーパークラスにチェーンすることがあります。これは、特定の戻り値を強制する必要があることを具体的に行っていないことを前提としています。

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

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

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

ユーザー Steve Benettもこれに注目しています。

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

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

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

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

于 2013-09-18T14:09:48.577 に答える
12

(あなたが言うには) 最初に super onCreate を呼び出すのは理にかなっています: 考えてみてください。

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

逆に: (一種のスタック)

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


この意味で、これは任意の関数 (onCreate/onDestroy、onResume/onPause、onStart/onStop) に適用されます。当然、onCreate はリソースを作成し、onDestroy はこれらのリソースを解放します。ちなみに、他のカップルについても同様の証明が成り立ちます。

場所を提供する getLocation() 関数を含む LocationActivity を持つ、ダウンロードしたライブラリを考えてみましょう。ほとんどの場合、このアクティビティは onCreate() でその内容を初期化する必要があるため、最初に super.onCreate を呼び出す必要があります。あなたはそれが理にかなっていると感じているので、すでにそれを行っています。ここで、onDestroy で、場所を SharedPreferences のどこかに保存することを決定します。最初に super.onDestroy を呼び出すと、この呼び出しの後に getLocation が null 値を返す可能性がある程度あります。これは、LocationActivity の実装が onDestroy の場所の値を無効にするためです。これが起こっても、あなたはそれを責めないだろうという考えです。したがって、独自の 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より前に)スーパーコールでfinalおよび/またはprivateに触れるだけでなく、実際にonPause ...関数をディスパッチするさまざまな内部関数(private)も呼び出しました。

たとえば、performStopfunction は呼び出された関数であり、次に 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 を呼び出していることに注意してください。したがって、すべてのコード (super.onStop に含まれる) を onStop の呼び出しの前または後に配置し、空の onStop スーパー関数を使用して onStop についてサブクラスに通知するだけで、SuperNotCalledException を追加したり、これが呼び出されたことを確認したりすることさえありません。

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

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

于 2013-09-18T11:54:41.300 に答える
3

あなたは、Google が方法 1 を提案していると言いますが、有名な Android フレームワーク エンジニアである Dianne Hackborn は、そうでないことを提案しています。Google Forum Linkを参照してください。

onPause、onStop、およびonDestroyメソッドでインスタンスを破棄するときはスーパー クラスを最後に呼び出し、 onCreate、onResume、およびonStartメソッドでインスタンスを作成するときは最初にスーパー クラスを呼び出すことは直感的に理にかなっています。

于 2013-09-19T11:26:49.837 に答える
1

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);
        }
}

実際、サブフィールドは上位クラスの前に初期化する必要があることを示しています! その間、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

于 2013-09-18T12:05:28.527 に答える