刺身の上にたんぽぽ乗せる日記

プログラミングしたり、自販機の下に落ちてる小銭を集めたりしてます

Reflectionで非同期呼び出し

androidではUIコンポーネントの状態の変更をUIスレッドでしかできないため、通信処理が多い2chビューワだと、非同期呼び出しがちらほら出て来る。以下のような感じ。


Handler handler = new Handler();

void beginDownloading() {
 class UpdateSomething implements Runnable {
  private final SomeObject arg;
  public UpdateSomething(SomeObject arg) {
   this.arg = arg;
  }
  public void run() {
   updateUI(this.arg);
  }
 }
 (new Thread() {
  public void run() {
   SomeObject result = downloadSomething();
   handler.post(new UpdateSomething(result));
  }
 }).start();
}

SomeObject resultをインスタンス変数として保持すればupdateUIの呼び出しを無名クラスからもできるけど、メソッド間での引数のためにメンバを増やすのはあまりにもエレガンスに欠ける。そこで少し違うバージョンを書いてみた。確か下みたいな感じ。


Handler handler = new Handler();

void beginDownloading() {
 (new Thread() {
  public void run() {
   SomeObject result = downloadSomething();
   handler.post(new SingleArgumentRunnable(result) {
void run(SomeObject arg) {
updateUI(arg);
}
});
  }
 }).start();
}

若干マシになったものの、やっぱりまだうざい。で、可変長引数とリフレクション使うことにした。


void beginDownloading() {
AsyncMethodCreator.getMethod(this, "doDownlodSomething").runOnNewThread();
}
void doDownloadSomething() {
SomeObject result = downloadSomething();
AsyncMethodCreator.getMethod(this, "updateUI", SomeObject).runOnUIThread(result);
}

AsyncMethodCreator.getMethodでインスタンスと呼び出す関数を保持している別クラスのオブジェクトを返す。その別クラスのrunOnNewThread/runOnUIThreadでそれぞれ対応したスレッドで指定したメソッドと引数を実行する。
大分すっきりして、可読性が上がって来たけど、Stringで関数名を参照するところにエレガンスがない。this.updateUI.nameとかで関数名がとれたり、this.updateUIでMethodのインスタンスがとれれば苦労しないわけですが、冷静に考えると、両方とも構文的にサポートされていないっぽいことに気付いた。うんこ。

で、Overkillのような気もするけど、InvocationHandlerを使って実装することにした。


interface AsyncMethods {
public void doDownloadSomething();
public void updateUI(SomeObject arg);
}

class MyClass implements AsyncMethods {
private final AsyncInvoker async;

public MyClass() {
this.async = new AsyncInvoker(AsyncMethods.class, this);
}
public void beginDownloading() {
this.async.thread.doDownloadSomething();
}
public void doDownloadSomething() {
SomeObject result = downloadSomething();
this.async.ui.updateUI(result);
}
}

確かこんな感じだった気がする。AsyncInvoker.asyncとAsyncInvoker.uiはAsyncMethodsを実装したProxyオブジェクトで、呼び出した関数をInvocationHandlerで拾って、対応した別スレッドで実行する感じの実装。
無駄にリソース喰う気もするけど、若干奇麗な作りにはなると思う。
別にランタイムにできなくてもいいから、クラス名.nameとか関数名.nameとかをコンパイラがサポートしてくれりゃあこんなこといらないんだけどなぁ。