Перенос приложений Wear в GoogleApi

Начиная с версии 11.8.0 сервисов Google Play, приложения Wear OS должны перейти из класса GoogleApiClient и вместо этого использовать клиентские объекты, основанные на классе GoogleApi .

Использование GoogleApi упрощает настройку асинхронных операций. Например, как описано во введении в API Tasks , вы можете получить объект Task вместо объекта PendingResult .

Эта страница включает в себя:

  • Таблица сменных компонентов
  • Пример обновления существующего приложения для использования Tasks API

Примечание. Это обновление не распространяется на приложения Wear OS для Китая , которые обычно используют сервисы Google Play версии 10.2.0.

Примечание. В настоящее время этот API доступен только на телефонах Android и часах с ОС Wear, сопряженных с телефонами Android. Для часов Wear OS, подключенных к телефонам iOS, приложения могут запрашивать другие облачные API, если доступно подключение к Интернету.

Замена устаревших компонентов

Когда вы используете классы, расширяющие класс GoogleApi , такие как DataClient и MessageClient , SDK сервисов Google Play управляет подключениями к сервисам Google Play за вас.

Приложениям, использующим приведенные ниже классы замены, не требуется создавать объекты GoogleApiClient и управлять ими. Также см. Доступ к API Google и справочную страницу для класса Wearable .

В следующей таблице приведены устаревшие компоненты и их замены:

Устаревший компонент Сменный компонент
CapabilityApi CapabilityClient
Channel ChannelClient.Channel
ChannelApi ChannelClient
DataApi DataClient
MessageApi MessageClient
NodeApi NodeClient

Также обратите внимание на следующее:

Пример миграции приложения Wear

В качестве примера миграции приведенные ниже фрагменты кода иллюстрируют, как образец Wear Data Layer , использующий Data Layer API , был обновлен для версии 11.8.0 сервисов Google Play. Если в вашем приложении есть модуль телефона, его обновления могут быть аналогичны обновлениям модуля Wear.

Обновите зависимость от сервисов Google Play.

Поскольку ваше приложение может зависеть от более ранней версии сервисов Google Play, обновите следующую зависимость в файле build.gradle вашего модуля Wear:

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

Обновите операторы импорта вашего приложения.

Импортируйте необходимые классы, в том числе классы в Tasks API .

Например, ранее образец уровня данных об износе включал в файл MainActivity.java следующий оператор импорта. Этот оператор import следует удалить:

Котлин

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

Ява

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

В примере Wear Data Layer операторы import , подобные приведенным выше, были заменены, например, на следующие (второй — для обработки исключений задач):

Котлин

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

Ява

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

Внедрение новых клиентских интерфейсов

Удалите любое использование класса GoogleApiClient и связанных с ним интерфейсов ( ConnectionCallbacks , OnConnectionFailedListener и т. д.) и замените другие реализации прослушивателя их новыми версиями. Фактические методы для переопределения обычно имеют те же имена, что и раньше, поэтому основное изменение аналогично примеру ниже.

Основное действие примера Wear Data Layer (как указано в diff на GitHub ) реализовало, например, интерфейс CapabilityApi.CapabilityListener . Но теперь основное действие реализует CapabilityClient.OnCapabilityChangedListener .

Ниже приведено сравнение определений классов.

Вот фрагмент до использования сервисов Google Play версии 11.8.0:

Котлин

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

Ява

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

Вот фрагмент после использования сервисов Google Play версии 11.8.0:

Котлин

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

Ява

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

Удаление и добавление прослушивателей

Новые клиентские объекты кэшируются и совместно используются экземплярами GoogleApi , поэтому нет необходимости хранить переменные-члены; клиенты не требуют больших затрат в создании и не теряют своих слушателей.

Ниже приведен фрагмент измененного образца слоя данных об износе :

Котлин

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

Ява

@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 для поиска подключенных узлов с любыми заданными возможностями:

Котлин

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

Ява

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

Дополнительный код, использующий API-интерфейсы Wearable и Tasks, см. в примере уровня данных об износе . И в качестве примера использования тяжелых задач вне потока пользовательского интерфейса или в службе доступен еще один вариант. Вот пример того, как заблокировать задачу и получить результат синхронно:

Котлин

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

Ява

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