Androidの非同期処理でAsyncTaskLoaderを使う

データをネットワークからダウンロードするなどの時間のかかる処理を行う場合、メインスレッドで行うのではなく別スレッドで非同期に処理するというは避けて通れないものです。

 

前回はAndroidの非同期処理はAsyncTaskよりもAsyncTaskLoaderを使うのが良さそうだというのを書きましたが、今回はそのAsyncTaskLoaderの使い方についてまとめてみようと思います。


基本的な流れ

AsyncTaskLoaderクラスをベースとしたLoaderクラスを作成し、その中で非同期で行う処理を実装します。AsyncTaskLoaderはジェネリックなクラスで定義されていて処理結果のオブジェクトを自由に定義できます。

 

Activity (もしくはFragment) 側では、LoaderManager.LoaderCallbacksインターフェースを実装します。このインターフェースを実装する事で非同期処理後の処理などを実装できます。

Activity側の流れ

LoaderManager.initLoaderメソッドを呼び出す

LoaderCallbacksインターフェースの onCreateLoader() が呼び出されます。

 

onCreateLoaderメソッドで非同期処理のインスタンスを作成する

メソッドの戻り値に作成したインスタンスを返します。

LoaderManagerは適切なタイミングで非同期処理を開始し始めます。

 

非同期処理が終了したら onLoadFinishedメソッドが呼び出される

非同期処理終了後の処理を実装します。

LoaderManager.destroyLoaderメソッドを呼び出して非同期処理のインスタンスを破棄します。

Loaderクラスの流れ

onStartLoadingメソッドでforceLoadを実行する

LoaderManagerは非同期処理の開始準備が整うと Loader.onStartLoading() が呼び出しされます。

inStartLoadingをオーバーライドして Loader.forceLoad() を実行すると処理を開始します。

 

別スレッドでloadInBackgroundメソッドが動き始める

loadInBackgroundをオーバーライドして処理を実装します。


Activityのライフサイクルに対応する

大まかな流れは上記の通りですが、それだけでは足りません。

詳しくは「Androidの非同期処理はAsyncTaskよりもAsyncTaskLoaderで」をみて頂ければと思いますが、Activityは画面が回転する時など再構築されてしまいます。

 

 

非同期処理を開始したActivityは破棄される可能性があり、再構築後のActivityが非同期処理の終了通知を受け取れるようにする必要があります。

Activityサンプル

public class TestActivity extends AppCompatActivity implements
                                        LoaderManager.LoaderCallbacks<TestTaskLoader.Result> {
    //Loaderを識別する為のID
    private static final int LOADERID_TEST = 0;



    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test);

        LoaderManager lm = getSupportLoaderManager();

        //回転などでActivityが再作成された時の為の処理
        if (null != lm.getLoader(LOADERID_TEST)) {
            //LoaderManagerはActivityが再作成されても状態を維持している
            //LoaderManagerが指定IDのLoaderを持っているなら再作成前にLoaderが実行を開始しているという事なので
            //再作成後のActivityがonLoadFinishedを受け取る為にinitLoaderをしてLoaderとの関連付けを行う
            lm.initLoader(LOADERID_TEST, null, this);
        }
    }

    public void onButtonClick(View v) {
        getSupportLoaderManager().initLoader(LOADERID_TEST, null, this);
    }

    @Override
    public Loader<TestTaskLoader.Result> onCreateLoader(int id, Bundle args) {
        return new TestTaskLoader(getApplication());
    }

    @Override
    public void onLoaderReset(Loader<TestTaskLoader.Result> loader) {
    }

    @Override
    public void onLoadFinished(Loader<TestTaskLoader.Result> loader, TestTaskLoader.Result result) {

        getSupportLoaderManager().destroyLoader(LOADERID_TEST);
    }
}

この例ではボタンが押された時に非同期処理を開始するという想定です。

その為、onButtonClickメソッドで initLoader を呼び出して非同期処理を開始していますが、Activityの再構築に備えて onCreateメソッドで LoaderManager が Loader を持っているか(非同期処理が実行中か)を確認し、持っていれば initLoader を呼び出しています。

AsyncTaskLoaderサンプル

public class TestTaskLoader extends AsyncTaskLoader<TestTaskLoader.Result> {
    public class Result {
        public int    status;
        public String message;
    }

    private Result  mResult;
    private boolean mIsStarted;



    public TestTaskLoader(Context context) {
        super(context);
        mResult    = null;
        mIsStarted = false;
    }

    @Override
    public TestTaskLoader.Result loadInBackground() {
        TestTaskLoader.Result result = new TestTaskLoader.Result();
        
        //
        // ここで非同期の処理をする
        //
        
        return result;
    }

    @Override
    public void deliverResult(Result result) {
        // 非同期処理の結果を保存しておく
        mResult = result;
        super.deliverResult(result);
    }

    @Override
    protected void onStartLoading() {
        // 呼び出し元のActivityが回転などで再作成されるとinitLoaderを再度呼ばなければならない
        // initLoaderがよばれるとココに来るが再度forceLoadしてしまうと
        // 実行中のloadInBackgroundは破棄されもう一度loadInBackgroundが開始されてしまう
        // 実行中のloadInBackgroundが無い時だけ実行を開始し終了している時は直ちに結果を返す
        if (null != mResult) {
            deliverResult(mResult);
            return;
        }
        if ((! mIsStarted) || takeContentChanged()) {
            forceLoad();
        }
    }

    @Override
    protected void onForceLoad() {
        super.onForceLoad();
        mIsStarted = true;
    }
}