バックグラウンドスレッドでインターネットからデータをダウンロードし(AsyncTask
を使用)、ダウンロード中に進行状況ダイアログを表示します。向きが変わり、アクティビティが再起動されてからAsyncTaskが完了しました-進行ダイアログを閉じて、新しいアクティビティを開始したいと思います。ただし、dismissDialogを呼び出すと、例外がスローされることがあります(おそらく、アクティビティが破棄され、新しいアクティビティがまだ開始されていないためです)。
この種の問題(ユーザーが向きを変えても機能するバックグラウンドスレッドからUIを更新する)を処理する最良の方法は何ですか? Googleの誰かが「公式ソリューション」を提供しましたか?
ステップ#1:AsyncTask
をstatic
ネストされたクラス、または内部(非静的なネストされた)クラスではなく、完全に別個のクラスにします。
ステップ#2:コンストラクターとセッターを介して設定されたデータメンバーを介してAsyncTask
をActivity
に保持させます。
ステップ#3:AsyncTask
を作成するとき、現在のActivity
をコンストラクターに提供します。
ステップ#4:onRetainNonConfigurationInstance()
で、現在進行中の元のアクティビティからデタッチした後、AsyncTask
を返します。
ステップ#5:onCreate()
で、getLastNonConfigurationInstance()
がnull
でない場合、それをAsyncTask
クラスにキャストし、セッターを呼び出して新しいアクティビティをタスクに関連付けます。
ステップ#6:doInBackground()
のアクティビティデータメンバーを参照しないでください。
上記のレシピに従えば、すべてうまくいきます。 onProgressUpdate()
とonPostExecute()
は、onRetainNonConfigurationInstance()
の開始から後続のonCreate()
の終了まで中断されます。
ここにサンプルプロジェクトがあります テクニックのデモ。
別のアプローチは、AsyncTask
を捨てて、作業をIntentService
に移動することです。これは、実行する作業が長く、アクティビティ(たとえば、大きなファイルのダウンロード)に関してユーザーが何をするかに関係なく続行する必要がある場合に特に役立ちます。順序付けられたブロードキャストIntent
を使用して、アクティビティを実行中の作業に応答させる(まだフォアグラウンドにある場合)か、Notification
を上げて、作業が完了したかどうかをユーザーに知らせることができます。 。 これはブログ投稿です このパターンの詳細。
受け入れられた答えは非常に役に立ちましたが、進行ダイアログはありません。
読者の皆様にとって幸いなことに、私は 進行状況ダイアログ付きのAsyncTaskの非常に包括的で実用的な例 !を作成しました。
マニフェストファイルの編集に頼ることなく、このジレンマの解決策を見つけるために1週間苦労しました。このソリューションの前提条件は次のとおりです。
実装
この投稿の下部にある2つのファイルをワークスペースにコピーする必要があります。以下を確認してください:
すべてのActivity
sはBaseActivity
を拡張する必要があります
onCreate()
では、ASyncTask
sがアクセスする必要があるメンバーを初期化した後に、super.onCreate()
を呼び出す必要があります。また、getContentViewId()
をオーバーライドして、フォームレイアウトIDを提供します。
onCreateDialog()
通常のように をオーバーライドして、アクティビティによって管理されるダイアログを作成します。
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;
}
}
Googleの誰かが「公式ソリューション」を提供しましたか?
はい
解決策は、一部のコードというよりも、アプリケーションアーキテクチャの提案です。
彼らは、つのデザインパターンを提案しました。これにより、アプリケーションの状態に関係なく、アプリケーションはサーバーと同期して動作できます(ユーザーはアプリを終了し、ユーザーは画面を変更し、アプリは終了します。バックグラウンドデータ操作が中断される可能性のある他のすべての状態。これがカバーします)
この提案は、Virgil Dobjanschiによる Android RESTクライアントアプリケーション スピーチ中のスピーチGoogle I/O 2010で説明されています。 1時間の長さですが、非常に見る価値があります。
その基本は、ネットワーク操作をService
に抽象化することです。これは、アプリケーション内の任意のActivity
に対して独立して動作します。データベースを使用している場合、ContentResolver
およびCursor
を使用すると、追加ロジックなしでUIを更新するのに便利な、すぐに使用可能なObserverパターンが得られます。取得したリモートデータでローカルデータベースを更新しました。他の操作後コードは、Service
に渡されるコールバックを介して実行されます(これにはResultReceiver
サブクラスを使用します)。
とにかく、私の説明は実際にはかなりあいまいです、あなたは間違いなくスピーチを見る必要があります。
Markの(CommonsWare)の回答は方向の変更に対して実際に機能しますが、Activityが直接破壊されると(電話の場合のように)失敗します。
Applicationオブジェクトを使用してASyncTaskを参照することにより、方向の変更とまれに破棄されるActivityイベントを処理できます。
問題と解決策に関する優れた説明があります ここ :
これを理解したのは、ライアンに完全にクレジットされています。
4年後、GoogleはActivity onCreateでsetRetainInstance(true)を呼び出すだけで問題を解決しました。デバイスのローテーション中にアクティビティインスタンスが保持されます。古いAndroid向けのシンプルなソリューションもあります。
アクティビティハンドラを使用して、すべてのアクティビティアクションを呼び出す必要があります。そのため、何らかのスレッドを使用している場合は、Runnableを作成し、Activitieのハンドラーを使用してポストする必要があります。そうしないと、致命的な例外でアプリがクラッシュすることがあります。
これは私の解決策です: https://github.com/Gotchamoh/Android-AsyncTask-ProgressDialog
基本的に手順は次のとおりです。
onSaveInstanceState
を使用して、まだ処理中のタスクを保存します。onCreate
で、タスクが保存されていれば取得します。onPause
に表示されている場合、ProgressDialog
を破棄します。onResume
には、タスクがまだ処理中である場合にProgressDialog
が表示されます。