web-dev-qa-db-ja.com

バックグラウンドタスク、進行状況ダイアログ、向きの変更-100%実用的なソリューションはありますか?

バックグラウンドスレッドでインターネットからデータをダウンロードし(AsyncTaskを使用)、ダウンロード中に進行状況ダイアログを表示します。向きが変わり、アクティビティが再起動されてからAsyncTaskが完了しました-進行ダイアログを閉じて、新しいアクティビティを開始したいと思います。ただし、dismissDialogを呼び出すと、例外がスローされることがあります(おそらく、アクティビティが破棄され、新しいアクティビティがまだ開始されていないためです)。

この種の問題(ユーザーが向きを変えても機能するバックグラウンドスレッドからUIを更新する)を処理する最良の方法は何ですか? Googleの誰かが「公式ソリューション」を提供しましたか?

233
fhucho

ステップ#1:AsyncTaskstaticネストされたクラス、または内部(非静的なネストされた)クラスではなく、完全に別個のクラスにします。

ステップ#2:コンストラクターとセッターを介して設定されたデータメンバーを介してAsyncTaskActivityに保持させます。

ステップ#3:AsyncTaskを作成するとき、現在のActivityをコンストラクターに提供します。

ステップ#4:onRetainNonConfigurationInstance()で、現在進行中の元のアクティビティからデタッチした後、AsyncTaskを返します。

ステップ#5:onCreate()で、getLastNonConfigurationInstance()nullでない場合、それをAsyncTaskクラスにキャストし、セッターを呼び出して新しいアクティビティをタスクに関連付けます。

ステップ#6:doInBackground()のアクティビティデータメンバーを参照しないでください。

上記のレシピに従えば、すべてうまくいきます。 onProgressUpdate()onPostExecute()は、onRetainNonConfigurationInstance()の開始から後続のonCreate()の終了まで中断されます。

ここにサンプルプロジェクトがあります テクニックのデモ。

別のアプローチは、AsyncTaskを捨てて、作業をIntentServiceに移動することです。これは、実行する作業が長く、アクティビティ(たとえば、大きなファイルのダウンロード)に関してユーザーが何をするかに関係なく続行する必要がある場合に特に役立ちます。順序付けられたブロードキャストIntentを使用して、アクティビティを実行中の作業に応答させる(まだフォアグラウンドにある場合)か、Notificationを上げて、作業が完了したかどうかをユーザーに知らせることができます。 。 これはブログ投稿です このパターンの詳細。

335
CommonsWare

受け入れられた答えは非常に役に立ちましたが、進行ダイアログはありません。

読者の皆様にとって幸いなことに、私は 進行状況ダイアログ付きのAsyncTaskの非常に包括的で実用的な例 !を作成しました。

  1. 回転は機能し、ダイアログは存続します。
  2. 戻るボタンを押すと、タスクとダイアログをキャンセルできます(この動作が必要な場合)。
  3. フラグメントを使用します。
  4. デバイスが回転すると、アクティビティの下のフラグメントのレイアウトが適切に変更されます。
13
Timmmm

マニフェストファイルの編集に頼ることなく、このジレンマの解決策を見つけるために1週間苦労しました。このソリューションの前提条件は次のとおりです。

  1. 常に進行状況ダイアログを使用する必要があります
  2. 一度に1つのタスクのみが実行されます
  3. 電話を回転させてもタスクが持続し、進行状況ダイアログが自動的に閉じるようにする必要があります。

実装

この投稿の下部にある2つのファイルをワークスペースにコピーする必要があります。以下を確認してください:

  1. すべてのActivitysはBaseActivityを拡張する必要があります

  2. onCreate()では、ASyncTasksがアクセスする必要があるメンバーを初期化した後に、super.onCreate()を呼び出す必要があります。また、getContentViewId()をオーバーライドして、フォームレイアウトIDを提供します。

  3. onCreateDialog()通常のように をオーバーライドして、アクティビティによって管理されるダイアログを作成します。

  4. AsyncTasksを作成するためのサンプルの静的内部クラスについては、以下のコードを参照してください。結果をmResultに保存して、後でアクセスできます。


final static class MyTask extends SuperAsyncTask<Void, Void, Void> {

    public OpenDatabaseTask(BaseActivity activity) {
        super(activity, MY_DIALOG_ID); // change your dialog ID here...
                                       // and your dialog will be managed automatically!
    }

    @Override
    protected Void doInBackground(Void... params) {

        // your task code

        return null;
    }

    @Override
    public boolean onAfterExecute() {
        // your after execute code
    }
}

最後に、新しいタスクを起動するには:

mCurrentTask = new MyTask(this);
((MyTask) mCurrentTask).execute();

それだけです!この堅牢なソリューションが誰かを助けることを願っています。

BaseActivity.Java(インポートを自分で整理する)

protected abstract int getContentViewId();

public abstract class BaseActivity extends Activity {
    protected SuperAsyncTask<?, ?, ?> mCurrentTask;
    public HashMap<Integer, Boolean> mDialogMap = new HashMap<Integer, Boolean>();

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

        setContentView(getContentViewId());

        mCurrentTask = (SuperAsyncTask<?, ?, ?>) getLastNonConfigurationInstance();
        if (mCurrentTask != null) {
            mCurrentTask.attach(this);
            if (mDialogMap.get((Integer) mCurrentTask.dialogId) != null
                && mDialogMap.get((Integer) mCurrentTask.dialogId)) {
        mCurrentTask.postExecution();
            }
        }
    }

    @Override
    protected void onPrepareDialog(int id, Dialog dialog) {
    super.onPrepareDialog(id, dialog);

        mDialogMap.put(id, true);
    }

    @Override
    public Object onRetainNonConfigurationInstance() {
        if (mCurrentTask != null) {
            mCurrentTask.detach();

            if (mDialogMap.get((Integer) mCurrentTask.dialogId) != null
                && mDialogMap.get((Integer) mCurrentTask.dialogId)) {
                return mCurrentTask;
            }
        }

        return super.onRetainNonConfigurationInstance();
    }

    public void cleanupTask() {
        if (mCurrentTask != null) {
            mCurrentTask = null;
            System.gc();
        }
    }
}

SuperAsyncTask.Java

public abstract class SuperAsyncTask<Params, Progress, Result> extends AsyncTask<Params, Progress, Result> {
    protected BaseActivity mActivity = null;
    protected Result mResult;
    public int dialogId = -1;

    protected abstract void onAfterExecute();

    public SuperAsyncTask(BaseActivity activity, int dialogId) {
        super();
        this.dialogId = dialogId;
        attach(activity);
    }

    @Override
    protected void onPreExecute() {
        super.onPreExecute();
        mActivity.showDialog(dialogId); // go polymorphism!
    }    

    protected void onPostExecute(Result result) {
        super.onPostExecute(result);
        mResult = result;

        if (mActivity != null &&
                mActivity.mDialogMap.get((Integer) dialogId) != null
                && mActivity.mDialogMap.get((Integer) dialogId)) {
            postExecution();
        }
    };

    public void attach(BaseActivity activity) {
        this.mActivity = activity;
    }

    public void detach() {
        this.mActivity = null;
    }

    public synchronized boolean postExecution() {
        Boolean dialogExists = mActivity.mDialogMap.get((Integer) dialogId);
        if (dialogExists != null || dialogExists) {
            onAfterExecute();
            cleanUp();
    }

    public boolean cleanUp() {
        mActivity.removeDialog(dialogId);
        mActivity.mDialogMap.remove((Integer) dialogId);
        mActivity.cleanupTask();
        detach();
        return true;
    }
}
8
Oleg Vaskevich

Googleの誰かが「公式ソリューション」を提供しましたか?

はい

解決策は、一部のコードというよりも、アプリケーションアーキテクチャの提案です。

彼らは、つのデザインパターンを提案しました。これにより、アプリケーションの状態に関係なく、アプリケーションはサーバーと同期して動作できます(ユーザーはアプリを終了し、ユーザーは画面を変更し、アプリは終了します。バックグラウンドデータ操作が中断される可能性のある他のすべての状態。これがカバーします)

この提案は、Virgil Dobjanschiによる Android RESTクライアントアプリケーション スピーチ中のスピーチGoogle I/O 2010で説明されています。 1時間の長さですが、非常に見る価値があります。

その基本は、ネットワーク操作をServiceに抽象化することです。これは、アプリケーション内の任意のActivityに対して独立して動作します。データベースを使用している場合、ContentResolverおよびCursorを使用すると、追加ロジックなしでUIを更新するのに便利な、すぐに使用可能なObserverパターンが得られます。取得したリモートデータでローカルデータベースを更新しました。他の操作後コードは、Serviceに渡されるコールバックを介して実行されます(これにはResultReceiverサブクラスを使用します)。

とにかく、私の説明は実際にはかなりあいまいです、あなたは間違いなくスピーチを見る必要があります。

Markの(CommonsWare)の回答は方向の変更に対して実際に機能しますが、Activityが直接破壊されると(電話の場合のように)失敗します。

Applicationオブジェクトを使用してASyncTaskを参照することにより、方向の変更とまれに破棄されるActivityイベントを処理できます。

問題と解決策に関する優れた説明があります ここ

これを理解したのは、ライアンに完全にクレジットされています。

2
Scott Biggs

4年後、GoogleはActivity onCreateでsetRetainInstance(true)を呼び出すだけで問題を解決しました。デバイスのローテーション中にアクティビティインスタンスが保持されます。古いAndroid向けのシンプルなソリューションもあります。

1
Singagirl

アクティビティハンドラを使用して、すべてのアクティビティアクションを呼び出す必要があります。そのため、何らかのスレッドを使用している場合は、Runnableを作成し、Activitieのハンドラーを使用してポストする必要があります。そうしないと、致命的な例外でアプリがクラッシュすることがあります。

0
xpepermint

これは私の解決策です: https://github.com/Gotchamoh/Android-AsyncTask-ProgressDialog

基本的に手順は次のとおりです。

  1. onSaveInstanceStateを使用して、まだ処理中のタスクを保存します。
  2. onCreateで、タスクが保存されていれば取得します。
  3. onPauseに表示されている場合、ProgressDialogを破棄します。
  4. onResumeには、タスクがまだ処理中である場合にProgressDialogが表示されます。
0
Gotcha