13

AsyncTask 中に構成の変更を処理する方法に関する投稿は多数ありますが、AsyncTask が終了して DialogFragment (互換性ライブラリ) を閉じようとするときにバックグラウンド (onPause()) にあるアプリに関する明確な解決策を提供するものは見つかりませんでした。

onPostExecute() で DialogFragment を却下する必要がある AsyncTask を実行している場合、DialogFragment を却下しようとしたときにアプリがバックグラウンドにある場合、IllegalStateException が発生します。

private static class SomeTask extends AsyncTask<Void, Void, Boolean> {

    public SomeTask(SomeActivity tActivity)
    {
        mActivity = tActivity;
    }

    private SomeActivity mActivity;

    /** Set the view during/after config change */
    public void setView(Activity tActivity) {
        mActivity tActivity;
    }

    @Override
    protected Boolean doInBackground(Void... tParams) {
        try {
          //simulate some time consuming process
          TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException ignore) {}
        return true;
    }

    @Override
    protected void onPostExecute(Boolean tRouteFound) {
        mActivity.dismissSomeDialog();  
    }

}

アクティビティは次のようになります。

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

public class SomeActivity extends FragmentActivity {

    public void someMethod() {
        ...
        displaySomeDialog();
        new SomeTask(this).execute();
        ...
    }

    public void displaySomeDialog() {
        DialogFragment someDialog = new SomeDialogFragment();
        someDialog.show(getFragmentManager(), "dialog");
    }

    public void dismissSomeDialog() {
        SomeDialogFragment someDialog = (SomeDialogFragment) getFragmentManager().findFragmentByTag("dialog");
        someDialog.dismiss();
    }

    ....

}

SomeTask がまだ実行されている間にアプリがバックグラウンドに切り替わらない限り、正常に動作します。その場合、SomeTask が SomeDialog() を却下しようとすると、IllegalStateException が発生します。

05-25 16:36:02.237: E/AndroidRuntime(965): java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState

私が見たすべての投稿は、精巧な回避策を備えた不器用な方向を指しているようです。これを処理するアンドロイドの方法はありませんか?それが DialogFragment ではなく Dialog である場合、Activity の DismissDialog() はそれを正しく処理します。ACP からの DialogFragment ではなく実際の DialogFragment である場合は、dismissAllowingStateLoss() が処理します。DialogFragment の ACP 版にこんなものはありませんか?

4

6 に答える 6

22

フラグメントは各アクティビティの状態の一部として保存されるため、onSaveInstanceState()呼び出された後にトランザクションを実行することは技術的に意味がありません。

commitAllowingStateLoss()この場合、例外を回避するために絶対に使用したくないでしょう。例として、次のシナリオを検討してください。

  1. アクティビティは を実行しAsyncTaskます。はAsyncTaskを表示DialogFragmentonPreExecute()、バックグラウンド スレッドでそのタスクの実行を開始します。
  2. ユーザーが「ホーム」をクリックすると、Activityが停止し、強制的にバックグラウンドになります。システムは、デバイスのメモリがかなり不足していると判断したため、デバイスも破棄する必要があると判断しましたActivity
  3. AsyncTaskが完了し、呼び出さonPostExecute()れます。内部では、例外を回避するために使用をonPostExecute()却下します。DialogFragmentcommitAllowingStateLoss()
  4. ユーザーは に戻りますActivity。は、の保存された状態FragmentManagerに基づいてフラグメントの状態を復元します。Activity保存された状態は、 が呼び出された後は何も知らないonSaveInstanceState()ため、 を却下する要求はDialogFragment記憶されず、が既に完了していDialogFragmentても が復元されます。AsyncTask

このような奇妙なバグが時折発生する可能性があるため、通常、commitAllowingStateLoss()この例外を回避するために使用することはお勧めできません。コールバック メソッド (バックグラウンド スレッドが作業を終了したことに応答して呼び出される) は、ライフサイクル メソッド (デバイスの落下など、システム全体の外部イベントに応答してシステム サーバー プロセスによって呼び出されるAsyncTask) とはまったく関係がないためです。Activity眠っている、またはメモリが不足している)、これらの状況を処理するには、少し余分な作業を行う必要があります。もちろん、これらのバグは非常にまれであり、それらからアプリを保護することは、多くの場合、Play ストアでの 1 つ星の評価と 5 つ星の評価の違いではありませんが、それでも注意が必要です。

うまくいけば、少なくともある程度の意味がありました。Dialogまた、 s も s 状態の一部として存在することに注意してください。そのActivityため、単純な古いものを使用するDialogと例外を回避できる可能性がありますが、基本的に同じ問題が発生します (つまり、の状態が後で復元されDialogたときに、 を破棄しても記憶されません)。 Activity.

率直に言って、最善の解決策は、AsyncTask. はるかにユーザーフレンドリーな解決策は、不確定な進行状況スピナーをActionBar(たとえば、G+ や Gmail アプリのように) に表示することです。非同期コールバックに応答してユーザー インターフェースに大きな変化を引き起こすことは、ユーザー エクスペリエンスに悪影響を及ぼします。これは予期せぬことであり、ユーザーが行っていることから突然引き離されるためです。

詳細については、件名に関するこのブログ投稿を参照してください。

于 2013-08-21T15:15:51.507 に答える
17

不正な状態の例外の問題を回避し、基本的にdismissAllowingStateLoss()を実装するには、次を使用して実行できます。

getFragmentManager().beginTransaction().remove(someDialog).commitAllowingStateLoss();

これにより、ハッキーなコードなしで問題が解決するはずです。ハンドラーを介してdialog.show()を使用してUIスレッドと通信するスレッドがある場合は、showにも同じことが適用できます。これも違法な状態の例外を引き起こす可能性があります

getFragmentManager().beginTransaction().add(someDialog).commitAllowingStateLoss();


ポスターの質問を考えると、@joneswahは正しいです。サポートライブラリを使用している場合は、

getFragmentManager()

getSupportFragmentManager()


将来のGoogle社員の場合:@Alex Lockwoodは、このソリューションに関して適切で有効な懸念を提起します。このソリューションはエラーを解決し、ほとんどの場合に機能しますが、UXの観点から、元の質問のアプローチに問題があることを示唆しています。

アクティビティは、非同期タスクが完了しない可能性があり、onPostExecute()を実行しないことを想定する必要があります。非同期操作をユーザーに通知するために開始されるUIアクション(つまり、スピナー、理想的にはダイアログではない)は、タイムアウト時に、または状態を追跡し、onRestore / onResumeタイプのライフサイクルイベントをチェックインして、 UIが正しく更新されます。サービスも調査する価値があるかもしれません。

于 2012-11-26T23:46:01.990 に答える
3

onPostExecute() で UI を更新する場合は、onPause() で AsyncTask をキャンセルする必要があります。アクティビティが一時停止している間は、UI を更新しようとしないでください。

例えば。onPause() で:

if (task != null) task.cancel(true);

タスクからの変更を次回まで保持する場合は、データ/変更を doInBackground() に保存し、アクティビティ/フラグメント/ダイアログが再開されたときに UI を更新します。

タスクからの変更を保持したくない場合は、 onPostExecute() まで変更を保存しないでください

于 2012-05-30T17:30:04.927 に答える
0

ユーザーが [戻る] または [ホーム] ボタンを押したために Android がアプリを停止すると、ダイアログが閉じられます。通常のトリックは、onStop()/onStart() の間のダイアログを保持することです。したがって、ダイアログを閉じる以上のことをする必要がない限り、心配する必要はないと思います。

編集: ダイアログをホストするアクティビティでは、ダイアログがまだ onStop() 内で開いている場合は、ダイアログを閉じたい場合があります。これにより、メモリ リークを防ぐことができます。ただし、これは AsyncTask からトリガーする必要はありません。

上で言ったように、問題は onStart() をもう一度押して AsyncTask がまだ完了していないときに何が起こるかです。それを判断する方法を見つけ、必要に応じてそのダイアログを再度開く必要があります。

于 2012-05-25T20:49:08.180 に答える
0

何度も再設計した後、私は最終的にうまくいくと思われるアプローチに落ち着きました. ただし、Android 開発の議論の他の場所で文書化されている私の設計の特定の側面を見つけることができませんでした。だから私はどんなフィードバックにも興味があります。

要約すると、私がしなければならなかったことはこれです:

  • onSaveInstanceState() - SomeTask が実行中の場合、単純なロックを使用して、一時停止中に SomeTask が doInBackground() を終了するのをブロックします。
  • onResume() - さまざまな再開状況を処理するために、switch ステートメントを挿入する必要がありました。アプリを起動する場合、何も一時停止していないため何もしません。非表示にした後または構成の変更後に再起動する場合は、保持された SomeTask インスタンスが中断したところから再開できるようにロックを解除します。
  • onDestroy() - SomeTask が実行中の場合はキャンセルします。

このソリューションのコード フラグメントは、元の投稿に掲載します。

于 2012-07-19T19:56:00.923 に答える