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 オブジェクトにコールバック メソッドを追加します。

// 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>) { ... }

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

同期呼び出し

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

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

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

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

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

データレイヤ イベントをリッスンする方法には次の 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 の実装方法を示しています。

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
                    )
            }
    }
}

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

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

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

<service
    android:name=".snippets.datalayer.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>

DATA_CHANGED アクション フィルタは、アプリがデータレイヤ イベントに関心があることをシステムに伝えます。

この例では、スマートウォッチが /start-activity データアイテムをリッスンし、スマートフォンが /data-item-receivedDATA_ITEM_RECEIVED_PATH)メッセージのレスポンスをリッスンします。

フィルタのマッチングには 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 の実装例を以下に示します。

class MainActivity : Activity(), DataClient.OnDataChangedListener {

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

    override fun onPause() {
        super.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)
            }
        }
    }
}

注意: Wearable Data Layer API を使用する前に、デバイスで利用可能であることを確認してください。そうしないと、例外が発生します。Horologist で実装されている GoogleApiAvailability クラスを使用します。

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

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

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