2012年8月5日日曜日

AndroidのAsyncTaskについて

今回は、AndroidのAsyncTaskについて。
知らない人のためにまずは、AsyncTaskの説明を。

Androidはメインスレッド内でDB接続等の時間のかかる処理を行うとその間、
画面はフリーズしてしまいます。
ボタンクリック時にDBへの登録処理を行い、
その間画面にはプログレスバー(くるくるするやつ)を表示しておきたいとします。
この時、メインスレッド(GUIスレッド)でプログレスバーを表示、そのまま同じスレッドで
登録処理を行うと、画面に表示されているプログレスバーは止まってしまいます。
これを解消するには、登録処理等のバックグラウンドで行いたい処理はメインスレッドとは
別のスレッドで行う必要があります。
この時、気をつけないと行けないのがバックグラウンドで動かしているスレッドから
UI操作を行うとエラーが発生してしまいます。
上記のような動作を簡単に実装できるのがAsyncTaskです。
以下に簡単なサンプルを。
public class SampleActivity extends Activity {

    /* ダイアログ(くるくるするやつ) */
    private ProgressDialog dialog;

    @Override
    public void onCreate(final Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.sample);

        final Button submitButton = (Button)findViewById(R.id.submitButton);
        submitButton.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(final View v) {
                // くるくるを表示
                dialog = new ProgressDialog(SampleActivity.this);
                dialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
                dialog.setMessage("登録中");
                dialog.show();

                // AsyncTaskを開始
                final SampleTask task = new SampleTask();
                task.execute();
            }
        });
    }

    /**
     * バックグラウンド処理を行うクラス
     */
    class SampleTask extends AsyncTask&ltVoid, Void, Void&gt {

        /**
         * executeが実行された後に実行される。
         */
        @Override
        protected void doInBackground(Void... params) {
            // DB登録等のUIに関与しない処理
        }

        /**
         * doInBackgroundの後に実行される。
         * このメソッド内ではUIを操作できる。
         */
        @Override
        public void onPostExecute(Void... params) {
            // くるくるを消去
            dialog.dismess();
        }
    }
}

こんな感じになります。
ただAsyncTaskを内部クラスにするのがいやっていうか、だいたいここで行われる処理っていうのは
ビシネスロジックになることが多いので、Activityとは分離しておきたいので
別クラスにしたいところです。
ただ、そうするとAsyncTaskからUIを操作するために
AsyncTaskにActivityの参照を渡す必要が出てきてしまいます。
まあ、DB等にアクセスするためにはActivityが必要だったりするんですけど
画面個別のUI処理が入ってくる場合はどうしても、
画面固有のActivity(XxxxActivityみたいな)の型の参照を渡さなくてはいけなくて嫌な感じです。
できるだけ、Activityとして渡すに留めたい。そこで以下のような感じはどうでしょう?
まずは、以下のようなインターフェースを用意。
public interface Callback {

    /** 成功時のレスポンスコード */
    public static final int SUCCESS = 0;

    /** 失敗時のレスポンスコード */
    public static final int ERROR = -1;

    /**
     * コールバックメソッド
     */
    public void callback(final int responseCode, final int requestCode,
                                                    final Map<String, Object> resultMap);

}

次にActivity。
public class SampleActivity extends BaseNoMapActivity implements Callback {
   /* ダイアログ(くるくるするやつ) */
    private ProgressDialog dialog;

    private static final String REQUEST_CODE = "sample";

    @Override
    public void onCreate(final Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.sample);

        final Button submitButton = (Button)findViewById(R.id.submitButton);
        submitButton.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(final View v) {
                // くるくるを表示
                dialog = new ProgressDialog(SampleActivity.this);
                dialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
                dialog.setMessage("登録中");
                dialog.show();

                // AsyncTaskを開始
                final SampleTask task = new SampleTask(SampleActivity.this, REQUEST_CODE);
                task.execute();
            }
        });
    }

    /**
     * コールバックメソッド。
     * バックグラウンド処理終了後に呼び出される。
     */
    public void callback(final int responseCode, final int requestCode,
                                                    final Map<String, Object> resultMap) {

        if (REQUEST_CODE.equals(requestCode)) {
            dialog.dismess();
        }
    }
}

そして、AsyncTask。
    class SampleTask extends AsyncTask&ltVoid, Void, Void&gt {

        /** コールバック */
        private Callback callback;

        /** リクエストコード */
        private String requestCode;

        /** コールバックの引数に渡す結果データ */
        private Map<String, Object> resultMap;

        /**
         * コンストラクタ.
         */
        public SampleTask(final Callback callback, final String requestCode) {
           this.callback = callback;
           this.requestCode = requestCode;

            resultMap = new HashMap<String, Object>();
        }

        /**
         * executeが実行された後に実行される。
         */
        @Override
        protected void doInBackground(Void... params) {
            // DB登録等のUIに関与しない処理
            // resuletMapにActivityに渡した値を詰めたり
        }

        /**
         * コールバックメソッドを実行する。
         */
        @Override
        public void onPostExecute(Void... params) {
            callback.callback(Callback.OK, requestCode, resultMap);
        }
    }
こんな感じです。
まずは、Callbackインターフェースですが、これでAsyncTackからActivityに処理を戻せるようにしています。
callbackメソッドによりActivityは「どのTaskを実行したのか」「成功したのか?」「結果データは?」と
といったものを受け取れるので、それに応じたUI処理を行えば良くなります。
考え方的にには、ActivityのstartActivityForResultとonActivityResultの考え方と同じです。
これなら、ある程度役割を分割できるのではないでしょうか。

1 件のコメント: