web-dev-qa-db-ja.com

androidのWeakReference / AsyncTaskパターン

Androidでこの頻繁に発生する状況に関する質問があります。

Mainアクティビティがあり、mainactivityの参照とともにAsyncTaskを呼び出して、AsyncTaskがMainActivityのビューを更新できるようにします。

イベントをステップに分解します

  • MainActivityはAyncTaskを作成し、その参照を渡します。
  • AysncTaskは、作業を開始し、たとえば10個のファイルをダウンロードします
  • ユーザーがデバイスの向きを変更しました。これにより、AsyncTaskに孤立したポインターが作成されます。
  • AsyncTaskが完了し、アクティビティにアクセスしてステータスを更新しようとすると、nullポインタが原因でクラッシュします。

上記の解決策は、「Pro Android 4」という本で推奨されているように、AsyncTaskにWeakReferenceを保持することです。

WeakReference<Activity> weakActivity;

in method onPostExecute

Activity activity = weakActivity.get();
if (activity != null) {
   // do your stuff with activity here
}

これはどのように状況を解決しますか?

私の質問は、私のasynctaskが10個のファイルをダウンロードしており、5の完了時に(向きの変更のため)アクティビティが再開される場合、FileDownloadingTaskがもう一度呼び出されますか?.

最初に呼び出された以前のAsyncTaskはどうなりますか?

ありがとう、そして質問の長さをおaびします。

これはどのように状況を解決しますか?

WeakReferenceを使用すると、Activityをガベージコレクションできるため、メモリリークは発生しません。

Null参照は、AsyncTaskcannotが、接続されなくなったユーザーインターフェイスを盲目的に更新しようとすることを意味し、例外をスローします(たとえば、ウィンドウマネージャに接続されていないビュー)。もちろん、NPEを避けるためにnullをチェックする必要があります。

私のasynctaskが10個のファイルをダウンロードしており、5の完了時に(方向の変更のため)アクティビティが再開されると、FileDownloadingTaskがもう一度呼び出されますか?.

実装に依存しますが、おそらくそうです-結果をどこかにキャッシュするなど、意図的に繰り返しダウンロードを不要にするために何かをしない場合。

最初に呼び出された以前のAsyncTaskはどうなりますか?

以前のバージョンのAndroidは、完了するまで実行され、すべてのファイルをダウンロードするだけで、それらを破棄します(実装によってはキャッシュすることもあります)。

新しいAndroidでは、AsyncTaskがそれらを開始したActivityとともに殺されているのではないかと疑っていますが、私の疑いの根拠は、メモリリークデモが RoboSpice =(以下を参照)私のJellyBeanデバイスで実際にリークしないでください。

アドバイスを提供する場合:AsyncTaskは、ネットワークなどの潜在的に長時間実行されるタスクの実行には適していません。

単一のワーカースレッドが受け入れられる場合、IntentServiceはより良い(そして比較的単純な)アプローチです。スレッドプールを制御する場合は、(ローカル)Serviceを使用します。メインスレッドで作業しないように注意してください。

RoboSpice バックグラウンドで確実にネットワークを実行する方法を探している場合は良いようです(免責事項:私は試していません;私は提携していません)。 Play Storeには RoboSpice Motivations demo app があり、whyを説明しています。これはAsyncTaskでうまくいかない可能性があります-WeakReferenceの回避策を含みます。

このスレッドも参照してください: AsyncTaskは本当に概念的に欠陥がありますか、それとも何か不足していますか?

更新:

githubプロジェクト を作成し、別のSOの質問にIntentServiceを使用してダウンロードする例を示します( How to fix Android.os.NetworkOnMainThreadException ? )しかし、ここでも関連があると思いますonActivityResultを介して結果を返すことにより、デバイスを回転させたときに飛行中のダウンロードがActivityを再起動しました。

34
Stevie

WeakReferenceクラスは、基本的に、JREが特定のインスタンスの参照カウンターを増やすことを防止します。

Javaのメモリ管理には触れず、あなたの質問に直接答えません。WeakReferenceは、AsyncTaskに親アクティビティがまだ有効かどうかを知る方法を提供することで状況を解決します。

方向の変更自体は、AsyncTaskを自動的に再起動しません。既知のメカニズム(onCreate/onDestroyonSave/RestoreInstanceState)。

元のAsyncTaskに関して、これらのオプションのどれが起こるかは100%確信できません。

  • いずれかのJavaはスレッドを停止し、AsyncTaskを破棄します。これは、参照を保持している唯一のオブジェクト(元のActivity)が破棄されるためです。
  • または、内部のJavaオブジェクトがAsyncTaskオブジェクトへの参照を維持し、ガベージコレクションをブロックし、バックグラウンドでAsyncTaskを実質的に残します

いずれにしても、AsyncTaskを手動で中断/一時停止および再起動/再開する(または新しいActivityに引き渡す)か、代わりにServiceを使用することをお勧めします。

7
domsom

これはどのように状況を解決しますか?

そうではありません。

WeakReferenceのリファレントは、ガベージコレクターがリファレントが弱く到達可能であると判断した場合、nullに設定されます。これは、アクティビティが一時停止されている場合は発生せず、アクティビティが破棄されてフレームワークがそのアクティビティへのすべての参照を破棄したときに必ずしもすぐに発生するわけではありません。 GCが実行されていない場合、AsyncTaskがデッドアクティビティへの参照をまだ含んでいる間にWeakReferenceが完了する可能性があります。

それだけでなく、このアプローチはAsyncTaskがCPUを無駄に消費するのを防ぐために何もしません。

より良い方法は、適切なティアダウンライフサイクルメソッドでActivityAsyncTaskcancel(...) への強い参照を維持させることです。 AsyncTaskisCancelled() を監視し、不要になったら動作を停止する必要があります。

AsyncTaskを構成の変更(ただしnot他の形式のアクティビティの破壊)を越えて存続させたい場合は、保持されたフラグメントでホストできます。

1
Kevin Krumwiede