正しいAsyncTaskLoaderの使い方

「AsyncTaskLoader 使い方」でググると**誰一人正しい挙動をしていなかった**ので、AsyncTaskLoaderを利用したOneshotな非同期処理の実装方法について改めてまとめます。

# AsyncTaskLoaderを実装する上で配慮すること

**Activityが再起動**しても、**`loadInBackground`が再度呼ばれないこと**。

よくあるサンプルなどを見る限り、`loadInBackground`実行中にinitLoaderをすると再度`forceLoad`されます。
`forceLoad`を呼ぶと既に動いている`loadInBackgroud`実行スレッドはキャンセルされ、再度スレッドが作成され`loadInBackground`が実行されます。この挙動は間違っています。

公式サンプルではコンテンツの変更を考慮した実装になっているため、求めている挙動とは違った実装になっています。

# AsyncTaskLoaderのabstractなサブクラスを用意する

`AsyncTaskLoader`の面倒な挙動を吸収するために、abstractなサブクラスを作成します。
これでサブクラスは`loadInBackground`を実装するだけで良くなります。

`mIsStarted`フラグで`forceLoad`が複数回呼ばれることを防ぎます。

```java
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`に実装するだけで良いです。

```java
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`で同様の実装を書きます。

```java
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
        }
    };
}
```