Scala で Android アプリ開発(AsyncTask 編)

ScalaでAndroidアプリ作成時、AsyncTaskの可変長引数メソッドが使えないことへの対策とサンプル ― Gist を丸パクリ。

Scala は、可変長引数を持つメソッドを上書きできないので、可変長引数を上書き済みの /path/to/hello-world/src/main/java/com/github/cooldaemon/HelloWorld/AsyncTask.java を作成する。

package com.github.cooldaemon.HelloWorld;

public abstract class AsyncTask<Params, Progress, Result> extends android.os.AsyncTask<Params, Progress, Result> {
   @Override
   protected Result doInBackground(Params... params) {
      return doInBackground(params.length > 0 ? params[0] : null);
   }

   abstract protected Result doInBackground(Params param);

   @Override
   protected void onProgressUpdate(Progress... values) {
      onProgressUpdate(values.length > 0 ? values[0] : null);
   }

   protected void onProgressUpdate(Progress value) {}

   @SuppressWarnings({"unchecked"})
   protected final void publishProgress(Progress value) {
      super.publishProgress(value);
   }
}

用意した AsyncTask を継承して、別スレッドで処理したいタスクを記述する。

//可変長引数を使えない分は、case class で対応する
case class FooParam(foo: String, bar: String)

//  AsyncTask から Activity を操作する(doInBackground は除く)ために Activity のインスタンスを渡しておく
class FooTask(val activity: FooActivity) extends AsyncTask[FooParam, Int, Either[Throwable, Unit]] {
  override protected def onPreExecute() {
    // Activity に変化を加える。例えばダイアログの表示など
  }

  // doInBackground だけ別スレッドで動作する
  // doInBackground 内で publishProgress を使うと UI のスレッドに Int の値を送れる
  // 送った Int の値は onProgressUpdate で受け取れる
  override protected def doInBackground(param: FooParam): Either[Throwable, Unit] = {
    for {
      _     <- ham(param).right
      chick <- egg(param).right
      _     <- spam(param, chick).right
    } yield ()
  }

  override protected def onProgressUpdate(progress: Int) {
    // プログレスの更新
  }

  // AsyncTask の cancel メソッドを使用されると onCancelled が呼ばれる
  override protected def onCancelled() {
    // doInBackground の停止を促す。例えば doInBackground で参照しているフラグを onCancelled で更新するなど
  }

  override protected def onPostExecute(result: Either[HttpClientErrorResult, Unit]) {
    // Activity に変化を加える。例えばダイアログを消すなど
  }
}

このコードは、画面の回転に対応していない。
画面が回転すると Activity のインスタンスは置き換えられるが AsyncTask は残り続けるので AsyncTask が保持する Activity のインスタンスを置き換える必要がある。
また、画面の回転と ProgressDialog を組み合わせる場合、画面回転後に ProgressDialog を再表示する必要があり、onCreateDialog に頼る事になる。
ProgressDialog を再表示する場合、以前の ProgressDialog 進行状況を引き継いだり、キャンセル処理を正常に動作させる必要があるので、なるべく画面が回転しないように固定しておいた方が良い。
もし、どうしても AsyncTask + ProgressDialog + 画面回転に対応したいのであれば、Activity から操作できる AsyncTask と ProgressDialog を管理する object を用意する。
この辺りは、ダウンローダ編で詳しく解説する。