Wear 上でデータレイヤ イベントを処理する

Data Layer API を呼び出したときは、呼び出しの完了時に呼び出しのステータスを受け取ることができます。また、Wear OS by Google ネットワーク上の任意の場所でアプリが行ったデータ変更に起因するデータイベントをリッスンできます。

Data Layer API の効果的な使用例については、Android DataLayer サンプルアプリをご覧ください。

Data Layer API 呼び出しのステータスを待機する

Data Layer API の呼び出し( DataClient クラスの putDataItem メソッドを使用した呼び出しなど)では、 Task<ResultType> オブジェクトが返されることがあります。Task オブジェクトが作成されると、直ちにオペレーションがバックグラウンドでキューに登録されます。この後に実行する処理がない場合、オペレーションは最終的に通知なしで完了します。

しかし、通常はオペレーションの完了後に結果に基づいてなんらかの処理を行う必要があるため、Task オブジェクトにより、非同期的または同期的に結果のステータスを待機できます。

非同期呼び出し

コードがメイン UI スレッドで実行されている場合は、Data Layer API の呼び出しをブロックしないでください。非同期的に呼び出しを実行するには、オペレーションの完了時に呼び出される Task オブジェクトにコールバック メソッドを追加します。

Kotlin

// Using Kotlin function references
task.addOnSuccessListener(::handleDataItem)
task.addOnFailureListener(::handleDataItemError)
task.addOnCompleteListener(::handleTaskComplete)
...
fun handleDataItem(dataItem: DataItem) { ... }
fun handleDataItemError(exception: Exception) { ... }
fun handleTaskComplete(task: Task<DataItem>) { ... }

Java

// Using Java 8 Lambdas.
task.addOnSuccessListener(dataItem -> handleDataItem(dataItem));
task.addOnFailureListener(exception -> handleDataItemError(exception));
task.addOnCompleteListener(task -> handleTaskComplete(task));

その他の実行可能な操作(各種タスクの連続実行など)については、Task API をご覧ください。

同期呼び出し

バックグラウンド サービス内( WearableListenerService 内など)の別個のハンドラ スレッドでコードが実行されている場合は、呼び出しをブロックしても問題ありません。この場合は、Task オブジェクトに対して Tasks.await() を呼び出すことができます。このメソッドはリクエストが完了するまでブロックし、Result オブジェクトを返します。次の例はこのメソッドを示しています。

注: このメソッドはメインスレッドで呼び出さないでください。

Kotlin

try {
    Tasks.await(dataItemTask).apply {
        Log.d(TAG, "Data item set: $uri")
    }
}
catch (e: ExecutionException) { ... }
catch (e: InterruptedException) { ... }

Java

try {
    DataItem item = Tasks.await(dataItemTask);
    Log.d(TAG, "Data item set: " + item.getUri());
} catch (ExecutionException | InterruptedException e) {
  ...
}

データレイヤ イベントをリッスンする

データレイヤはハンドヘルド デバイスとウェアラブル デバイスの間でデータの同期と送信を行うので、通常はデータアイテムの作成やメッセージの受信などの重要なイベントをリッスンする必要があります。

データレイヤ イベントをリッスンする方法には次の 2 つがあります。

どちらの方法でも、処理対象のイベントのデータイベント コールバック メソッドをオーバーライドします。

注: リスナー実装を選択する際は、アプリのバッテリー使用量を考慮してください。WearableListenerService はアプリのマニフェストに登録されるため、アプリがまだ実行されていない場合でも、このサービスでアプリを起動できます。アプリがすでに実行されているときにイベントのリッスンのみを行う必要がある場合(これはインタラクティブなアプリでよくあります)、WearableListenerService は使用しないでください。代わりに、ライブリスナーを登録します。 たとえば、DataClient クラスの addListener メソッドを使用します。これにより、システムにかかる負荷とバッテリー使用量を削減できます。

WearableListenerService を使用する

通常は、ウェアラブル アプリとハンドヘルド アプリの両方で WearableListenerService のインスタンスを作成します。ただし、いずれか一方のアプリでデータイベントを処理しない場合、そのアプリにサービスを実装する必要はありません。

たとえば、データアイテム オブジェクトを設定および取得するハンドヘルド アプリと、そのオブジェクトの更新をリッスンして UI を更新するウェアラブル アプリがあるとします。この場合、ウェアラブル アプリがデータアイテムを更新することはないため、ハンドヘルド アプリではウェアラブル アプリのデータイベントをリッスンしません。

WearableListenerService を使用してリッスンできるイベントを次にいくつか示します。

  • onDataChanged(): データアイテム オブジェクトが作成、削除、変更されたとき、接続されているすべてのノードで、システムによりこのコールバックがトリガーされます。
  • onMessageReceived(): ノードからメッセージが送信されたとき、ターゲット ノードでこのコールバックがトリガーされます。
  • onCapabilityChanged(): アプリのインスタンスがアドバタイズする機能がネットワークで使用可能になったとき、そのイベントによりこのコールバックがトリガーされます。近くにあるノードを探している場合、コールバックで提供されたノードの isNearby() メソッドに対してクエリを実行できます。

ChannelClient.ChannelCallback からのイベント(onChannelOpened() など)をリッスンすることもできます。

上記のイベントはすべて、メインスレッドではなくバックグラウンド スレッドで実行されます。

WearableListenerService を作成する手順は次のとおりです。

  1. WearableListenerService を拡張するクラスを作成します。
  2. 対象のイベント(onDataChanged() など)をリッスンします。
  3. Android マニフェスト内でインテント フィルタを宣言して、WearableListenerService についてシステムに通知します。この宣言により、必要に応じてシステムがサービスをバインドできるようになります。

シンプルな WearableListenerService の実装方法の例を次に示します。

Kotlin

private const val TAG = "DataLayerSample"
private const val START_ACTIVITY_PATH = "/start-activity"
private const val DATA_ITEM_RECEIVED_PATH = "/data-item-received"

class DataLayerListenerService : WearableListenerService() {

    override fun onDataChanged(dataEvents: DataEventBuffer) {
        if (Log.isLoggable(TAG, Log.DEBUG)) {
            Log.d(TAG, "onDataChanged: $dataEvents")
        }

        // Loop through the events and send a message
        // to the node that created the data item.
        dataEvents.map { it.dataItem.uri }
                .forEach { uri ->
                    // Get the node ID from the host value of the URI.
                    val nodeId: String = uri.host
                    // Set the data of the message to be the bytes of the URI.
                    val payload: ByteArray = uri.toString().toByteArray()

                    // Send the RPC.
                    Wearable.getMessageClient(this)
                            .sendMessage(nodeId, DATA_ITEM_RECEIVED_PATH, payload)
                }
    }
}

Java

public class DataLayerListenerService extends WearableListenerService {
    private static final String TAG = "DataLayerSample";
    private static final String START_ACTIVITY_PATH = "/start-activity";
    private static final String DATA_ITEM_RECEIVED_PATH = "/data-item-received";

    @Override
    public void onDataChanged(DataEventBuffer dataEvents) {
        if (Log.isLoggable(TAG, Log.DEBUG)) {
            Log.d(TAG, "onDataChanged: " + dataEvents);
        }

        // Loop through the events and send a message
        // to the node that created the data item.
        for (DataEvent event : dataEvents) {
            Uri uri = event.getDataItem().getUri();

            // Get the node ID from the host value of the URI.
            String nodeId = uri.getHost();
            // Set the data of the message to be the bytes of the URI.
            byte[] payload = uri.toString().getBytes();

            // Send the RPC.
            Wearable.getMessageClient(this).sendMessage(
                  nodeId,  DATA_ITEM_RECEIVED_PATH, payload);
        }
    }
}

次のセクションでは、このリスナーとともにインテント フィルタを使用する方法について説明します。

WearableListenerService とともにフィルタを使用する

前のセクションで示した WearableListenerService サンプルのインテント フィルタは次のようになります。

<service android:name=".DataLayerListenerService" android:exported="true" tools:ignore="ExportedService" >
  <intent-filter>
      <action android:name="com.google.android.gms.wearable.DATA_CHANGED" />
      <data android:scheme="wear" android:host="*"
               android:path="/start-activity" />
  </intent-filter>
</service>

このフィルタでは、以前推奨されていた BIND_LISTENER アクションが DATA_CHANGED アクションに置き換えられています。これにより、特定のイベントの場合にのみ、アプリの復帰や起動が行われます。この変更により、システムの効率性を改善し、電池の消費量およびアプリに関連するその他のオーバーヘッドを削減できます。この例では、スマートウォッチが /start-activity データアイテムをリッスンし、スマートフォンが /data-item-received メッセージのレスポンスをリッスンします。

フィルタのマッチングには Android の標準のルールが適用されます。マニフェストごとに複数のサービスを、サービスごとに複数のインテント フィルタを、フィルタごとに複数のアクションを、フィルタごとに複数のデータスタンザを指定できます。フィルタのマッチングは、ワイルドカード ホストまたは特定のホストで行うことができます。ワイルドカード ホストでマッチングを行うには、host="*" を使用します。一方、特定のホストでマッチングを行うには、host=<node_id> を指定します。

リテラルパスまたはパス プレフィックスをマッチングすることも可能です。そのためには、ワイルドカード ホストまたは特定のホストを指定する必要があります。それ以外のパスを指定すると、システムによって無視されます。

Wear OS がサポートするフィルタタイプについて詳しくは、WearableListenerService の API リファレンス ドキュメントをご覧ください。

データフィルタとマッチング ルールについて詳しくは、API リファレンス ドキュメントの <data> マニフェスト要素をご覧ください。

インテント フィルタをマッチングする際は、次の 2 つの重要なルールに留意してください。

  • インテント フィルタのスキームが指定されていない場合、システムは他の URI 属性をすべて無視します。
  • フィルタのホストが指定されていない場合、システムはパス属性をすべて無視します。

ライブリスナーを使用する

ユーザーがアプリを操作しているときにアプリがデータレイヤ イベントのみを監視する場合、サービスを長時間実行してすべてのデータ変更を処理する必要はありません。そのような場合は、以下のインターフェースを 1 つ以上実装することで、アクティビティ内でイベントをリッスンできます。

データイベントをリッスンするアクティビティを作成する手順は次のとおりです。

  1. 目的のインターフェースを実装します。
  2. onCreate() メソッドまたは onResume() メソッドで、Wearable.getDataClient(this).addListener()MessageClient.addListener()CapabilityClient.addListener()ChannelClient.registerChannelCallback() のいずれかを呼び出して、アクティビティがデータレイヤ イベントをリッスンすることに関心があることを Google Play 開発者サービスに通知します。
  3. onStop() または onPause() で、DataClient.removeListener()MessageClient.removeListener()CapabilityClient.removeListener()ChannelClient.unregisterChannelCallback() のいずれかを使用してリスナーの登録を解除します。
  4. アクティビティの関心の対象が特定のパス プレフィックスを持つイベントのみの場合は、適切なプレフィックス フィルタを指定したリスナーを追加することで、現在のアプリの状態に関するデータのみを受け取ることができます。
  5. 実装したインターフェースに応じて、onDataChanged()onMessageReceived()onCapabilityChanged()、あるいは ChannelClient.ChannelCallback のメソッドを実装します。各メソッドはメインスレッドで呼び出されます。また、WearableOptions を使用してカスタム Looper を指定することもできます。

DataClient.OnDataChangedListener の実装例を以下に示します。

Kotlin

class MainActivity : Activity(), DataClient.OnDataChangedListener {

    public override fun onResume() {
        Wearable.getDataClient(this).addListener(this)
    }

    override fun onPause() {
        Wearable.getDataClient(this).removeListener(this)
    }

    override fun onDataChanged(dataEvents: DataEventBuffer) {
        dataEvents.forEach { event ->
            if (event.type == DataEvent.TYPE_DELETED) {
                Log.d(TAG, "DataItem deleted: " + event.dataItem.uri)
            } else if (event.type == DataEvent.TYPE_CHANGED) {
                Log.d(TAG, "DataItem changed: " + event.dataItem.uri)
            }
        }
    }
}

Java

public class MainActivity extends Activity implements DataClient.OnDataChangedListener {

    @Override
    public void onResume() {
        Wearable.getDataClient(this).addListener(this);
    }

    @Override
    protected void onPause() {
        Wearable.getDataClient(this).removeListener(this);
    }

    @Override
    public void onDataChanged(DataEventBuffer dataEvents) {
        for (DataEvent event : dataEvents) {
            if (event.getType() == DataEvent.TYPE_DELETED) {
                Log.d(TAG, "DataItem deleted: " + event.getDataItem().getUri());
            } else if (event.getType() == DataEvent.TYPE_CHANGED) {
                Log.d(TAG, "DataItem changed: " + event.getDataItem().getUri());
            }
        }
    }
}

ライブリスナーとともにフィルタを使用する

前述のように、マニフェスト ベースの WearableListenerService オブジェクトに対してインテント フィルタを指定できるのと同様に、Wearable API を通じてライブリスナーを登録する際にもインテント フィルタを使用できます。API ベースのライブリスナーとマニフェスト ベースのリスナーの両方に同じルールが適用されます。

一般的なパターンとしては、アクティビティの onResume() メソッドで特定のパスまたはパス プレフィックスを指定してリスナーを登録し、その後アクティビティの onPause() メソッドでリスナーを削除します。この方法でリスナーを実装すると、アプリは選択的にイベントを受信できるようになり、アプリの設計と効率性が改善されます。