読者です 読者をやめる 読者になる 読者になる

今更ながら入門する AsyncTaskLoader

今更ながらAsyncTaskLoaderを触る機会があったので挙動をメモしておく


基本的な使い方

基本的な実装は

とか

Tumbling Dice — [Android]AsyncTaskLoaderをもっと便利にする(準備編)

挙動メモ的なこと

そもそも Google様自体が CursorLoader というのを作りたいために作った仕組みらしい。*1

  • 基本処理中はメモリ常駐する。
    • Activityが消えても処理中は処理を続行できる仕組み
  • ただLoaderから再作成時に引数なしで作成される *2

    • => この時に引数無しでLoaderが作成されるため、Bundleで引数を渡す作り等にしていると挙動が変になる
    • => だからBundle無視して、下記の☆みたいな記述を書いているコードが出てくる・・
  • loaderCallbacks自体が

    • よくググるとActivityとか使用クラスでベタにimplementsしてるけど
    • Activtyで複数Loaderを使おうとする場合はあり得ない実装
      • G様的にUI無しFragmentを作って処理を分けろという設計推奨なのでしょうか?
class ImageLoaderCallback implements LoaderManager.LoaderCallbacks<Bitmap> {

    @Override
    public Loader<Bitmap> onCreateLoader(int id, Bundle args) {
        return new HttpAsyncImageLoader(getContext(), this.mUrl);//☆
    }

    @Override
    public void onLoadFinished(Loader<Bitmap> loader, Bitmap data) {
        setImageBitmap(data);
    }

    @Override
    public void onLoaderReset(Loader<Bitmap> loader) {

    }
}

使いづらいこと

publishProgress => AsynkTask:onProgress

みたいな仕組みがない。

これググると、

  1. mActivity.runOnUiThread を書く
    • ただ普通createに書けない
    • restartLoaderセット+カスタムセッター+forceLoad でActivityの参照を渡す
  2. loaderCallbacks側等にstatic Handler変数を容易して
    • sendMessageで送れ
  3. loaderCallbacksの
    • createLoader でProgresslg開始
    • loadfinished でProgressDlg終了

でええんんやない?

とくに3つ目がG様推奨パターンで

  • 3番めみたいなインナークラスどうせ書くなら
    • RxJavaで書きなおせばいいよね

というのが最新の流行りになっている。

だからやたらと

  • 通信が重い、複数本API投げてる
  • 通信が全て完了するまでぐるぐるインジケータ回りっぱなし*3
  • 綺麗なデザパタの為に
    • 通信キャッシュを持たず毎回通信してる

なアプリが乱造されている気がする。

複数本APIをまとめる書き方は

1ユーザとしては凄くストレスたまるんだけど、皆さんどうなんでしょう?

結論的なこと

イベントトリガー関数等で

getSupportLoaderManager::restartLoader 

を呼び出すのがベストかなと。

通常のサンプルみたいな使い方で

  • Activity::onCreateで getSupportLoaderManager::initLoader
  • イベントトリガー関数で getSupportLoaderManager::forceLoadを呼び出す

とかやると

  • onLoadFinishedが二度実行されたり
  • 前回の実行状態が残っていたり
    • 変に状態が残っているためにクラッシュしたり

わけわからない挙動をする。

そのための対処方法をググる

上記方法でやりつつ、

  • Loader内部側で呼び出す度に、内部で初期化(stopしてstart)とかしているコードがあったりしますが
    • 実行中か判定して再初期化するなら、restartLoaderでよくない?
    • 再実行の時って前回の状態っていらないでしょう?

例外的な対処

loaderCallbacks に渡せる引数は、標準だとBundleのみなので

PraceableやSerialize形式等の強制で引数を渡しにくいものに限って*4

その場で 下記の形式で実行させるイメージかなと*5

  1. restartLoader
  2. カスタムセッタークラス
  3. forceLoad()

クラッシュ対策

Activityを終了してもLoaderがどうも残る仕様らしい。

実行中でなければ Activity::onStart() で破棄する必要がある*6

あと仕組み的に下記が想定動作らしいので

  • AsyncTaskLoaderを使ったActivityに戻ってきたとき*7
    • onStartLoaderが呼ばれることがAsyncTaskLoaderを使う目的
    • onLoadFinishedでdestroyLoaderしてしまう実装はナンセンス*8

勿論処理中であればそのまま実行させるべきだとは思う。

loader.isRunnnig() は勿論処理中を表す、カスタム関数です

勿論忘れがちですが、処理が終了していたらProgressDialog等のインジケータも消すべきです

  • HogeActivity::onStart
if(loader != null && !loader.isRunnnig()){
    loader =null;
}
  • HogeLoader
public static class HogeLoader extends AsyncTaskLoader<ResultClass> {

    private boolean bRunnning = false;        
    public HogeLoader(Context context) {
        super(context);
        bRunnning = false;
    }

    @Override
    public void deliverResult(ResultClass data) {
        // Loderが処理した結果を返す。(メインスレッドで実行される)
        super.deliverResult(data);
    }

    @Override
    public ResultClass loadInBackground() {
        bRunnning = true;

        //[TODO] Loderが実行するバックグラウンド処理

        bRunnning = false;
        return "abc";
    }
        
    @Override
    protected void onStartLoading() {
        // Loder側の準備ができたタイミングで呼び出される
        // UIスレッドで実装される
        forceLoad();
    }

    // カスタムセッターの実装例
    //  Bundle形式だとMapとかは渡せない。このあとforceLoadを手動で呼ぶ
    private Map<String,CustomClass> param;
    public void setParam(Map<String,CustomClass> args){
          param = args;
    }

    public boolean isRunning(){
            return bRunnning;
    }
}

AsyncTaskLoaderのテスト

後で試す。メモ

*1:ContentProvider内で使う

*2:Home画面/ロック画面からから復帰等のタイミングで

*3:タイムアウトしがちなのでLTE以上の回線が必須とか

*4:loaderCallbacks::onCreateLoaderに引数が渡ってくる

*5:自前で loaderCallbacks::onCreateLoader を実行させる感じ

*6:参照がnullでなければ実行されてしまう

*7:他のActivityから戻ってきて、onStartなどがコールされるとき

*8:其の場合はAsyncTaskを使うべき