Wear OS のデータを新しいモバイル デバイスに転送する

ユーザーが Wear OS デバイスをセットアップする際には、Wear OS デバイスを特定のモバイル デバイスに接続します。その後、新しいモバイル デバイスを入手して、既存の Wear OS デバイスをその新しいモバイル デバイスに接続する可能性があります。Wear OS デバイスに関連するデータの一部は、その時点で接続中のモバイル デバイスに保存されます。

Wear OS 4 以降では、ユーザーが新しいモバイル デバイスに接続したときに、Wear OS のデータを新しいモバイル デバイスに転送できます。データは転送時に自動的に同期されます。

ユーザーが転送をリクエストすると、Wearable Data Layer によって、一方のモバイル デバイスに保存されていた DataItem オブジェクトがもう一方のモバイル デバイスに送られます。これで、ユーザーはアプリをシームレスに利用できるようになります。

ここでは、このシナリオに対応するよう Wear OS アプリとそのコンパニオン モバイルアプリを設定する方法について説明します。

準備

データ転送プロセスにおける DataItem オブジェクトの扱い方は、どちらのアプリがデータを所有するかによって異なります。

Wear OS アプリが所有するオブジェクト
Wear OS デバイスに保持されます。
モバイルアプリが所有するオブジェクト

以前のデバイスにアーカイブされます。アーカイブされたデータは、システムによって DataItemBuffer オブジェクトにパッケージングされ、新しいモバイル デバイスにインストールされているモバイルアプリに送られます。

アーカイブが送られるとすぐに、Wearable Data Layer から onNodeMigrated() リスナーが呼び出されます。これは、Wear OS デバイスからデータが書き込まれると通知される仕組みと同様です。

転送されたデータを保持する

転送された DataItem オブジェクトは、アプリ側で保持してください。データが新しいモバイル デバイスに送られるとすぐに、以前のデバイスからアーカイブが削除されます。

次の各条件を満たしていることを確認してください。

  1. 転送に関与するどちらのモバイル デバイスにもアプリがインストールされていること。
  2. それぞれのモバイル デバイスにインストールされているモバイルアプリのパッケージ署名が一致すること。

上記の条件を満たさないと、アーカイブされた DataItem オブジェクトは送信されず、破棄されます。

以前のモバイル デバイスからデータを受信する

以前のモバイル デバイスにアーカイブされたデータを新しいモバイル デバイスで受信するには、モバイルアプリで、WearableListenerService クラスの onNodeMigrated() コールバックを実装する必要があります。そのための手順は次のとおりです。

  1. モバイルアプリのビルドファイルに、Google Play 開発者サービスのウェアラブル ライブラリの最新バージョンへの依存関係を追加します。

    dependencies {
        ...
        implementation 'com.google.android.gms:play-services-wearable:18.2.0'
    }
  2. アプリのマニフェスト ファイルで WearableListenerService を宣言してエクスポートします。

    <service
    android:name=".MyWearableListenerService"
    android:exported="true">
    <intent-filter>
        ...
        <action android:name="com.google.android.gms.wearable.NODE_MIGRATED" />
        <data android:scheme="wear" />
    </intent-filter>
    </service>
    
  3. WearableListenerService を拡張して onNodeMigrated() をオーバーライドするサービスクラスを作成します。

    Kotlin

    class MyWearableListenerService : WearableListenerService() {
        val dataClient: DataClient = Wearable.getDataClient(this)
    
        private fun shouldHandleDataItem(nodeId: String,
                                        dataItem: DataItem): Boolean {
            // Your logic here
            return dataItem.uri.path?.startsWith("/my_feature_path/") == true
        }
    
        private fun handleDataItem(nodeId: String, dataItem: DataItem) {
            val data = dataItem.data ?: return
            val path = dataItem.uri.path ?: return
            // Your logic here
            if (data.toString().startsWith("Please restore")) {
                dataClient.putDataItem(
                    PutDataRequest.create(path).setData(data)
                )
            }
        }
    
        override fun onNodeMigrated(nodeId: String, archive: DataItemBuffer) {
            val dataItemsToHandle = mutableListOf<DataItem>()
    
            for (dataItem in archive) {
                if (shouldHandleDataItem(nodeId, dataItem)) {
                    dataItemsToHandle.add(dataItem.freeze())
                }
            }
    
            // Callback stops automatically after 20 seconds of data processing.
            // If you think you need more time, delegate to a coroutine or thread.
            runBlocking {
                for (dataItem in dataItemsToHandle) {
                    handleDataItem(nodeId, dataItem)
                }
            }
        }
    }

    Java

    public class MyWearableListenerService extends WearableListenerService {
        private final DataClient dataClient = Wearable.getDataClient(this);
    
        private boolean shouldHandleDataItem(String nodeId, DataItem dataItem) {
            // Your logic here
            return Objects.requireNonNull(dataItem.getUri().getPath())
                    .startsWith("/my_feature_path/");
        }
    
        private Task<DataItem> handleDataItem(String nodeId, DataItem dataItem) {
            byte[] data = dataItem.getData();
            String path = dataItem.getUri().getPath();
            // Your logic here
            if (data != null && path != null && Arrays.toString(data)
                    .startsWith("Please restore")) {
                assert path != null;
                return dataClient.putDataItem(
                            PutDataRequest.create(path).setData(data));
        }
    
        @Override
        public void onNodeMigrated(@NonNull String nodeId, DataItemBuffer archive) {
            List<DataItem> dataItemsToHandle = new ArrayList<>();
    
            for (DataItem dataItem : archive) {
                if (shouldHandleDataItem(nodeId, dataItem)) {
                    dataItemsToHandle.add(dataItem.freeze());
                }
            }
    
            for (dataItem in dataItemsToHandle) {
                handleDataItem(nodeId, dataItem);
            }
    
            // Callback stops automatically after 20 seconds of data processing.
            // If you think you need more time, delegate to another thread.
        }
    }