Wear アプリを GoogleApi に移行する

バージョン 11.8.0 以降の Google Play 開発者サービスを使用する Wear OS アプリは、GoogleApiClient クラスの使用をやめて、 GoogleApi クラスをベースとしたクライアント オブジェクトを使用するように移行する必要があります。

GoogleApi を使用すると、非同期処理を簡単にセットアップすることができます。たとえば、Tasks API の概要で説明しているとおり、 PendingResult オブジェクトの代わりに Task オブジェクトを取得することができます。

このページでは、以下について説明します。

  • 後継コンポーネント(表を参照)
  • Tasks API を使用するように既存のアプリをアップデートする例

注: 主に Google Play 開発者サービス バージョン 10.2.0 を使用している中国向け Wear OS アプリの場合、このアップデートは該当しません。

サポートが終了したコンポーネントの後継コンポーネント

DataClient MessageClient など、 GoogleApi クラスを拡張するクラスを使用すると、Google Play 開発者サービスに対する接続が、Google Play 開発者サービス SDK によって自動的に管理されます。

下記の後継クラスを使用しているアプリの場合、GoogleApiClient オブジェクトの作成や管理が不要になります。Google API にアクセスするWearable クラスのリファレンス ページもご覧ください。

サポートが終了したコンポーネントと、その後継コンポーネントを以下の表に示します。

サポート終了コンポーネント 後継コンポーネント
CapabilityApi CapabilityClient
Channel ChannelClient.Channel
ChannelApi ChannelClient
DataApi DataClient
MessageApi MessageClient
NodeApi NodeClient

注:

Wear アプリの移行例

移行例として、Data Layer API を使用している Wear データレイヤ サンプルを Google Play 開発者サービス バージョン 11.8.0 向けにアップデートするコード スニペットを以下に示します。アプリにスマートフォン モジュールがある場合は、スマートフォン モジュールも Wear モジュールと同様にアップデートしてください。

Google Play 開発者サービスに対する依存関係をアップデートする

アプリが旧バージョンの Google Play 開発者サービスに依存している可能性があるため、Wear モジュールの build.gradle ファイル内で以下の依存関係をアップデートします。

    dependencies {
    ...
    compile 'com.google.android.gms:play-services-wearable:11.8.0'
    }
    

アプリのインポート ステートメントをアップデートする

Tasks API 内のクラスを含め、必要なクラスをインポートします。

たとえば、以前の Wear データレイヤ サンプルでは、MainActivity.java ファイル内に次のインポート ステートメントが含まれていました。この import ステートメントは削除する必要があります。

Kotlin

    ...
    import com.google.android.gms.common.api.GoogleApiClient
    ...
    

Java

    ...
    import com.google.android.gms.common.api.GoogleApiClient;
    ...
    

Wear データレイヤ サンプルにおいて、上記のような import ステートメントは、たとえば次のように置き換えます(2 番目のステートメントは、タスクの例外を処理するためのものです)。

Kotlin

    ...
    import com.google.android.gms.tasks.Tasks
    import java.util.concurrent.ExecutionException
    ...
    

Java

    ...
    import com.google.android.gms.tasks.Tasks;
    import java.util.concurrent.ExecutionException;
    ...
    

新しいクライアント インターフェースを実装する

GoogleApiClient クラスと関連インターフェース(ConnectionCallbacksOnConnectionFailedListener など)を使用している場合は、すべて削除し、他にリスナーを実装している場合は、すべて新しいバージョンに置き換えます。オーバーライドする実際のメソッドは通常、旧バージョンと同じ名前を持っているため、主な変更点は以下の例のようになります。

たとえば、以前の Wear データレイヤ サンプルのメイン アクティビティ(GitHub 内の差分を参照)は CapabilityApi.CapabilityListener インターフェースを実装していました。これに対し、現在のメイン アクティビティは CapabilityClient.OnCapabilityChangedListener を実装しています。

クラス定義の比較を以下に示します。

Google Play 開発者サービス バージョン 11.8.0 を使用する前のスニペット:

Kotlin

    class MainActivity :
            Activity(),
            GoogleApiClient.ConnectionCallbacks,
            GoogleApiClient.OnConnectionFailedListener,
            DataApi.DataListener,
            MessageApi.MessageListener,
            CapabilityApi.CapabilityListener
    

Java

    public class MainActivity extends Activity implements
      ConnectionCallbacks,
      OnConnectionFailedListener,
      DataApi.DataListener,
      MessageApi.MessageListener,
      CapabilityApi.CapabilityListener
    

Google Play 開発者サービス バージョン 11.8.0 を使用した後のスニペット:

Kotlin

    class MainActivity :
            Activity(),
            DataClient.OnDataChangedListener,
            MessageClient.OnMessageReceivedListener,
            CapabilityClient.OnCapabilityChangedListener
    

Java

    public class MainActivity extends Activity implements
      DataClient.OnDataChangedListener,
      MessageClient.OnMessageReceivedListener,
      CapabilityClient.OnCapabilityChangedListener
    

リスナーを削除して追加する

新しいクライアント オブジェクトはキャッシュされ、 GoogleApi インスタンス間で共有されるため、メンバー変数を保持する必要はありません。クライアントは低コストで作成することが可能で、リスナーを失うことはありません。

改訂後の Wear データレイヤ サンプルのスニペットを以下に示します。

Kotlin

    override fun onResume() {
        super.onResume()
        Wearable.getDataClient(this).addListener(this)
        Wearable.getMessageClient(this).addListener(this)
        Wearable.getCapabilityClient(this)
                .addListener(
                        this,
                        Uri.parse("wear://"),
                        CapabilityClient.FILTER_REACHABLE
                )
    }

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

Java

    @Override
    protected void onResume() {
      super.onResume();
      Wearable.getDataClient(this).addListener(this);
      Wearable.getMessageClient(this).addListener(this);
      Wearable.getCapabilityClient(this)
      .addListener(
        this, Uri.parse("wear://"), CapabilityClient.FILTER_REACHABLE);
    }

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

Tasks API を使用して情報をリクエストする

データが変更されたときにアプリを更新するリスナー以外の情報をリクエストすることもできます。そのような場合、 DataClient などのクライアントを使用してリクエストを作成し、Tasks API や結果クラス(Task<ResultType>)と組み合わせます。

たとえば、Wear データレイヤ サンプルに示すように、Tasks API を使用することで、特定の機能を持つ接続ノードを検出することができます。

Kotlin

    private fun showNodes(vararg capabilityNames: String) {
        Wearable.getCapabilityClient(this)
                .getAllCapabilities(CapabilityClient.FILTER_REACHABLE).apply {
                    addOnSuccessListener { capabilityInfoMap ->
                        val nodes: Set<Node> = capabilityInfoMap
                                .filter { capabilityNames.contains(it.key) }
                                .flatMap { it.value.nodes }
                                .toSet()
                        showDiscoveredNodes(nodes)
                    }
                }
    }

    private fun showDiscoveredNodes(nodes: Set<Node>) {
        val nodesList: Set<String> = nodes.map { it.displayName }.toSet()
        val msg: String = if (nodesList.isEmpty()) {
            Log.d(TAG, "Connected Nodes: No connected device was found for the given capabilities")
            getString(R.string.no_device)
        } else {
            Log.d(TAG, "Connected Nodes: ${nodesList.joinToString(separator = ", ")}")
            getString(R.string.connected_nodes, nodesList)
        }
        Toast.makeText(this@MainActivity, msg, Toast.LENGTH_LONG).show()
    }
    

Java

    private void showNodes(final String... capabilityNames) {
      Task<Map<String, CapabilityInfo>> capabilitiesTask =
        Wearable.getCapabilityClient(this)
                .getAllCapabilities(CapabilityClient.FILTER_REACHABLE);
      capabilitiesTask.addOnSuccessListener(new
        OnSuccessListener<Map<String, CapabilityInfo>>() {
          @Override
          public void onSuccess(Map<String, CapabilityInfo>
            capabilityInfoMap) {
              Set<Node> nodes = new HashSet<>();
              if (capabilityInfoMap.isEmpty()) {
                showDiscoveredNodes(nodes);
                return;
              }
              for (String capabilityName : capabilityNames) {
                CapabilityInfo capabilityInfo = capabilityInfoMap.get(capabilityName);
                if (capabilityInfo != null) {
                  nodes.addAll(capabilityInfo.getNodes());
                }
              }
              showDiscoveredNodes(nodes);
          }
      });
    }

    private void showDiscoveredNodes(Set<Node> nodes) {
      List<String> nodesList = new ArrayList<>();
      for (Node node : nodes) {
        nodesList.add(node.getDisplayName());
      }
      LOGD(TAG, "Connected Nodes: " + (nodesList.isEmpty()
        ? "No connected device was found for the given capabilities"
        : TextUtils.join(",", nodesList)));
      String msg;
      if (!nodesList.isEmpty()) {
        msg = getString(R.string.connected_nodes, TextUtils.join(", ", nodesList));
      } else {
        msg = getString(R.string.no_device);
      }
      Toast.makeText(MainActivity.this, msg, Toast.LENGTH_LONG).show();
    }
    

Wearable API と Tasks API を活用した追加コードについては、Wear データレイヤ サンプルをご覧ください。また、UI スレッドやサービス内の高付加タスクを使用する例として、別のオプションも利用できます。タスクをブロックして、結果を同期取得する方法の例を以下に示します。

Kotlin

    override fun doInBackground(vararg params: Asset): Bitmap? {
        if (params.isNotEmpty()) {
            val asset = params[0]
            val getFdForAssetResponseTask: Task<DataClient.GetFdForAssetResponse> =
                    Wearable.getDataClient(applicationContext).getFdForAsset(asset)
            return try {
                // Block on a task and get the result synchronously. This is generally done
                // when executing a task inside a separately managed background thread. Doing
                // this on the main (UI) thread can cause your application to become
                // unresponsive.
                val getFdForAssetResponse: DataClient.GetFdForAssetResponse =
                        Tasks.await(getFdForAssetResponseTask)
                getFdForAssetResponse.inputStream?.let { assetInputStream ->
                    BitmapFactory.decodeStream(assetInputStream)
                } ?: run {
                    Log.w(TAG, "Requested an unknown Asset.")
                    null
                }

            } catch (exception: ExecutionException) {
                Log.e(TAG, "Failed retrieving asset, Task failed: $exception")
                return null
            } catch (exception: InterruptedException) {
                Log.e(TAG, "Failed retrieving asset, interrupt occurred: $exception")
                return null
            }

        } else {
            Log.e(TAG, "Asset must be non-null")
            return null
        }
    }

    override fun onPostExecute(bitmap: Bitmap?) {
        bitmap?.also {
            Log.d(TAG, "Setting background image on second page..")
            moveToPage(1)
            assetFragment.setBackgroundImage(it)
        }
    }
    

Java

    @Override
    protected Bitmap doInBackground(Asset... params) {
      if (params.length > 0) {
        Asset asset = params[0];
        Task<DataClient.GetFdForAssetResponse> getFdForAssetResponseTask =
          Wearable.getDataClient(getApplicationContext()).getFdForAsset(asset);
        try {
          // Block on a task and get the result synchronously. This is generally done
          // when executing a task inside a separately managed background thread. Doing
          // this on the main (UI) thread can cause your application to become
          // unresponsive.
          DataClient.GetFdForAssetResponse getFdForAssetResponse =
            Tasks.await(getFdForAssetResponseTask);
          InputStream assetInputStream = getFdForAssetResponse.getInputStream();
          if (assetInputStream != null) {
            return BitmapFactory.decodeStream(assetInputStream);
          } else {
            Log.w(TAG, "Requested an unknown Asset.");
            return null;
          }

        } catch (ExecutionException exception) {
          Log.e(TAG, "Failed retrieving asset, Task failed: " + exception);
          return null;
        } catch (InterruptedException exception) {
          Log.e(TAG, "Failed retrieving asset, interrupt occurred: " + exception);
          return null;
        }
      } else {
        Log.e(TAG, "Asset must be non-null");
        return null;
      }
    }

    @Override
    protected void onPostExecute(Bitmap bitmap) {
      if (bitmap != null) {
        LOGD(TAG, "Setting background image on second page..");
        moveToPage(1);
        assetFragment.setBackgroundImage(bitmap);
      }
    }