目次
大事なのは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にする。
動作ログを見てみる。
さらっと検証したソースについて説明。
オプションメニューから「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()
で、
void retain() {
if (DEBUG) Log.v(TAG, " Retaining: " + this);
mRetaining = true;
mRetainingStarted = mStarted;
mStarted = false;
mCallbacks = null;
}
となっていることから、
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あたり
public class AsyncTaskLoaderTestActivity extends ListActivity implements LoaderCallbacks<Bundle> {
// ================================================================
// Lifecycle
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setTitle(this.getClass().getSimpleName());
// XXX : Activity復帰時のコールバック登録のため、必ず呼び出すこと。
getLoaderManager().initLoader(0, null, this);
}
@Override
public boolean onOptionsItemSelected
(MenuItem item
) { boolean result = true;
switch (item.getItemId()) {
case android.R.id.text1:
// XXX : 呼び出しは現在実行中なら自動中断してくれるrestartLoader()を選ぶ。
Bundle args = new Bundle();
getLoaderManager().restartLoader(0, args, this).forceLoad();
break;
default:
result = super.onOptionsItemSelected(item);
break;
}
return result;
}
// ================================================================
// LoaderCallbacks
public Loader<Bundle> onCreateLoader(int id, Bundle args) {
if (id == 0) {
return new TaskLoaderTest(this.getApplicationContext());
}
return null;
}
public void onLoadFinished(Loader<Bundle> loader, Bundle data) {
}
public void onLoaderReset(Loader<Bundle> loader) {
}
// ================================================================
@Override
public boolean onCreateOptionsMenu
(Menu menu
) { menu.add(0, android.R.id.text1, 0, "start!");
return true;
}
}
AsyncTaskLoaderを継承するところ
public class TaskLoaderTest extends AsyncTaskLoader<Bundle> {
@Override
public Bundle loadInBackground() {
try {
final long time = 500;
for (int index = 0, size = 10; index < size; index++) {
L.d("sleep time = %d", index * time);
}
// TODO 自動生成された catch ブロック
e.printStackTrace();
}
return null;
}
}
最終更新:2012年02月13日 01:37