AsyncTaskLoader
やCursorLoader
など、AsyncTask
では不便だったいくつかのことがLoader
という仕組みによって解決されました。
主には、AsyncTask
で悩みの種であったActivity
のライフサイクルとの分離が、Loader
によって実現されます。これによって、画面回転したときにAsyncTask
のインスタンスをうまいこと作りなおさないようにしつつも、コールバックを受けるオブジェクトの参照は新しいActivity
に紐付いたものにしなければならない、ということも、システムが勝手にやってくれるようになるので、管理がとても楽になります。
一方で、結構扱い方には注意が必要なことがあって、実装者が知った上で使用すべきこともいくつかあります。
Loader のライフサイクル
LoaderManager
のインスタンス取得は Activity#onStart()
より前で呼ぶ
LoaderManager
は、1 つのActivity
の中でシングルトンであることが保証されていて、Activity
のライフサイクルに基いてLoader
を管理してくれています。画面回転等でActivity
を再生成すると、LoaderManager
のインスタンスも引き継がれます。
で、Loader
をキックする(非同期処理を開始する)タイミングは、Activity#onStart()
のタイミングなので、Activity
が起動したらLoader
が動くようにしたい場合、LoaderManager
でLoader
の初期化をするのはそれ以前にしておく必要があります。
そうすると、必然的にLoaderManager
のインスタンスを得るためのActivity#getSupportLoaderManager()
や Activity#getLoaderManager()
も、Activity#onStart()
以前に呼んでおかないとダメということになります。
LoaderCallback#onLoadFinished() の呼ばれるタイミング
Loader
の処理が終わって結果を通知する際に、LoaderCallback#onLoadFinished()
が呼ばれます。
Activity#onStart()
によって Loader が動き始めるため、Activity
のライフサイクルが一周するとそのたびにLoader
が動作し、LoaderCallback#onLoadFinished()
も呼ばれることになります。
なので、例えば、別の画面に遷移してから戻ってきた時も、Activity#onRestart()
からのActivity#onStart()
をたどるので、LoaderCallback#onLoadFinished()
が呼ばれます。
さらに、LoaderManager
にはバグがあって、Fragment
が configuration change(画面回転等)で再生成されると、LoaderCallback#onLoadFinished()
を 2 度呼びます。
何れにしても、LoaderCallback#onLoadFinished()
は、Activity
のライフサイクル管理によって何度も呼ばれる可能性があるものになるため、開発者は、何度も呼ばれても良いような設計・実装をしておく必要があります。
例えば、データに更新がなければ何もしない、重複したデータを持たない構造にする、など。
One-Shot な Loader の使い方
これまでのAsyncTask
のような、一回限りのライフサイクルのLoader
とすることで、不用意にLoaderCallback#onLoadFinished()
が複数回呼ばれる事象を回避出来ます。
Loader
がその非同期処理を終え、LoaderCallback#onLoadFinished()
に結果が通知されたタイミングで、Loader
を破棄すれば、LoaderManager
はそれ以上Loader
のライフサイクルを管理しなくなります。
ただし、One-Shot であるため、Loader
による非同期処理を実行する度にLoader
のインスタンスを都度生成するコストが増えます。
Configuration change を気にしなくて良いのならば、AsyncTask
を使うほうが良いかもしれませんが、LoaderManager
はActivity
や Fragment
のライフサイクルを見て、適切なタイミングになるまでLoader
の結果をキャッシュしてくれていたりもするので、まずはLoader
を使うというところを前提に作っていくのが良いと思います。
LoaderCallback
と FragmentManager
LoaderCallback#onLoadFinished()
で FragmentTransaction
を取り扱ってはならない
よく、非同期処理中に進捗ダイアログを出し、終わったら消す、みたいなことをすると思います。
で、進捗ダイアログと言えば、DialogFragment
を用いて実装することになると思いますが、Loader
の処理が終わったタイミングであるところの LoaderCallback#onLoadFinished()
の中で DialogFragment
を操作しようとすると、例外によって怒られます。
これは、LoaderCallback#onLoadFinished()
の中で FragmentTransaction
を取り扱うことが出来ないからです。これは公式のドキュメントにも書いてあります。
Called when a previously created loader has finished its load. Note that normally an application is not allowed to commit fragment transactions while in this call, since it can happen after an activity's state is saved. See FragmentManager.openTransaction() for further discussion on this.
このため、LoaderCallback#onLoadFinished()
で FragmentTransaction
を取り扱う場合は、Handler
を用いて、メインスレッドの Looper
にメッセージを投げて、メインスレッド上で FragmentTransaction
を取り扱うようにする必要があります。
public void onLoadFinished(Loader<HogeLoader> loader, Hoge result) {
new Handler().post(new Runnalbe() {
@Override
public void run() {
// ここで Fragment を操作する
}
});
}
Loader の id
LoaderManager
は、Activity
ごとに異なるインスタンスとなりますので、異なるActivity
間で異なる Loader
の ID を割り振らなければいけないということはなく、同一のActivity
内でユニークになるように ID を割り振るだけでよいのです。