バージョン 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
|
また、次の点に注意してください。
- チャネル変更の通知の場合、
Channel.ChannelListener
はChannelClient.ChannelCallback
に置き換えられます。 - リスナー コールバック用のスレッドを設定する場合、
GoogleApiClient.Builder.setHandler
はWearableOptions.Builder
のsetLooper
メソッドに置き換えられます。
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
クラスと関連インターフェース(ConnectionCallbacks
、OnConnectionFailedListener
など)を使用している場合は、すべて削除し、他にリスナーを実装している場合は、すべて新しいバージョンに置き換えます。オーバーライドする実際のメソッドは通常、旧バージョンと同じ名前を持っているため、主な変更点は以下の例のようになります。
たとえば、以前の 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); } }