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 アプリの場合、このアップデートは該当しません。

注: この API は現在のところ、Android スマートフォンおよび Android スマートフォンとペア設定された Wear OS スマートウォッチでのみ使用できます。iOS スマートフォンとペア設定された Wear OS スマートウォッチの場合、インターネット接続があれば、アプリは他のクラウドベースの API にクエリできます。

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

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 ステートメントが含まれていました。この 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);
  }
}