バックグラウンドタスク、進行状況ダイアログ、向きの変更-100%有効な解決策はありますか?


235

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

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


4
このトピックに関する私のブログ投稿が役立つかもしれません。これは、構成の変更全体で長時間実行されるタスクを保持することです。
Alex Lockwood 2013

1
この質問も関連しています。
Alex Lockwood 2014年

FTRだけに関連する謎があります.. stackoverflow.com/q/23742412/294884
Fattie

回答:


336

ステップ#1:あなたの作成し、ネストされたクラス、または全く別のクラスだけではない内側の(非静的ネストされた)クラスを。AsyncTaskstatic

ステップ#2:データメンバーを介してAsyncTask保持しActivity、コンストラクターとセッターを介して設定します。

ステップ#3:を作成するとき、コンストラクタにAsyncTask電流Activityを供給します。

ステップ#4:で、元の廃止予定のアクティビティからデタッチした後onRetainNonConfigurationInstance()、を返しAsyncTaskます。

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

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

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

これは、テクニックを示すサンプルプロジェクトです。

もう1つの方法は、を破棄してAsyncTask、作業をに移動することIntentServiceです。これは、実行する作業が長く、ユーザーがアクティビティ(大きなファイルのダウンロードなど)に関して何をするかに関係なく続行する必要がある場合に特に便利です。順序付けされたブロードキャストIntentを使用して、アクティビティが実行中の作業に応答するか(まだフォアグラウンドにある場合)、またはNotificationを発生させて、作業が完了したかどうかをユーザーに通知できます。これは、このパターンの詳細を記したブログ投稿です。


8
この一般的な問題に対するすばらしい回答に感謝します。念のため、AsyncTaskのアクティビティを切り離す(nullに設定する)必要があることを手順4に追加します。ただし、これはサンプルプロジェクトでよく説明されています。
Kevin Gaudin、

3
しかし、アクティビティのメンバーにアクセスする必要がある場合はどうなりますか?
ユージン

3
@Andrew:静的内部クラスまたはいくつかのオブジェクトを保持する何かを作成し、それを返します。
CommonsWare、2011

11
onRetainNonConfigurationInstance()は非推奨であり、推奨される代替手段はを使用することsetRetainInstance()ですが、オブジェクトを返しません。asyncTaskで構成変更を処理することは可能setRetainInstance()ですか?
IndrekKõue2012年

10
@SYLARRR:もちろんです。持ってFragmentホールドAsyncTask。持ってFragment通話をsetRetainInstance(true)それ自体に。とAsyncTaskのみ話してくださいFragment。これで、構成の変更時に、Fragmentは破棄されて再作成されず(アクティビティが存在する場合でも)、そのため、AsyncTaskは構成の変更後も保持されます。
CommonsWare 2012年

13

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

幸いなことに、読者の皆さん、私は、進行状況ダイアログを備えたAsyncTaskの非常に包括的で実用的な例を作成しました。

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

受け入れられた答えは、静的クラスに関するものです(メンバーではありません)。これら、AsyncTaskが外部クラスインスタンスへの(非表示の)ポインターを持ち、アクティビティの破棄時にメモリリークになることを回避するために必要です。
Bananeweizen 2013年

ええ、なぜ私が静的メンバーについてそれを置いたのか分かりません、私も実際にそれらを使用したので... 回答を編集しました。
Timmmm 2013年

リンクを更新していただけませんか?本当に必要です。
Romain Pellerin

申し訳ありませんが、私のウェブサイトを復元することに慣れていません-私はすぐにやります!しかし、その間にそれは基本的にこの答えのコードと同じです:stackoverflow.com/questions/8417885/...
Timmmm

1
リンクは偽です。コードがどこにあるのかを示すことなく、役に立たないインデックスにつながるだけです。
FractalBob 2015

9

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

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

実装

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

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

  2. ではonCreate()super.onCreate()がアクセスする必要のあるメンバーを初期化した後で呼び出す必要がありますASyncTask。また、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;
    }
}

4

Googleの誰かが「公式の解決策」を提供しましたか?

はい。

解決策は、単なるコードではなく、アプリケーションアーキテクチャの提案にあります

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

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

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

とにかく、私の説明は実際にはかなりあいまいです、あなたは間違いなく演説を見るべきです。


2

Markの(CommonsWare)回答は実際に向きを変更するために機能しますが、アクティビティが直接(電話の場合のように)破棄されると失敗します。

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

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

クレジットは、これを理解するために完全にライアンに行きます。


1

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


1
Androidがローテーション、キーボード拡張などのイベントでアクティビティクラスを破棄するが、非同期タスクが破棄されたインスタンスへの参照を保持し、UIの更新にそれを使用しようとするため、人々が観察する問題が発生します。マニフェストまたは実用的にアクティビティを破棄しないようにAndroidに指示できます。この場合、非同期タスク参照は有効なままで、問題は観察されません。ローテーションでは、ビューの再読み込みなどの追加作業が必要になる場合があるため、アクティビティを保持することはお勧めしません。だからあなたが決める。
Singagirl、2015

ありがとう、私は状況を知っていましたが、setRetainInstance()については知りませんでした。私が理解していないのは、Googleがこれを使用して質問で尋ねられた問題を解決したというあなたの主張です。情報源をリンクできますか?ありがとう。
jj_ 2015


onRetainNonConfigurationInstance()この関数は、純粋に最適化として呼び出されるため、呼び出されることに依存してはなりません。<同じソースから:developer.android.com/reference/android/app/...
Dhananjay M

0

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


0

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

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

  1. onSaveInstanceStateまだ処理中の場合は、タスクの保存に使用します。
  2. onCreate、それが保存されていた場合、私のタスクを取得します。
  3. onPause、私は捨てるProgressDialogことが示されている場合。
  4. では、タスクがまだ処理中かどうかonResumeを示しProgressDialogます。
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.