109
103

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Loader について知っておいたほうが良いこと 3 つ

Posted at

AsyncTaskLoaderCursorLoaderなど、AsyncTaskでは不便だったいくつかのことがLoaderという仕組みによって解決されました。
主には、AsyncTaskで悩みの種であったActivityのライフサイクルとの分離が、Loaderによって実現されます。これによって、画面回転したときにAsyncTaskのインスタンスをうまいこと作りなおさないようにしつつも、コールバックを受けるオブジェクトの参照は新しいActivityに紐付いたものにしなければならない、ということも、システムが勝手にやってくれるようになるので、管理がとても楽になります。

一方で、結構扱い方には注意が必要なことがあって、実装者が知った上で使用すべきこともいくつかあります。

Loader のライフサイクル

LoaderManager のインスタンス取得は Activity#onStart() より前で呼ぶ

LoaderManager は、1 つのActivityの中でシングルトンであることが保証されていて、Activityのライフサイクルに基いてLoader を管理してくれています。画面回転等でActivityを再生成すると、LoaderManagerのインスタンスも引き継がれます。

で、Loaderをキックする(非同期処理を開始する)タイミングは、Activity#onStart()のタイミングなので、Activityが起動したらLoaderが動くようにしたい場合、LoaderManagerLoaderの初期化をするのはそれ以前にしておく必要があります。

そうすると、必然的に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を使うほうが良いかもしれませんが、LoaderManagerActivityFragmentのライフサイクルを見て、適切なタイミングになるまでLoaderの結果をキャッシュしてくれていたりもするので、まずはLoader を使うというところを前提に作っていくのが良いと思います。

LoaderCallbackFragmentManager

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 を割り振るだけでよいのです。

109
103
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
109
103

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?