Android Dev Summit, October 23-24: two days of technical content, directly from the Android team. Sign-up for livestream updates.

Wear 上的网络访问和同步

有了 Wear OS by Google 谷歌,手表可以直接与网络通信,无需访问 Android 或 iOS 手机。这种直接访问网络的方式取代了使用 Data Layer API(在 Wear 1.x 中)连接到网络。

请参阅以下相关资源:

网络访问

Wear OS 应用可以发出网络请求。当手表通过蓝牙连接到手机时,手表的网络流量通常由手机代理。但是,当手机不可用时,会使用 WLAN 和移动数据网络,具体取决于硬件。Wear 平台可处理网络之间的转换。

您可以使用 HTTP、TCP 和 UDP 等协议。不过,不能使用 android.webkit API(包括 CookieManager 类)。您可以通过读取和写入请求和响应的标头来使用 Cookie。

此外,我们还建议您使用以下 API:

  • JobScheduler API,适用于异步作业,包括定期轮询(如下所述)
  • 多网络 API,在需要连接到特定网络类型时使用,请参阅多个网络连接

高带宽网络访问

Wear OS 平台管理网络连接的目标是提供最佳的整体用户体验。该平台通过平衡以下两个因素来选择默认活动网络:

  • 对较长电池续航时间的需求
  • 对网络带宽的需求

当优先考虑节省电池电量时,活动网络可能没有足够的带宽来执行需要高带宽的网络任务,如传输大型文件或流式传输媒体。

本部分将指导您如何使用 ConnectivityManager 类来确保为您的应用提供必需的网络带宽。有关对网络资源进行精细控制的一般信息,请参阅管理网络使用情况

另请参阅下述说明做法的示例

获取高带宽网络

在 Wear OS 上,不要认为高带宽网络始终可用。对于需要高带宽网络访问的用例,如传输大型文件或流式传输媒体,我们建议您执行以下步骤:

  1. 检查活动网络,如果有活动网络,则检查其带宽。
  2. 如果没有活动网络或其带宽不足,则请求访问不按流量计费的 WLAN 或移动数据网络。

您可以使用 ConnectivityManager 类检查是否存在活动网络以及它是否具有足够的带宽:

Kotlin

    const val MIN_BANDWIDTH_KBPS = 320
    connectivityManager = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
    val bandwidth: Int = connectivityManager.activeNetwork?.let { activeNetwork ->
        connectivityManager.getNetworkCapabilities(activeNetwork).linkDownstreamBandwidthKbps
    } ?: -1

    when {
        bandwidth < 0 -> {
            // No active network
        }
        bandwidth in (0 until MIN_BANDWIDTH_KBPS) -> {
            // Request a high-bandwidth network
        }
        else -> {
            // You already are on a high-bandwidth network, so start your network request
        }
    }
    

Java

    int MIN_BANDWIDTH_KBPS = 320;
    connectivityManager =
      (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
    Network activeNetwork = connectivityManager.getActiveNetwork();

    if (activeNetwork != null) {
      int bandwidth =
        connectivityManager.getNetworkCapabilities(activeNetwork).getLinkDownstreamBandwidthKbps();

      if (bandwidth < MIN_BANDWIDTH_KBPS) {
        // Request a high-bandwidth network
      } else {
        // You already are on a high-bandwidth network, so start your network request
      }
    } else {
      // No active network
    }
    

您可以使用 ConnectivityManager 请求不按流量计费的高带宽网络。对于单次网络请求,您可以请求不按流量计费的 WLAN 或移动数据网络。当网络准备就绪(例如,设备的 WLAN 无线装置连接到保存的网络)时,会调用 NetworkCallback 实例的 onAvailable() 方法。如果未找到合适的网络,则不会调用 onAvailable() 方法。因此,您应手动将您的请求设为超时;请参阅等待网络可用

Kotlin

    networkCallback = object : ConnectivityManager.NetworkCallback() {
        override fun onAvailable(network: Network) {
            if (bindProcessToNetwork(network)) {
                // socket connections will now use this network
            } else {
                // app doesn't have android.permission.INTERNET permission
            }
        }
    }

    val request: NetworkRequest = NetworkRequest.Builder().run {
        addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
        addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
        addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED)
        addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
        build()
    }

    connectivityManager.requestNetwork(request, networkCallback)
    

Java

    networkCallback = new ConnectivityManager.NetworkCallback() {
      @Override
      public void onAvailable(Network network) {
        if (bindProcessToNetwork(network)) {
          // socket connections will now use this network
        } else {
          // app doesn't have android.permission.INTERNET permission
        }
      }
    };

    NetworkRequest request = new NetworkRequest.Builder()
      .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
      .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
      .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED)
      .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
      .build();

    connectivityManager.requestNetwork(request, networkCallback);
    

释放网络

当您的应用不再需要高带宽网络时,您必须使用 ConnectivityManager 类释放网络,以确保平台可以恢复网络访问的管理。

Kotlin

    connectivityManager.bindProcessToNetwork(null)
    connectivityManager.unregisterNetworkCallback(networkCallback)
    

Java

    connectivityManager.bindProcessToNetwork(null);
    connectivityManager.unregisterNetworkCallback(networkCallback);
    

为了优化耗电量,网络连接应仅在 Activity 持续时间内保持注册状态。因此,您应考虑在 Activity 的 onStop() 方法中释放网络。

等待网络可用

可能无法瞬间获取网络,因为手表的 WLAN 或移动数据网络无线装置可能处于关闭状态以节省电池电量。此外,如果手表无法连接到网络,则不会调用 NetworkCallback 实例的 onAvailable() 方法。因此,您应在预先确定的时间长度过后将请求设为超时并释放所有关联资源。

Kotlin

    const val MESSAGE_CONNECTIVITY_TIMEOUT = 1
    const val NETWORK_CONNECTIVITY_TIMEOUT_MS: Long = 10000
    ...
    handler = MyHandler()

    networkCallback = object : ConnectivityManager.NetworkCallback() {
        override fun onAvailable(network: Network) {
            handler.removeMessages(MESSAGE_CONNECTIVITY_TIMEOUT)
            ...
        }
    }

    connectivityManager.requestNetwork(request, networkCallback)

    handler.sendMessageDelayed(
            handler.obtainMessage(MESSAGE_CONNECTIVITY_TIMEOUT),
            NETWORK_CONNECTIVITY_TIMEOUT_MS
    )
    ...
    // Don't make this an inner class otherwise there is the potential to leak the parent class
    private class MyHandler : Handler() {
        override fun handleMessage(msg: Message) {
            when (msg.what) {
                MESSAGE_CONNECTIVITY_TIMEOUT -> {
                    // unregister the network
                }
            }
        }
    }
    

Java

    int MESSAGE_CONNECTIVITY_TIMEOUT = 1;
    long NETWORK_CONNECTIVITY_TIMEOUT_MS = 10000;

    handler = new MyHandler();

    networkCallback = new ConnectivityManager.NetworkCallback() {
      @Override
      public void onAvailable(Network network) {
        handler.removeMessages(MESSAGE_CONNECTIVITY_TIMEOUT);
        ...
      }
    };

    connectivityManager.requestNetwork(request, networkCallback);

    handler.sendMessageDelayed(
      handler.obtainMessage(MESSAGE_CONNECTIVITY_TIMEOUT),
      NETWORK_CONNECTIVITY_TIMEOUT_MS);

    ...
    // This needs to be static to avoid potentially leaking the parent class
    private static class MyHandler extends Handler {
       @Override
       public void handleMessage(Message msg) {
           switch (msg.what) {
               case MESSAGE_CONNECTIVITY_TIMEOUT:
                   // unregister the network
                   break;
           }
       }
    }
    

监控网络状态

NetworkCallback 接口提供了一些方法来监控绑定网络的状态变化,如带宽变化和失去连接。

Kotlin

    networkCallback = object : ConnectivityManager.NetworkCallback() {
        override fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) {
            val bandwidth: Int = networkCapabilities.linkDownstreamBandwidthKbps

            if (bandwidth < MIN_BANDWIDTH_KBPS) {
                // handle insufficient network bandwidth
            }
        }

        override fun onLost(network: Network) {
            // handle network loss
        }
    }
    

Java

    networkCallback = ConnectivityManager.NetworkCallback {
      @Override
      public void onCapabilitiesChanged(Network network, NetworkCapabilities networkCapabilities) {
        int bandwidth = networkCapabilities.getLinkDownstreamBandwidthKbps();

          if (bandwidth < MIN_BANDWIDTH_KBPS) {
            // handle insufficient network bandwidth
          }
      }

      @Override
      public void onLost(Network network) {
        // handle network loss
      }
    }
    

启动 WLAN 设置 Activity

请求 WLAN 网络时,如果已配置保存的网络且该网络在范围之内,系统会尝试连接到该网络。不过,如果没有保存的 WLAN 网络可用,则绝不会调用 NetworkCallback 实例的 onAvailable() 回调方法。如果您使用 Handler 将网络请求设为超时,在发生超时后,您可以引导用户添加 WLAN 网络。您可以使用以下 intent 直接将用户带到用于添加 WLAN 网络的 Activity:

Kotlin

    context.startActivity(Intent("com.google.android.clockwork.settings.connectivity.wifi.ADD_NETWORK_SETTINGS"))
    

Java

    context.startActivity(new Intent("com.google.android.clockwork.settings.connectivity.wifi.ADD_NETWORK_SETTINGS"));
    

要启动设置 Activity,您的应用必须具有以下权限:android.permission.CHANGE_WIFI_STATE

界面注意事项

如果您的应用需要连接到新的 WLAN 网络以进行高带宽操作,请确保在启动 WLAN 设置之前先向用户清晰阐明连接的原因。只有在需要高带宽网络时,才要求用户添加新的 WLAN 网络。请勿阻止用户访问不需要高带宽网络的应用功能。

例如,图 1 显示了一款音乐应用。该应用应允许用户浏览音乐,并且只有在用户需要下载或流式传输音乐时,才要求用户添加新的 WLAN 网络。

音乐下载

图 1. 用于下载音乐的音乐应用流程。

如果您的应用需要高带宽网络才能运行,那么在要求用户添加新的 WLAN 网络之前,应先向用户阐述明确的理由。此外,对于长时间运行的网络操作(如下载用户的媒体播放列表),您应显示一个进度指示器,并说明正在执行的操作。

图 2 显示了该音乐应用的流式传输音乐流程。如果用户想要流式传输音乐并且需要高带宽网络,那么在将用户转到 WLAN 设置之前,该应用应清楚地解释为什么需要新的 WLAN 网络。

音乐流式传输

图 2. 用于流式传输音乐的音乐应用流程。

云消息传递

对于发送通知,应用可以直接使用 Firebase 云消息传递 (FCM),它取代了 Google 云消息传递 (GCM)。Wear 2.0 支持 FCM,但不支持 GCM。

没有网络访问或 FCM API 特定于 Wear OS。请参阅有关连接到网络云消息传递的现有文档。

FCM 可与低电耗模式很好地搭配使用,因此我们建议您采用这种方式将通知发送到手表。

当您的 Wear 应用运行时,通过收集设备的注册令牌,通过 FCM 提供消息。然后,当服务器将消息发送到 FCM REST 端点时,将令牌作为目标的一部分包括在内。FCM 会将消息发送到由令牌标识的设备。

FCM 消息采用 JSON 格式,并且可以包含以下两种或其中一种负载:

  • 通知负载。 当手表收到通知负载时,将直接在通知流中向用户显示数据。当用户点按通知时,将启动您的应用。
  • 数据负载。该负载具有一组自定义键值对。该负载将作为数据传送到您的 Wear 应用。

如需了解负载的详情和示例,请参阅关于 FCM 消息

默认情况下,通知会从手机应用桥接(共享)到手表。如果您具有独立 Wear 应用和对应的手机应用,则可能会出现重复的通知。例如,如果手机和手表接收了通过 FCM 传递的同一通知,这两部设备可能会独立地显示该通知。

使用后台服务

要确保后台任务得到正确执行,必须考虑到低电耗模式。在 Android 6.0 中,低电耗模式和应用待机模式延长了电池续航时间。

低电耗模式在 Android Nougat 和 Wear OS 中得到了增强。当屏幕关闭或进入微光模式的时间足够长时,设备会在一定的时间段内部分进入低电耗模式,且后台任务可能会延迟。之后,如果设备长时间处于静止状态,将进入常规低电耗模式。

您应使用 JobScheduler API 来调度作业,您的应用可通过它注册在低电耗模式下安全的代码执行。调度作业时,您可以选择约束条件,如定期执行、需要连接或设备充电。您应采用不会对电池续航时间产生不利影响的方式配置作业。作业应使用 JobInfo.Builder 对象来提供约束条件和元数据,例如,使用以下一种或多种方法来调度任务:

  • 要调度需要网络的任务,请使用 setRequiredNetworkType(int networkType),指定 NETWORK_TYPE_ANYNETWORK_TYPE_UNMETERED。请注意,NETWORK_TYPE_UNMETERED 适用于大型数据传输,而 NETWORK_TYPE_ANY 适用于小型数据传输。
  • 要调度在设备充电时执行的任务,请使用 setRequiresCharging(boolean requiresCharging)
  • 要指定在设备处于闲置状态时执行任务,请使用 setRequiresDeviceIdle(boolean requiresDeviceIdle)。此方法对于优先级较低的后台工作或同步很有用,特别是在与 setRequiresCharging 一起使用时。

请注意,某些低带宽网络(如蓝牙 LE)被视为按流量计费的网络。

使用约束条件进行调度

您可以调度需要约束条件的任务。在以下示例中,当符合以下约束条件时,JobScheduler 对象会激活 MyJobService

  • 不按流量计费的网络
  • 设备充电

您可以使用构建程序方法 setExtras 将特定于应用的元数据捆绑包附加到作业请求。执行您的作业时,会将此捆绑包提供给您的作业服务。请注意传递给 JobInfo.Builder 构造函数的 MY_JOB_ID 值。此 MY_JOB_ID 值是应用提供的标识符。如果对 cancel 命令进行后续调用并使用同一值创建后续作业,将更新现有作业:

Kotlin

    JobInfo.Builder(MY_JOB_ID,
            ComponentName(this, MyJobService::class.java))
            .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED)
            .setRequiresCharging(true)
            .setExtras(extras)
            .build()
            .also { jobInfo ->
                (getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler)
                        .schedule(jobInfo)
            }
    

Java

    JobInfo jobInfo = new JobInfo.Builder(MY_JOB_ID,
            new ComponentName(this, MyJobService.class))
            .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED)
            .setRequiresCharging(true)
            .setExtras(extras)
            .build();
    ((JobScheduler) getSystemService(JOB_SCHEDULER_SERVICE))
            .schedule(jobInfo);
    

下面是用于处理上述作业的 JobService 实现。执行作业时,会将一个 JobParameters 对象传入 onStartJob 方法。通过该 JobParameters 对象,您可以获得作业 ID 值以及在调度作业时提供的任何 extra 捆绑包。onStartJob 方法在主应用线程上进行调用,因此任何开销大的逻辑都应从单独的线程运行。在本例中,使用了 AsyncTask 在后台运行代码。工作完成后,您应调用 jobFinished 方法以通知 JobScheduler 任务已完成:

Kotlin

    private class MyJobService : JobService() {

        override fun onStartJob(params: JobParameters): Boolean {
            JobAsyncTask().execute(params)
            return true
        }

        private class JobAsyncTask : AsyncTask<...>() { ... }
    }
    

Java

    public class MyJobService extends JobService {
        @Override public boolean onStartJob(JobParameters params) {
            new JobAsyncTask().execute(params);
            return true;
        }

        private class JobAsyncTask extends AsyncTask