ブロッキング API

Thing-IF SDK ではブロッキング API だけを提供している点にご注意ください。API は JDeferred などの非同期処理のフレームワークや Android SDK の非同期処理向け機能 を通して呼び出すことをおすすめします。

ユーザー登録や Thing へのコマンドの送信など、Android 端末と Thing Interaction Framework が通信するメソッドでは、処理完了までに時間を要します。一般的に、このようなメソッドに対しては、ブロッキング API とノンブロッキング API という 2 つの異なる実現方式により、問題を解決します。それぞれの違いについては、ブロッキング API vs. ノンブロッキング API をご覧ください。

各 SDK でのサポート状況は以下のとおりです。

  • Thing-IF SDK で提供する機能はブロッキング API だけを提供しています。

  • Kii Cloud SDK で提供する機能は、ブロッキング API とノンブロッキング API の両方を提供しています。

ブロッキング API をメインスレッドから呼び出すと、ユーザーインターフェイスが停止する問題を起こします。以下に挙げるような非同期処理の技術を使うか、自分で作成した作業スレッドから呼び出すようにしてください。

JDeferred の利用

JDeferred を使うと、Android 上で Promise を使った非同期処理を実現できます。これにより、メインスレッドが停止する問題と、コールバックの多用によりプログラムの見通しが悪くなる問題の両方を同時に改善できます。

ここでは、JDeferred の利用方法と、本ガイドに記載されているサンプルコードでの利用方法を簡単に説明します。

JDeferred は Thing-IF SDK の サンプル でも使用しているため、理解のための手がかりとなる情報を簡単に示しています。JDeferred のプロジェクトへの組み込み方や、Promise を使った設計方法などの詳細は、一般の技術情報を参照してください。また、Thing-IF SDK での非同期処理の実現には、必ずしも JDeferred を使う必要はありません。

API の例

ここでは、JDeferred の説明のため、以下のような簡易的な API を定義します。いずれも、実行には 1 秒程度の時間がかかるブロッキング API です。これらは Thing-IF SDK の API に相当します。

class Result1 {
  public String value1;
}

class Result2 {
  public String value2;
}

class API {
  public static Result1 api1(String a) {
    sleep(1000);
    Result1 result = new Result1();
    result.value1 = a;
    return result;
  }

  public static Result2 api2(String a) {
      sleep(1000);
      Result2 result = new Result2();
      result.value2 = a;
      return result;
  }

  public static void api3(String a) {
    sleep(1000);
    System.out.println(a);
  }

  private static void sleep(int timeInMs) {
    try {
      Thread.sleep(timeInMs);
    } catch (InterruptedException e) {
    }
  }
}

最終的に、呼び出し側から以下のような処理を行う想定であるものとします。これらはブロッキング API の呼び出しですが、最終的に同じ処理を、Promise を使って連続的に非同実行するのが目標です。

API api = new API();
Result1 result1 = api.api1("abcd");
Result2 result2 = api.api2(result1.value1);
api.api3(result2.value2);

Promise の定義

API.api1()API.api3() メソッドの呼び出しをそれぞれ JDeferred を使って記述すると、以下のようになります。doInBackgroundSafe() メソッドに非同期で実行される処理を記述しています。

private AndroidDeferredManager mAdm;

private Promise<Result1, Throwable, Void> executeApi1(final String input) {
  return mAdm.when(new DeferredAsyncTask<Void, Void, Result1>() {
    @Override
    protected Result1 doInBackgroundSafe(final Void... params) throws Exception {
      return API.api1(input);
    }
  });
}

private Promise<Result2, Throwable, Void> executeApi2(final String input) {
  return mAdm.when(new DeferredAsyncTask<Void, Void, Result2>() {
    @Override
    protected Result2 doInBackgroundSafe(final Void... params) throws Exception {
      return API.api2(input);
    }
  });
}

private Promise<Void, Throwable, Void> executeApi3(final String input) {
  return mAdm.when(new DeferredAsyncTask<Void, Void, Void>() {
    @Override
    protected Void doInBackgroundSafe(final Void... params) throws Exception {
      API.api3(input);
      return null;
    }
  });
}

AndroidDeferredManager は JDeferred のクラスです。ここではクラスのフィールドで保持しています(初期化は後述します)。

executeApi1()executeApi3() のメソッドを定義し、DeferredAsyncTask クラスの doInBackgroundSafe() メソッド内で API の呼び出しを行っています。各メソッドで使用している型は複雑に見えますが、以下のように決まります。

  • メソッドの戻り値の型:Promise<Result1, Throwable, Void> など

    • 1 つ目の Result1 は非同期に実行される処理からの戻り値で、doInBackgroundSafe() メソッドの戻り値と同じ型になります。
    • 2 つ目は常に Throwable で、変更できません。
    • 3 つ目は進捗処理で利用する型です。ここでは進捗処理を利用しないため Void ですが、数値で表現したい場合は Integer なども利用できます。
  • メソッドの引数:final String input など

    メソッドの引数は実現したい機能に必要な引数です。ここでは、API に渡す値を引数に設定しています。

  • DeferredAsyncTask の型:DeferredAsyncTask<Void, Void, Resul1> など

    • 1 つ目は常に Void で、変更できません。
    • 2 つ目は進捗処理で利用する型です。Promise の型に合わせます。
    • 3 つ目の Result1 は非同期に実行される処理からの戻り値で、doInBackgroundSafe() メソッドの戻り値と同じ型になります。
  • doInBackgroundSafe() メソッドの戻り値:Result1 など

    非同期に実行される処理の戻り値の型です。モバイルアプリで自由に設定できます。

  • doInBackgroundSafe() メソッドの引数:final Void... params

    常に final Void... params を指定します。

  • メソッドの throws:throws Exception

    基本的にどのような例外型でも利用できます。

Promise を使ったメソッドの連続実行

これらの Promise を使った連続実行は次のようにして実現できます。

mAdm = new AndroidDeferredManager();
mAdm.when(executeApi1("abcd")
).then(new DonePipe<Result1, Result2, Throwable, Void>() {
  @Override
  public Promise<Result2, Throwable, Void> pipeDone(Result1 result) {
    return executeApi2(result.value1);
  }
}).then(new DoneCallback<Result2>() {
  @Override
  public void onDone(Result2 result) {
    executeApi3(result.value2);
  }
}).fail(new FailCallback<Throwable>() {
  @Override
  public void onFail(final Throwable tr) {
    tr.printStackTrace();
  }
});

when() メソッドには初めに実行したい Promise を記述します。Promise が成功したときは then() メソッドが、失敗したときは直近の fail() メソッドが実行されます。

直前の Promise から処理を引き継ぎ、さらに次の Promise に中継したい場合は DonePipe を、成功時の処理を記述したい場合は DoneCallback を使用します。

メソッドで例外が発生した場合は、直近の fail() メソッドが実行されます。上記のようにまとめて例外処理を記述することもできます。

Thing-IF SDK の変換例

Thing-IF SDK のガイドでは、以下のようにブロッキング API を trycatch で囲った形式でサンプルコードが提示されています。

List<Action> actions = new ArrayList<Action>();

TurnPower action1 = new TurnPower();
action1.power = true;
actions.add(action1);

try {
  Command command = api.postNewCommand("AirConditioner-Demo", 1, actions);
} catch (ThingIFException e) {
  // Handle the error.
}

これを Promise で記述すると、以下のように実装できます。

Promise<Command, Throwable, Void> getState(final boolean power) {
  return adm.when(new DeferredAsyncTask<Void, Void, Command>() {
    @Override
    protected Command doInBackgroundSafe(Void... voids) throws Exception {
      List<Action> actions = new ArrayList<Action>();

      TurnPower action1 = new TurnPower();
      action1.power = power;
      actions.add(action1);

      return api.postNewCommand("AirConditioner-Demo", 1, actions);
    }
  });
}

AsyncTask の利用

JDeferred を使用せず、Android SDK の機能を直接使って実装することもできます。たとえば、AsyncTask クラスによって独自の作業スレッドを生成し、その内部でブロッキング API を呼び出します。以下にサンプルコードを示します。

void test() {
  final List<Action> actions = new ArrayList<Action>();
  ...
  new AsyncTask<Void, Void, Void>() {
    final Handler handler = new Handler();
      @Override
      protected Void doInBackground(Void... params) {
        try {
          Command command = api.postNewCommand("AirConditioner-Demo", 1, actions);
        } catch (Exception e) {
          final Exception exception = e;
          handler.post(new Runnable() {
            @Override
            public void run() {
              // Handle the error in the main thread.
            }
          });
        }
        return null;
      }
    }.execute();
  }
}

このサンプルコードでは、作業スレッドから api.postNewCommand() メソッドを実行し、エラーが発生した場合はさらにメインスレッドに切り替えて exception の内容に応じてユーザーインターフェイスの操作を行います。

サンプルコードでの注意点は以下のとおりです。JDeferred と同様に、多くの行が非同期処理を行うためのオーバーヘッドです。

  • final List<Action>… はメインスレッドで実行される処理です。作業スレッドで実行される doInBackground() メソッドに actions をパラメーターとして渡すため、Java の言語仕様に基づいて final 宣言を行っています。
  • doInBackground() メソッドに、作業スレッドで行う処理を記述します。api.postNewCommand() メソッドは作業スレッドで実行したい処理の本体です。
  • run() メソッドに、例外発生時にメインスレッドで行う処理を記述します。ユーザーインターフェイスを操作するには、メインスレッドから Android の API を呼び出す必要があります。作業スレッドからメインスレッドに切り替えるため、Handler の post() メソッドを使用します。exceptionrun() メソッドに渡すため、final 宣言を行っています。

上に記述した 3 行のみがアプリケーションの処理で、これ以外はスレッドを切り替えるための定型の処理です。サンプルコードの全体を貼り付けた後、上記 3 行の処理のみを書き換えれば、目的のプログラムでの非同期処理を実現できます。