Android: AsyncTaskの便利な使い方のコツ

Androidで開発しててAPI通信しようとしたらまず思いつくのは、AsyncTaskですよね。

自分でスレッドを起こしてなんやかんやするよりはるかに便利です。

APIからニュース記事を取ってきて画面に表示するイメージで使い方を簡単に説明します。

コツ: AsyncTaskはモデルから使うこと
--------------------------

AsyncTaskはモデル層の中で使います。今回は説明を簡単にするため、シングルトンにしてあります。

refreshが呼ばれたら、AsyncTaskを起動してデータ通信をします。

アンチパターンとしては、ActivityやFragmentの中で直接AsyncTaskを呼び出してしまうというのがあります。するとActivityやFragmentの状態を管理しなければいけないためにフラグが増えていきます。これは便利ではありません。

```
import android.os.AsyncTask;

import java.util.Observable;

public class NewsModel extends Observable {
    private static NewsModel ourInstance = new NewsModel();

    public static NewsModel getInstance() {
        return ourInstance;
    }

    private boolean busy;
    private AsyncTask<Void, Void, NewsEntity> task;
    private NewsEntity entity;

    public enum Event {
        ENTITY_CHANGED, BUSY_STATE_CHANGED,
    }

    private NewsModel() {
    }

    public void refresh() {
        if (isBusy()) return;

        setBusy(true);
        task = new MyTask();
        task.execute();
    }

    public boolean isBusy() {
        return busy;
    }

    public void setBusy(boolean busy) {
        if (this.busy == busy) return;

        this.busy = busy;

        setChanged();
        notifyObservers(Event.BUSY_STATE_CHANGED);
    }

    public NewsEntity getEntity() {
        return entity;
    }

    public void setEntity(NewsEntity entity) {
        this.entity = entity;

        setChanged();
        notifyObservers(Event.ENTITY_CHANGED);
    }

    private class MyTask extends AsyncTask<Void, Void, NewsEntity> {

        @Override
        protected NewsEntity doInBackground(Void... params) {
            // TODO ここで通信処理を行う
            return new NewsEntity();
        }

        @Override
        protected void onPostExecute(NewsEntity newsEntity) {
            super.onPostExecute(newsEntity);

            setBusy(false);
            setEntity(newsEntity);
        }
    }
}
```

コツ：モデルからの通知で画面を更新すること
------

ニュース記事を表示するActivityかFragmentを作成します。Fragmentの方が何かと便利ですね。

通信完了などでモデルの状態が変わったら、通知を受け取って画面をリフレッシュします。この例では簡単のためObserverを使ってますが、EventBusなどのライブラリーを使うとさらに便利です。

アンチパターンとしては、AsyncTaskにActivityやFragmentの中で定義したコールバックを渡しているのを時々見かけます。AsyncTaskが終わった時にActivityやFragmentがもういなくなっていたらどうするのでしょうか？これは便利ではない使い方です。

```
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import java.util.Observable;
import java.util.Observer;

public class NewsFragment extends Fragment {
    private NewsModel model = NewsModel.getInstance();
    
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        // ここは適切に実装してください。
        return inflater.inflate(R.layout.activity_main, container, false);
    }

    @Override
    public void onStart() {
        super.onStart();
        
        resetContent();
        model.refresh();
        model.addObserver(observer);
    }

    @Override
    public void onStop() {
        model.deleteObserver(observer);
        super.onStop();
    }
    
    private Observer observer = new Observer() {
        @Override
        public void update(Observable observable, Object data) {
            if ( !(data instanceof NewsModel.Event)) return;
            
            switch ((NewsModel.Event)data) {
                case BUSY_STATE_CHANGED:
                    resetProgressBar();
                    break;
                
                case ENTITY_CHANGED:
                    resetContent();
                    break;
            }
        }
    };

    private void resetContent() {
        // TODO: コンテンツの差し替え
    }

    private void resetProgressBar() {
        // TODO: プログレスバーの表示・非表示の切り替え
    }
}
```


まとめ
-----

AsyncTaskはシンプルで便利です。

ただし、ActivityやFragmentから直接呼び出すべきではないです。

* コツ: AsyncTaskはモデルから使うこと
* コツ: モデルからの通知で画面を更新すること

この二つを守っていれば、とても簡単に使えます。