「AsyncTaskLoader 使い方」でググると誰一人正しい挙動をしていなかったので、AsyncTaskLoaderを利用したOneshotな非同期処理の実装方法について改めてまとめます。
AsyncTaskLoaderを実装する上で配慮すること
Activityが再起動しても、loadInBackground
が再度呼ばれないこと。
よくあるサンプルなどを見る限り、loadInBackground
実行中にinitLoaderをすると再度forceLoad
されます。
forceLoad
を呼ぶと既に動いているloadInBackgroud
実行スレッドはキャンセルされ、再度スレッドが作成されloadInBackground
が実行されます。この挙動は間違っています。
公式サンプルではコンテンツの変更を考慮した実装になっているため、求めている挙動とは違った実装になっています。
AsyncTaskLoaderのabstractなサブクラスを用意する
AsyncTaskLoader
の面倒な挙動を吸収するために、abstractなサブクラスを作成します。
これでサブクラスはloadInBackground
を実装するだけで良くなります。
mIsStarted
フラグでforceLoad
が複数回呼ばれることを防ぎます。
public abstract class AkkumaAsyncTaskLoader<D> extends AsyncTaskLoader<D> {
private D mResult;
private boolean mIsStarted = false;
public AkkumaAsyncTaskLoader(Context context) {
super(context);
}
@Override
protected void onStartLoading() {
if (mResult != null) {
deliverResult(mResult);
return;
}
if (!mIsStarted || takeContentChanged()) {
forceLoad();
}
}
@Override
protected void onForceLoad() {
super.onForceLoad();
mIsStarted = true;
}
@Override
public void deliverResult(D data) {
mResult = data;
super.deliverResult(data);
}
}
Loader実装
面倒な処理はスーパークラスに書いたので、
行いたい非同期処理をloadInBackground
に実装するだけで良いです。
public class SampleLoader extends AkkumaAsyncTaskLoader<String> {
private String mExtraParam;
public SampleLoader(Context context, String extraParam) {
super(context);
mExtraParam = extraParam;
}
@Override
public String loadInBackground() {
Log.d("SampleLoader", "loadInBackground");
try {
Thread.sleep(3000);
} catch (InterruptedException ignored) {}
return "task complete: " + mExtraParam;
}
}
コールバックの受け取り
initLoader
は処理を始めたい時 または 既に走っている処理にLoaderCallbacksを再度設定したいときに呼びます。LoaderManagerではonDestroy時にLoaderCallbacksは破棄されるため、Activity再起動時のLoaderCallbacksを再度設定も必要です。
よってonCreate
ではデータを保持していない場合にinitLoader
するようにします。初期起動時 と 処理中にActivity再起動した場合のLoaderCallbacks再設定 両方をカバーできます。
Fragment
を利用している場合は、onActivityCreate
で同様の実装を書きます。
public class MainActivity extends FragmentActivity {
private static final int LOADER_ID = 1;
private static final String SAVE_INSTANCE_TASK_RESULT = "info.akkuma.loader.MainActivity.SAVE_INSTANCE_TASK_RESULT";
private static final String ARG_EXTRA_PARAM = "ARG_EXTRA_PARAM";
private String mTaskResult;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (savedInstanceState != null) {
mTaskResult = savedInstanceState.getString(SAVE_INSTANCE_TASK_RESULT);
}
if (mTaskResult != null) {
TextView textView = (TextView) findViewById(R.id.text_view);
textView.setText(mTaskResult);
}
// Activityが結果を持っていない場合ロードを行う
//
// 結果を保持しない性質のリクエストの場合
// getSupportLoaderManager().getLoader(LOADER_ID) != null という条件にもできる
if (mTaskResult == null) {
Bundle args = new Bundle();
args.putString(ARG_EXTRA_PARAM, "サンプルパラメータ");
getSupportLoaderManager().initLoader(LOADER_ID, args, mCallback);
}
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putString(SAVE_INSTANCE_TASK_RESULT, mTaskResult);
}
private final LoaderManager.LoaderCallbacks<String> mCallback = new LoaderManager.LoaderCallbacks<String>() {
@Override
public Loader<String> onCreateLoader(int id, Bundle args) {
String extraParam = args.getString(ARG_EXTRA_PARAM);
return new SampleLoader(MainActivity.this, extraParam);
}
@Override
public void onLoadFinished(Loader<String> loader, String data) {
getSupportLoaderManager().destroyLoader(loader.getId());
// 結果は data に出てくる
mTaskResult = data;
TextView textView = (TextView) findViewById(R.id.text_view);
textView.setText(mTaskResult);
}
@Override
public void onLoaderReset(Loader<String> loader) {
// do nothing
}
};
}