將 Wear 應用程式遷移至 GoogleApi

11.8.0 版的 Google Play 服務開始,Wear OS 應用程式應進行遷移,不再使用 GoogleApiClient 類別,而是改用基於 GoogleApi 類別的用戶端物件。

使用 GoogleApi 可以減少設定非同步作業的複雜度。舉例來說,您可以取得 Task 而非 PendingResult 物件,如 Tasks API 簡介所述。

本頁面收錄了:

  • 替換元件表格
  • 將現有應用程式更新為使用 Tasks API 的範例

注意:這項更新不適用於中國版 Wear OS 應用程式;這類應用程式通常採用 10.2.0 版的 Google Play 服務。

注意:目前只有和 Android 手機配對的 Android 手機和 Wear OS 手錶才能使用此 API。Wear OS 手錶與 iOS 手機配對時,在有網際網路連線的情況下,應用程式可以查詢其他雲端式 API。

替換已淘汰的元件

如果您使用的類別可以擴充 GoogleApi 類別,例如 DataClient MessageClient,那麼 Google Play 服務 SDK 會為您管理 Google Play 服務的連線。

使用以下替換類別的應用程式不需要建立及管理 GoogleApiClient 物件。另請參閱「存取 Google API」以及 Wearable 類別的參考資料頁面

以下表格會列出已淘汰的元件及替換元件:

已淘汰的元件 替換元件
CapabilityApi CapabilityClient
Channel ChannelClient.Channel
ChannelApi ChannelClient
DataApi DataClient
MessageApi MessageClient
NodeApi NodeClient

此外,也請注意下列事項:

Wear 應用程式遷移範例

以下程式碼片段可做為遷移範例,顯示採用 Data Layer APIWear Data Layer 示例針對 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 Data Layer 示例之前在 MainActivity.java 檔案中含有以下匯入陳述式。您應移除這段 import 陳述式:

Kotlin

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

Java

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

舉例來說,在 Wear Data Layer 示例中,上文所示的 import 陳述式已取代為以下內容 (第二段用來處理工作例外狀況):

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 等) 的用例,然後將其他 Listener 實作替換為新版本。實際需要覆寫的方法通常都會繼承之前的名稱,所以主要變更內容會像下方範例這樣。

舉例來說,Wear Data Layer 示例的主要活動 (如 GitHub 的差異比較所示) 之前是實作 CapabilityApi.CapabilityListener 介面。但現在,主要活動會實作 CapabilityClient.OnCapabilityChangedListener

下文將比較類別定義。

以下是使用 11.8.0 版 Google Play 服務前的程式碼片段:

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

以下是使用 11.8.0 版 Google Play 服務後的程式碼片段:

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 Data Layer 示例

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 Data Layer 範例所示,您可以使用 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 和 Tasks API 的程式碼,請參閱 Wear Data Layer 示例。以在 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);
  }
}