実装技術の紹介

ここでは、Android 版 Hello Thing-IF モバイルアプリの実装に使用しているライブラリーについて簡単に紹介します。

このページの内容は、Kii Cloud の機能範囲外のため、情報が不要であれば、読み飛ばしても問題ありません。また、このページに記載されている情報の詳細を知りたい場合は、Web 上の一般的な技術情報などを検索してください。

JDeferred による Promise の利用

Kii Cloud にアクセスする API は実行に時間がかかるため、作業スレッドから API を呼び出す必要があります。

Thing-IF SDK for Android の API はすべて、処理が完了するまで呼び出し元に制御を返さない ブロッキング API です。Android では、ユーザーインターフェイスの処理をメインスレッドが行っているため、メインスレッドがネットワーク呼び出しで待ち状態になると、モバイルアプリ全体がフリーズしてしまいます。この問題を防止するには、作業スレッドを作成し、作業スレッド内で API を実行する必要があります。

Hello Thing-IF では、JDeferred を使って作業スレッドでの実行を自動化しています。

Thing-IF SDK を利用するには、Kii Cloud SDK を併用する必要があります。Kii Cloud SDK はブロッキング API とノンブロッキング API の両方をサポートしていますが、Hello Thing-IF ではブロッキング API に統一しています。

ノンブロッキング API は、SDK 内部で作業スレッドを作成し、結果をコールバックメソッドで返すものです。詳細は、このページの最後の「より詳しく学びたい方へ」をご覧ください。

たとえば、コマンドの実行 を行うため、次のようなブロッキング API を呼び出したいとします。

try {
  Command command = mApi.postNewCommand(HelloThingIF.SCHEMA_NAME, HelloThingIF.SCHEMA_VERSION, actions);
  Log.i("CommandID: " + command.getCommandID());
} catch (Throwable tr) {
  tr.printStackTrace();
}

JDeferred による Promise を使って同じ処理を書き換えると、次のようになります。

まず、postNewCommand() を作業スレッドで実行できるようにします。PromiseAPIWrapper.java には、以下のような API のラッパーが用意されています。doInBackgroundSafe メソッドの内部に作業スレッドで実行したい処理を記述します。

public Promise<Command, Throwable, Void> postNewCommand(final String schemaName, final int schemaVersion, final List<Action> actions) {
  return mAdm.when(new DeferredAsyncTask<Void, Void, Command>() {
    @Override
    protected Command doInBackgroundSafe(Void... voids) throws Exception {
      return mApi.postNewCommand(schemaName, schemaVersion, actions);
    }
  });
}

postNewCommand() の呼び出し元(サンプルアプリの CommandFragment.java)では以下のように AndroidDeferredManager のメソッドを呼び出して非同期処理を行います。成功時の処理を onDone() メソッドに、失敗時の処理を onFail() メソッドに記述します。

AndroidDeferredManager mAdm = new AndroidDeferredManager();

PromiseAPIWrapper api = new PromiseAPIWrapper(mAdm, mApi);
mAdm.when(api.postNewCommand(HelloThingIF.SCHEMA_NAME, HelloThingIF.SCHEMA_VERSION, actions)
).then(new DoneCallback<Command>() {
  @Override
  public void onDone(Command command) {
    Log.i("CommandID: " + command.getCommandID());
  }
}).fail(new FailCallback<Throwable>() {
  @Override
  public void onFail(final Throwable tr) {
    tr.printStackTrace();
  }
});

JDeferred を使った実装は非常に複雑に見えますが、ほとんどの部分はアプリケーションのロジックとしての意味を持ちません。たとえば、上記のコードでは、アプリケーションの処理に関する部分は下の図の着色部分のみで、その他は JDeferred を使うためのオーバーヘッドです。着色部分のみを書き換えれば、任意の処理を Promise で実装できます。

実行される処理は以下のシーケンス図のようになります。AndroidDeferredManager の内部でスレッドが作成されて doInBackgroundSafe メソッドが呼び出されます。Kii Cloud SDK や Thing-IF SDK の API はブロッキング API として実行されますが、実行は作業スレッドのため、この間にユーザーが画面を操作しても問題ありません。

処理の完了後は、onDone() または onFail() メソッドが呼び出されます。ここでは、メインスレッドに切り替えられているため、他のユーザーインターフェイスの処理との排他処理は不要です。

なお、Promise を使う本質は、連続した非同期処理を混乱なく実行できる点にあります。API 呼び出しのチェーンを行っても、いわゆる「コールバック地獄」によってプログラムが複雑化しない長所があります。Hello Thing-IF では、1 回だけの API 呼び出しで処理が完結するため、単純に非同期処理のフレームワークとしてしか使用していません。Promise を本格的に使用する場合は、下記 より詳しく学びたい方へ の内容もご確認ください。

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 {
          // Do something with the worker thread
          mApi.postNewCommand(HelloThingIF.SCHEMA_NAME, HelloThingIF.SCHEMA_VERSION, actions);
        } catch (Exception e) {
          final Exception exception = e;
          handler.post(new Runnable() {
            @Override
            public void run() {
              // Do something with the main thread
              mTextViewMessage.setText("Error: " + exception.getLocalizedMessage());
            }
          });
        }
        return null;
      }
    }.execute();
  }
}

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

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

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

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

Butter Knife の利用

Hello Thing-IF では、Butter Knife を利用しています。Butter Knife を使うと、ユーザーインターフェイスの実装をシンプルにできます。

Butter Knife は、次の 2 通りの用途に使用しています。

  • ユーザーインターフェイス部品とクラスのフィールドを紐付ける処理
  • ボタンをリスナーのメソッドに紐付ける処理

ユーザーインターフェイスの部品とクラスのフィールドを紐付けるには、フィールドの宣言で以下のように @BindView アノテーションを使用します。従来のように、findViewById() で紐付ける必要はありません。

@BindView(R.id.textEdit1)
TextEdit myTextEdit;

ボタンとリスナーを紐付けるには、メソッドで以下のように @OnClick アノテーションを使用します。従来のように、setOnClickListener() の呼び出しや、リスナークラスの準備は必要ありません。

@OnClick(R.id.buttonExecute)
void onExecuteClicked() {
}

これらの準備を行った後、初期化で ButterKnife.bind() を、後処理で unbind() をそれぞれ実行します。

private Unbinder mButterknifeUnbunder;

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
                         Bundle savedInstanceState) {
  ...
  mButterknifeUnbunder = ButterKnife.bind(this, view);
  ...
}

@Override
public void onDestroyView() {
  super.onDestroyView();
  mButterknifeUnbunder.unbind();
}

Hello Thing-IF では、LoginFragment と CommandFragment で利用しています。


次は...

HelloThingIF クラスでモバイルアプリの初期化部分について説明します。

Application クラスの実装 に移動してください。

より詳しく学びたい方へ

  • JDeferred の実装方法の概要は、Thing-IF SDK ガイドの ブロッキング API を参照してください。
  • Promise は Kii Cloud SDK for JavaScript でも利用できます。Kii Cloud SDK 向けの チュートリアル も Promise の利用方法の参考にしてください。
  • Kii Cloud SDK for Android の機能範囲では、ブロッキング API とノンブロッキング API の両方を用意しています。詳細は ブロッキング API vs. ノンブロッキング API を参照してください。
  • Butter Knife の詳細は、Butter Knife の Web サイト をご覧ください。なお、新規プロジェクトに導入する場合は Butter Knife の Web サイトではなく、GitHub に詳細な手順が記載されています。