Di chuyển ứng dụng Wear sang GoogleApi

Kể từ phiên bản 11.8.0 của Dịch vụ Google Play, ứng dụng Wear OS sẽ di chuyển khỏi lớp GoogleApiClient để chuyển sang sử dụng đối tượng ứng dụng khách dựa trên lớp GoogleApi.

Việc sử dụng GoogleApi giúp bạn dễ dàng thiết lập các thao tác không đồng bộ. Ví dụ: như mô tả trong phần giới thiệu về API Tasks, bạn có thể lấy đối tượng Task thay vì đối tượng PendingResult.

Trang này gồm có:

  • Bảng các thành phần thay thế
  • Ví dụ về cách cập nhật một ứng dụng hiện có để sử dụng API Tasks

Lưu ý: Bản cập nhật này không áp dụng cho các ứng dụng Wear OS ở Trung Quốc (các ứng dụng này thường sử dụng phiên bản 10.2.0 của Dịch vụ Google Play).

Lưu ý: API này hiện chỉ có trên điện thoại Android và đồng hồ Wear OS được ghép nối với điện thoại Android. Đối với đồng hồ Wear OS được ghép nối với điện thoại iOS, ứng dụng có thể truy vấn các API trên đám mây khác nếu có kết nối Internet.

Thay thế cho các thành phần đã ngừng sử dụng

Khi bạn sử dụng các lớp có khả năng mở rộng lớp GoogleApi, chẳng hạn như DataClient MessageClient, thì SDK Dịch vụ Google Play sẽ quản lý các kết nối của bạn đến Dịch vụ Google Play.

Ứng dụng sử dụng các lớp thay thế bên dưới không cần tạo và quản lý đối tượng GoogleApiClient. Ngoài ra, hãy xem phần Truy cập các API của Googletrang tham chiếu của lớp Wearable (thiết bị đeo).

Bảng sau đây chứa các thành phần đã ngừng sử dụng và thành phần thay thế:

Thành phần đã ngừng sử dụng Thành phần thay thế
CapabilityApi CapabilityClient
Channel ChannelClient.Channel
ChannelApi ChannelClient
DataApi DataClient
MessageApi MessageClient
NodeApi NodeClient

Ngoài ra, hãy lưu ý những điều sau:

Ví dụ về di chuyển cho ứng dụng Wear

Ví dụ về việc di chuyển: các đoạn mã dưới đây minh hoạ cách mẫu Lớp dữ liệu trên Wear (sử dụng API Lớp dữ liệu) được cập nhật cho phiên bản 11.8.0 của Dịch vụ Google Play. Nếu ứng dụng có một mô-đun điện thoại, thì các bản cập nhật của ứng dụng đó có thể tương tự như các bản cập nhật dành cho mô-đun Wear.

Cập nhật phần phụ thuộc trên Dịch vụ Google Play

Vì ứng dụng của bạn có thể phụ thuộc vào một phiên bản cũ của Dịch vụ Google Play nên hãy cập nhật phần phụ thuộc sau trong tệp build.gradle của mô-đun Wear:

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

Cập nhật câu lệnh nhập của ứng dụng

Nhập các lớp cần thiết, bao gồm cả các lớp trong API Tasks.

Ví dụ: trước đây mẫu Lớp dữ liệu trên Wear đã đưa câu lệnh nhập sau vào tệp MainActivity.java. Câu lệnh import này nên được xoá:

Kotlin

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

Java

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

Chẳng hạn trong mẫu Lớp dữ liệu trên Wear, các câu lệnh import như trên đã được thay thế bằng các câu lệnh sau (câu lệnh thứ hai là để xử lý các trường hợp ngoại lệ của nhiệm vụ):

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;
...

Triển khai giao diện của ứng dụng mới

Xoá mọi hoạt động sử dụng lớp GoogleApiClient và các giao diện liên kết (ConnectionCallbacks, OnConnectionFailedListener, v.v.), đồng thời thay thế phương thức triển khai Trình nghe khác bằng các phiên bản mới của các giao diện này. Các phương thức thực tế để ghi đè thường có cùng tên như trước đây, vì vậy thay đổi chính sẽ tương tự như ví dụ dưới đây.

Hoạt động chính của mẫu Lớp dữ liệu trên Wear (như được biểu thị trong một điểm khác biệt trên GitHub) đã được triển khai. Chẳng hạn như giao diện CapabilityApi.CapabilityListener. Nhưng hiện giờ hoạt động chính sẽ triển khai CapabilityClient.OnCapabilityChangedListener.

Dưới đây là phần so sánh các định nghĩa lớp.

Đây là đoạn mã trước khi sử dụng phiên bản 11.8.0 của Dịch vụ 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

Đây là một đoạn mã sau khi sử dụng phiên bản 11.8.0 của Dịch vụ 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

Loại bỏ và thêm người nghe

Các đối tượng ứng dụng khách mới được lưu vào bộ nhớ đệm và chia sẻ giữa các phiên bản GoogleApi, vì vậy bạn không cần phải giữ lại các biến thành viên; việc tạo ứng dụng không hề tốn kém và không làm mất trình nghe của các biến này.

Dưới đây là một đoạn mã từ mẫu Lớp dữ liệu trên Wear đã sửa đổi:

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);
}

Yêu cầu thông tin bằng API Tasks

Bạn có thể muốn yêu cầu thông tin bên ngoài trình nghe cập nhật ứng dụng khi có thay đổi về dữ liệu. Trong những trường hợp như vậy, hãy đưa ra yêu cầu bằng cách sử dụng một ứng dụng như DataClient, kết hợp với API Tasks và một lớp kết quả (tức là Task<ResultType>).

Ví dụ: như hiển thị trong mẫu Lớp dữ liệu trên Wear, bạn có thể sử dụng API Tasks để tìm các nút kết nối có khả năng sau:

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();
}

Để biết thêm mã sử dụng API Wearable và Tasks, hãy xem bài viết mẫu Lớp dữ liệu trên Wear. Và trong ví dụ về việc sử dụng các tác vụ nặng (tốn kém) ngoài luồng giao diện người dùng hoặc trong một dịch vụ, chúng ta có một tuỳ chọn khác. Dưới đây là ví dụ về cách chặn một tác vụ và nhận kết quả đồng bộ:

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);
  }
}