フラグメントトランザクションとアクティビティの損失

この問題に遭遇したことがない人のために、例を挙げて説明します-長いバックグラウンド操作の最後にダイアログを表示します(はい、Googleはこれを行うことを推奨しませんが、顧客は必要とします)。 Homeキーを押してダイアログを表示する前にアプリケーションを最小化すると、ダイアログが表示されたときにIllegalStateExceptionがスローされます。 バックグラウンドアクティビティが完了したときに待機ダイアログが表示され、非表示になった場合も同じことが起こります。状態を保存した後にdismiss()メソッドを呼び出すと、例外がスローされます。







問題をグーグルで見つけたこのトピックに関する最良の記事は、 フラグメントトランザクションとアクティビティ状態の損失です。 この記事では問題を説明していますが、一般的なアドバイスのみを提供しており、問題自体は未解決のままです。 おそらく、一部の行商人はこの記事の翻訳に興味があるでしょうが、ここではその意味を簡単に説明します。 Androidシステムには、メモリ不足でアプリケーションとそのフラグメントのアクティビティを完了する機能があります。 この不幸な事実をユーザーから隠すために、Androidはアクティビティの状態を保存し、必要に応じて復元します。これにより、ユーザーはコードレベルで発生した災害に気付かないようになります。 状態を保存した後にダイアログを表示しようとすると、実際には保存された状態に違反しており、そのようなアクティビティは復元できません。 Androidは、これを最も簡単な方法で解決します-例外をスローし、フラグメントトランザクションのコミットを許可しません。 そして、アプリケーションがクラッシュするだけです。



この問題に対処する方法はたくさんあります。それらはすべて、回復後まで、つまりActivity.onPostResume関数またはFragment.onResume関数でトランザクションを延期することに要約されます。 最初に思い浮かぶのは、ダイアログを表示する代わりにフラグを立て、onResumeでチェックしてダイアログを表示することです。 onActivityResultの場合、これも機能します。アクティビティの状態が復元される前にこの関数が常に呼び出されるためです。 しかし、バックグラウンド処理の場合、アクティビティの状態はわかりませんが、アクティビティのステータスを確認する簡単な方法はありません。 Googleが推奨する方法は、バックグラウンド処理にローダーを使用することです。 ただし、これは必ずしも便利ではありません。 たとえば、彼らのVolleyライブラリはこのテンプレートを使用せず、簡単に接続する方法はありません。



問題を回避する他の失敗した試みであなたを苦しませません。 あなたの多くはいくつかの選択肢を持っていると思いますが、私は私の決定を共有します。



テストアプリケーションを作成しましょう。



public class MainActivity extends ActionBarActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); startAsyncTask(); } private void startAsyncTask() { new AsyncTask<Void, Void, Void>() { @Override protected Void doInBackground(Void... params) { try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } runOnUiThread(new Runnable() { @Override public void run() { showMyDialog(); } }); return null; } }.execute(); } private void showMyDialog() { new TestDialog().show(getSupportFragmentManager(), "dialog"); } public class TestDialog extends DialogFragment { public TestDialog(){} @Override public Dialog onCreateDialog (Bundle savedInstanceState){ AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this); builder.setMessage("Hello World"); builder.setPositiveButton("OK", null); return builder.create(); } } }
      
      







実行すると、3秒後にダイアログが表示されます。 ただし、3秒が経過する前にHomeキーを押すと、ダイアログが表示されたときにアプリケーションがクラッシュします。



次のクラスを追加すると、アクティビティのステータスが追跡されます。



 public class StateHandler { /** *   Runnable */ private final List<Runnable> queueBuffer = Collections.synchronizedList(new ArrayList<Runnable>()); /** * ,    */ private Activity activity; /** *   */ public final synchronized void resume(Activity activity) { this.activity = activity; while (queueBuffer.size() > 0) { final Runnable runnable = queueBuffer.get(0); queueBuffer.remove(0); runnable.run(); } } /** *   */ public final synchronized void pause() { activity = null; } /** *  Runnable     ,      * * @param runnable  Runnable,   . */ public final synchronized void run(Runnable runnable) { if (activity == null) { queueBuffer.add(runnable); } else { runnable.run(); } } }
      
      







状態処理クラスを初期化およびサポートするルーチンの手順を繰り返さないために、基本的なアクティビティをレンダリングします。



 public class BaseActivity extends ActionBarActivity { protected StateHandler stateHandler = new StateHandler(); @Override protected void onPause() { super.onPause(); stateHandler.pause(); } @Override protected void onPostResume() { super.onPostResume(); stateHandler.resume(this); } }
      
      







あとは、新しい状態ハンドラーを使用してトランザクションをラップするだけです。



  stateHandler.run(new Runnable() { @Override public void run() { new TestDialog().show(getSupportFragmentManager(), "dialog"); } });
      
      







状態が許可する場合、Runnable内のコードはすぐに実行されます。 それ以外の場合、コードはキューに配置され、アクティビティが復元された後に実行されます。



同様に、onPostResumeメソッドの代わりにのみ、フラグメント内でこのクラスを使用して、ベースフラグメントのonResumeメソッドからハンドラコードを呼び出すことができます。



この例のソースコードはgithubにあります。



All Articles