AsyncTaskLoaderの動作をソースから知る

目次


大事なのはLoaderManager

AsyncTaskLoaderの動作を知るためには、まずLoaderManagerの動作を知る必要がある。
まずは概略説明を読もう。
1.2 ローダ - ソフトウェア技術ドキュメントを勝手に翻訳
https://sites.google.com/a/techdoctranslator.com/jp/android/guide/activities/loaders
Loaders
http://developer.android.com/guide/topics/fundamentals/loaders.html

ソースからみるLoaderManagerの動作

LoaderManagerのログをONにする。

LoaderManager は親切な作りで、必要なときにソースからログを出せる。
まずはこれで動作を見るのが先決。
LoaderManager.enableDebugLogging(boolean)

動作ログを見てみる。

さらっと検証したソースについて説明。
オプションメニューから「item=start!」を選ぶとAsyncTaskLoaderがスタート。
適当にスリープして loadInBackground() を浪費したあと、
LoaderCallbacks を実装している AsyncTaskLoaderTestActivity に
onLoadFinished() がコールバックされる。

それぞれのソースは「まとめ」に掲載している。

普通に動作させたときのログ。

AsyncTaskLoaderTestActivity を起動して
メニューから forceLoad() した感じ。

見づらかったので加工して貼り付けてる。
LoaderManager のログは 「LoaderManager」というTAGで表示されるので分かりやすい。

AsyncTaskLoaderTestActivity:onCreate() Start.
TestApplication            :onActivityCreated() Start : act=AsyncTaskLoaderTestActivity, Bundle=null
TestApplication            :onActivityCreated() called=null
LoaderManager              : initLoader in LoaderManager{4165fc40 in AsyncTaskLoaderTestActivity{41659580}}: args=null
AsyncTaskLoaderTestActivity:onCreateLoader() Start.
TaskLoaderTest             :<init>() Start.
LoaderManager              :   Created new loader LoaderInfo{416605e0 #0 : TaskLoaderTest{41662a18}}
LoaderManager              : Starting in LoaderManager{4165fc40 in AsyncTaskLoaderTestActivity{41659580}}
LoaderManager              :   Starting: LoaderInfo{416605e0 #0 : TaskLoaderTest{41662a18}}
TaskLoaderTest             :onStartLoading() Start.
AsyncTaskLoaderTestActivity:onOptionsItemSelected() Start : item=start!
LoaderManager              : restartLoader in LoaderManager{4165fc40 in AsyncTaskLoaderTestActivity{41659580}}: args=Bundle[{}]
LoaderManager              :   Making last loader inactive: LoaderInfo{416605e0 #0 : TaskLoaderTest{41662a18}}
TaskLoaderTest             :onAbandon() Start.
AsyncTaskLoaderTestActivity:onCreateLoader() Start.
TaskLoaderTest             :<init>() Start.
LoaderManager              :   Starting: LoaderInfo{41631ce0 #0 : TaskLoaderTest{4163f0f8}}
TaskLoaderTest             :onStartLoading() Start.
TaskLoaderTest             :onForceLoad() Start.
TaskLoaderTest             :onLoadInBackground() Start.
TaskLoaderTest             :loadInBackground() Start.
TaskLoaderTest             :loadInBackground() sleep time = 0
TaskLoaderTest             :loadInBackground() sleep time = 500
~~~
TaskLoaderTest             :loadInBackground() sleep time = 4000
TaskLoaderTest             :loadInBackground() sleep time = 4500
TaskLoaderTest             :loadInBackground() End.
LoaderManager              : onLoadComplete: LoaderInfo{41631ce0 #0 : TaskLoaderTest{4163f0f8}}
LoaderManager              :   onLoadFinished in TaskLoaderTest{4163f0f8 id=0}: null}
AsyncTaskLoaderTestActivity:onLoadFinished() Start.
LoaderManager              :   Destroying: LoaderInfo{416605e0 #0 : TaskLoaderTest{41662a18}}
TaskLoaderTest             :onReset() Start.
 

AsyncTaskLoaderが動作中に縦横切替したときのログ。

メニューから forceLoad() したら、適当なタイミングで縦横切替。

LoaderManager で 「Retaining」 というログが出ている。
これは onSaveInstanceState() が呼ばれた後くらいにログにでる。
対応して 「Finished Retaining」 が onRestoreInstanceState() 呼び出し前くらいに呼ばれる。

AsyncTaskLoaderTestActivity:onOptionsItemSelected() Start : item=start!
LoaderManager              : restartLoader in LoaderManager{4165fc40 in AsyncTaskLoaderTestActivity{41683690}}: args=Bundle[{}]
LoaderManager              :   Making last loader inactive: LoaderInfo{41631ce0 #0 : TaskLoaderTest{4163f0f8}}
TaskLoaderTest             :onAbandon() Start.
AsyncTaskLoaderTestActivity:onCreateLoader() Start.
TaskLoaderTest             :<init>() Start.
LoaderManager              :   Starting: LoaderInfo{41612698 #0 : TaskLoaderTest{41625930}}
TaskLoaderTest             :onStartLoading() Start.
TaskLoaderTest             :onForceLoad() Start.
TaskLoaderTest             :onLoadInBackground() Start.
TaskLoaderTest             :loadInBackground() Start.
TaskLoaderTest             :loadInBackground() sleep time = 0
TaskLoaderTest             :loadInBackground() sleep time = 500
~~~
TaskLoaderTest             :loadInBackground() sleep time = 1500
TaskLoaderTest             :loadInBackground() sleep time = 2000
TestApplication            :onActivityPaused() Start : act=AsyncTaskLoaderTestActivity, isFinishing=false
LoaderManager              : Retaining in LoaderManager{4165fc40 in AsyncTaskLoaderTestActivity{41683690}}
LoaderManager              :   Retaining: LoaderInfo{41612698 #0 : TaskLoaderTest{41625930}}
TestApplication            :onActivityDestroyed() Start : act=test.asynktask.AsyncTaskLoaderTestActivity@41683690
LoaderManager              : Destroying Inactive in LoaderManager{4165fc40 in AsyncTaskLoaderTestActivity{41683690}}
LoaderManager              :   Destroying: LoaderInfo{41631ce0 #0 : TaskLoaderTest{4163f0f8}}
LoaderManager              :   Reseting: LoaderInfo{41631ce0 #0 : TaskLoaderTest{4163f0f8}}
AsyncTaskLoaderTestActivity:onLoaderReset() Start.
TaskLoaderTest             :onReset() Start.
AsyncTaskLoaderTestActivity:onCreate() Start.
TestApplication            :onActivityCreated() Start : act=AsyncTaskLoaderTestActivity, Bundle=Bundle[{...}]}]
TestApplication            :onActivityCreated() called=null
LoaderManager              : initLoader in LoaderManager{4165fc40 in AsyncTaskLoaderTestActivity{416aeff8}}: args=null
LoaderManager              :   Re-using existing loader LoaderInfo{41612698 #0 : TaskLoaderTest{41625930}}
LoaderManager              : Starting in LoaderManager{4165fc40 in AsyncTaskLoaderTestActivity{416aeff8}}
LoaderManager              : Finished Retaining in LoaderManager{4165fc40 in AsyncTaskLoaderTestActivity{416aeff8}}
LoaderManager              :   Finished Retaining: LoaderInfo{41612698 #0 : TaskLoaderTest{41625930}}
TaskLoaderTest             :loadInBackground() sleep time = 2500
TaskLoaderTest             :loadInBackground() sleep time = 3000
~~~
TaskLoaderTest             :loadInBackground() sleep time = 4000
TaskLoaderTest             :loadInBackground() sleep time = 4500
TaskLoaderTest             :loadInBackground() End.
LoaderManager              : onLoadComplete: LoaderInfo{41612698 #0 : TaskLoaderTest{41625930}}
LoaderManager              :   onLoadFinished in TaskLoaderTest{41625930 id=0}: null}
AsyncTaskLoaderTestActivity:onLoadFinished() Start.
 

AsyncTaskLoaderが動作中にActivityを終了したときのログ

メニューから forceLoad() したら、適当なタイミングでBackキー押下。

LoaderManagerは「Destroying」のログを出してTaskLoaderTestのonReset()をコールバックしている。
ここでキャンセル処理を入れていないので、loadInBackground() は走り続けている。
きちんとキャンセル処理を入れてやる必要があるね。

AsyncTaskLoaderTestActivity:onOptionsItemSelected() Start : item=start!
LoaderManager              : restartLoader in LoaderManager{41665380 in AsyncTaskLoaderTestActivity{4165bf40}}: args=Bundle[{}]
LoaderManager              :   Making last loader inactive: LoaderInfo{41665d20 #0 : TaskLoaderTest{41668158}}
TaskLoaderTest             :onAbandon() Start.
AsyncTaskLoaderTestActivity:onCreateLoader() Start.
TaskLoaderTest             :<init>() Start.
LoaderManager              :   Starting                   : LoaderInfo{416409d0 #0 : TaskLoaderTest{4166ecc8}}
TaskLoaderTest             :onStartLoading() Start.
TaskLoaderTest             :onForceLoad() Start.
TaskLoaderTest             :onLoadInBackground() Start.
TaskLoaderTest             :loadInBackground() Start.
TaskLoaderTest             :loadInBackground() sleep time = 0
TaskLoaderTest             :loadInBackground() sleep time = 500
TaskLoaderTest             :loadInBackground() sleep time = 1000
LoaderManager              : Stopping in LoaderManager{41665380 in AsyncTaskLoaderTestActivity{4165bf40}}
LoaderManager              :   Stopping                   : LoaderInfo{416409d0 #0 : TaskLoaderTest{4166ecc8}}
TaskLoaderTest             :onStopLoading() Start.
LoaderManager              : Destroying Active in LoaderManager{41665380 in AsyncTaskLoaderTestActivity{4165bf40}}
LoaderManager              :   Destroying                 : LoaderInfo{416409d0 #0 : TaskLoaderTest{4166ecc8}}
TaskLoaderTest             :onReset() Start.
LoaderManager              : Destroying Inactive in LoaderManager{41665380 in AsyncTaskLoaderTestActivity{4165bf40}}
LoaderManager              :   Destroying                 : LoaderInfo{41665d20 #0 : TaskLoaderTest{41668158}}
TaskLoaderTest             :onReset() Start.
TaskLoaderTest             :loadInBackground() sleep time = 1500
TaskLoaderTest             :loadInBackground() sleep time = 2000
~~~
TaskLoaderTest             :loadInBackground() sleep time = 4000
TaskLoaderTest             :loadInBackground() sleep time = 4500
TaskLoaderTest             :loadInBackground() End.
 

ソースを見てみる。

LoaderManager のソースは Android SDK Manager で落とせる sources に入っているので見てみよう。

LoaderManager#initLoader

中には LoaderManagerImpl という、いかにもなクラスがある。
  • LoaderManagerImpl.initLoader(int, Bundle, LoaderCallbacks<D>)
を見ると LoaderCallbacks は LoaderManagerImpl.LoaderInfom#mCallbacks に覚えられる様子。
これが「Retaining」のログが出る
  • LoaderManagerImpl.LoaderInfo.retain()
で、
  1. void retain() {
  2. if (DEBUG) Log.v(TAG, " Retaining: " + this);
  3. mRetaining = true;
  4. mRetainingStarted = mStarted;
  5. mStarted = false;
  6. mCallbacks = null;
  7. }
  8.  
となっていることから、
onSaveInstanceState() が動作した後にいったんコールバックが消えることが分かる。

onRestoreInstanceState() で mCallbacks を復帰させる情報は覚えないようなので、
コールバックを復帰させるには
  • LoaderManagerImpl.initLoader(int, Bundle, LoaderCallbacks<D>)
で再度コールバックを登録する必要があるようだ。

故に、Activity.onCreate(Bundle)で
・getLoaderManager().initLoader(0, null, this);
のようにするのが必須になる。

LoaderManager#restartLoader

また、
  • LoaderManagerImpl.restartLoader(int, Bundle, LoaderCallbacks<D>)
を見てみると、initLoader()とは違って色々やってる。
見た感じ「指定の LoaderInfo が動作中だったら止めて~」
みたいなことをしているようだ。

故に、実際に処理を始める際は
・getLoaderManager().restartLoader(0, args, this).forceLoad();
のようにすれば LoaderCallbacks#onLoaderReset も動作して自動中断できる。

LoaderManager#loadInBackground

このloadInBackground()では以下のように書いているのだが、
ログを見る限りでは for文 も完全に止まっている。

AsyncTask#doInBackground(Void...)でコールバックしているところを見ると、
AsyncTask の スレッド を sleep() しているんだろうなー。

故に、AsyncTaskLoaderが動作中は常に LoaderManager が存在している状態を維持できる。
これは同時に Activity or Fragment が確実に存在していることを意味する。
まー、だからと言って再起動するとインスタンスが変わるのだから、
当然ながら AsyncTaskLoader に Activity or Fragment のインスタンスは覚えられない。

その他

あと AsyncTaskLoader を見ると、途中でUIにコールバックできてた
  • AsyncTask#publishProgress()
が叩けない感じ。
こういうときに
  • AsyncTaskLoader.LoadTask<D>
をどうにかする方法はないのだろうか。。。

というわけで、UIにProgressを更新すると見せかけて
  • ArrayAdapter.notifyDataSetChanged()
とか叩いたりできない(T_T)

まとめ

流れはこんな感じになる。
  • Activity#onCreate(Bundle) で LoaderManagerImpl.initLoader(int, Bundle, LoaderCallbacks<D>) で登録
  • 登録時、LoaderCallbacks<D>#onCreateLoader(int, Bundle) で動作させる AsyncTaskLoader を返す
  • 使うところで LoaderManagerImpl.restartLoader(int, Bundle, LoaderCallbacks<D>) で forceLoad()
  • 動作が終わったところで LoaderCallbacks#onLoadFinished(Loader<D>, D) が呼ばれる
  • 途中で Activity が終わると LoaderCallbacks#onLoaderReset(Loader<D>) が呼ばれる

つまりこんな感じのソースを書けばいいんだと思う。
Reset が 走った時のキャンセル処理は・・・どうしようかな。

LoaderManagerに登録するActivityあたり
  1. public class AsyncTaskLoaderTestActivity extends ListActivity implements LoaderCallbacks<Bundle> {
  2.  
  3. // ================================================================
  4. // Lifecycle
  5. @Override
  6. protected void onCreate(Bundle savedInstanceState) {
  7. super.onCreate(savedInstanceState);
  8. setTitle(this.getClass().getSimpleName());
  9.  
  10. // XXX : Activity復帰時のコールバック登録のため、必ず呼び出すこと。
  11. getLoaderManager().initLoader(0, null, this);
  12. }
  13.  
  14. @Override
  15. public boolean onOptionsItemSelected(MenuItem item) {
  16. boolean result = true;
  17.  
  18. switch (item.getItemId()) {
  19. case android.R.id.text1:
  20. // XXX : 呼び出しは現在実行中なら自動中断してくれるrestartLoader()を選ぶ。
  21. Bundle args = new Bundle();
  22. getLoaderManager().restartLoader(0, args, this).forceLoad();
  23. break;
  24.  
  25. default:
  26. result = super.onOptionsItemSelected(item);
  27. break;
  28. }
  29. return result;
  30. }
  31.  
  32. // ================================================================
  33. // LoaderCallbacks
  34. public Loader<Bundle> onCreateLoader(int id, Bundle args) {
  35. if (id == 0) {
  36. return new TaskLoaderTest(this.getApplicationContext());
  37. }
  38. return null;
  39. }
  40.  
  41. public void onLoadFinished(Loader<Bundle> loader, Bundle data) {
  42. }
  43.  
  44. public void onLoaderReset(Loader<Bundle> loader) {
  45. }
  46.  
  47. // ================================================================
  48. @Override
  49. public boolean onCreateOptionsMenu(Menu menu) {
  50. menu.add(0, android.R.id.text1, 0, "start!");
  51. return true;
  52. }
  53. }

AsyncTaskLoaderを継承するところ
  1. public class TaskLoaderTest extends AsyncTaskLoader<Bundle> {
  2.  
  3. @Override
  4. public Bundle loadInBackground() {
  5. try {
  6. final long time = 500;
  7. for (int index = 0, size = 10; index < size; index++) {
  8. L.d("sleep time = %d", index * time);
  9. Thread.sleep(time);
  10. }
  11. } catch (InterruptedException e) {
  12. // TODO 自動生成された catch ブロック
  13. e.printStackTrace();
  14. }
  15. return null;
  16. }
  17. }




最終更新:2012年02月13日 01:37