5

私はその理由を理解しようとしていました:

getSupportFragmentManager().beginTransaction().commit();

失敗し、

java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState

非常に基本的な FragmentActivity クラスで。

これが私の使用例です (これは疑似コードであり、完全な例ではありません。申し訳ありません): 内部 AsyncTask クラスを持つ 1 つの FragmentActivity がありました。おおよそ次のようなものです。

public class HelloWorld extends FragmentActivity {
    showFragment(Fragment fragment, String name) {
        getSupportFragmentManager().beginTransaction().replace(R.id.fragmentContainer, fragment, name).commit();
    }

    private class SlowFragmentShow extends AsyncTask<Context, String, Void> {
        protected Void doInBackground(Context... contexts) {
            try {
                Thread.sleep(10000);
            } catch (InterruptedException e) {
                /* meh */
            }
        }

        protected void onPostExecute(Void nothing) {
            showFragment(new MyFragment(), "myFragment");
        }
    }
}

または基本的に、アプリを起動してから 10 秒後に、別のフラグメントが表示されます。シンプルですね。電話を回転させることにするまで、これもうまくいくように見えました。私がそれをしたとき、アプリは「このアクションを実行できません...」で「getSupportFragmentManager()...」を呼び出すとクラッシュしました。

4

2 に答える 2

10

多くのデバッグの後、が呼び出されたときSlowFragmentShow.onPostExecute()に が呼び出されshowFragment()、次にが呼び出されたときにgetSupportFragmentManager()、 にある を受け取ったFragmentManagerことが判明しましたIllegalState(おそらく、私が受け取った例外は正しいものでした)。getSupportFragmentManager()なぜこのようなリンボ状態でオブジェクトを返すのかはまだわかりませんが、そうでした。何らかの方法で「正しい」 FragmentManager. FragmentManager要点を言えば、 をstatic 変数として myに保存し、が呼び出さHelloWorld FragmentActivityれたときに更新しました。HelloWorld.onStart()

public class HelloWorld extends FragmentActivity {
    private static FragmentManager fragmentManager;

    public void onStart() {
        fragmentManager = getSupportFragmentManager();
        /* more code here */
    }

    showFragment(Fragment fragment, String name) {
        fragmentManager.beginTransaction().replace(R.id.fragmentContainer, fragment, name).commit();
    }

    private class SlowFragmentShow extends AsyncTask<Context, String, Void> {
        protected Void doInBackground(Context... contexts) {
            try {
                Thread.sleep(10000);
            } catch (InterruptedException e) {
                /* meh */
            }
        }

        protected void onPostExecute(Void nothing) {
            showFragment(new MyFragment(), "myFragment");
        }
    }
}

そして、まあ、それはほとんどそれを修正しました。これで、電話を好きなように回転させることができました。AsyncTask が完了しても、フラグメントは引き続き表示されます。

振り返ってみると、「ああ、もちろん!」のように思えますが、Android の背後にある設計上の決定は、かなり「異質」で珍しいものに感じられます。try-catch(Exception)致命的ではないエラー (テキスト フィールドの更新に失敗するなど) でクラッシュするのを防ぐためだけに、コードのほぼ半分と、更新する必要がある多くの静的変数で終わるようonStart()です。 Android オブジェクトがIllegalState.

于 2013-06-14T22:49:21.470 に答える
0

@VidarWahlbergあなたの答えは良いFragmentActivityですが、タスクを実行する場所があり、FragmentActivity非同期が終了する前、またはアプリがバックグラウンドで実行される前に開く新しいものを開くとしましょう(例:ホームをクリックしました)。BaseActivity拡張する場所を作成しFragmentActivity、すべての活動がそれを拡張することをお勧めします。

何が重要ですか?

@VidarWahlbergが彼の回答で述べたように、BaseActivityが必要でありstatic FragmentManager fm;、それを子アクティビティで使用してDialogFragment.

違いはなんですか?

fm = getSupportFragmentManager();メソッドで初期化する代わりに、でonStart()行う必要がありますonResume()。スレッド実行アクティビティを開始すると、その新しいインスタンスが作成され、次のアクティビティを開始すると、初期化されるBaseActivity場所の新しいインスタンスが作成FragmenManagerされ、スレッド開始アクティビティfmオブジェクトに戻ると、2 番目のアクティビティ(一時停止されている) インスタンス。すでに一時停止しているアクティビティのビューでダイアログを表示しようとすると、アプリケーションがクラッシュします。onResume()一時停止したアクティビティのビューを変更することはできないため、呼び出されるたびに初期化する必要があります。onResume()アクティビティの開始時にも呼び出されるため、安全に初期化できますFragmentManager

他に何を心配する必要がありますか?

すべてがうまくいっていると思うかもしれませんが、そうではありません。最初は気付かないかもしれませんが、アプリがフォアグラウンドではなく (バックグラウンド) である場合、すべてのアクティビティは一時停止されますが、スレッドは既に開始され、実行されています。これは、スレッドが継続して表示しようとするDialogFragmernt(または、フォーカスされていないアクティビティのビューを変更するとしましょう) ため、問題です。したがって、解決策は、アプリがフォアグラウンドにあるかどうかを追跡することです。これは実行できますがPackageManager、マニフェスト ファイルで新しいアクセス許可が必要になります (ユーザーはこれを好まず、アプリをインストールしたくない場合があります)。解決策は、withおよびwithstatic boolean isOnBackground = false;のすべてのアクティビティで変更するフィールドを用意することです。これは大きな変化ではないので、onResume()isOnBackground = false;onPause()isOnBackground = true;BaseActivityそれができるところです。BaseActivity新しいアクティビティが開始されるたびにクラスの新しいインスタンスが作成されるため、これは安全ですが、フィールドはstatic一度だけ作成され、すべてのインスタンスで同じであるためです。isOnBackgroundスレッド ポスト メソッドの本体コードを開始する前に、状態を確認する必要があります。

onPause()との間の遅延が原因で問題が発生する可能性があると言うかもしれませんが、onResume()アクティビティをすばやく開始し、UI スレッドで長時間かかる作業を行わないようにアプリを設計する必要があります (バックグラウンド スレッドで行う必要があります)。したがって、アプリが適切に設計されている場合、問題は発生しません:)

アプリがフォアグラウンドにないときはいつでも、スレッド実行メソッドの再帰呼び出しを使用できます。アプリの状態がリセットされた場合 (メモリが必要な場合)、またはアプリがユーザーによって停止された場合、または Android 自体がループを停止した場合、すべてのアプリのインスタンスがメモリから解放されるため、これは安全でループを引き起こしません。

注: Android サポート ライブラリを使用してください。

コードは次のとおりです。

BaseActivity.java

.......................
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentManager;
.......................

public class BaseActivity extends FragmentActivity {

    public static FragmentManager fm;
    public static boolean isOnBackground;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }

    @Override
    protected void onResume() {
        super.onResume();
        BaseActivity.isOnBackground = false;
        BaseActivity.fm = getSupportFragmentManager();
    }

    @Override
    protected void onPause() {
        super.onPause();
        BaseActivity.isOnBackground = true;
    }
}

MainActivity.java

.....................
import android.support.v4.app.DialogFragment;
.....................

public class MainActivity extends BaseActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //This is just to call some second activity
        Button startNewActivity = (Button) findViewById(R.id.helloWorld);

        startNewActivity.setOnClickListener( new OnClickListener() {

            @Override
            public void onClick(View v) {
                Intent intent = new Intent(MainActivity.this,
                            SecondActivity.class);
                startActivity(intent);
            }
        });

        showEditDialog();
    }

    private void showEditDialog() {
        (new Handler()).postDelayed(new Runnable() {
            @Override
            public void run() {
                if(!BaseActivity.isOnBackground) {
                    DialogFragment myFragmentDialog = new DialogFragment();
                    myFragmentDialog.show(BaseActivity.fm,
                            "fragment_my_dialog");
                } else {
                    //this is optional
                    showEditDialog();
                }
            } 

        }, 15000);
    }
}
于 2013-10-09T14:48:59.173 に答える