Android手表-Wear Develop 之与手机开发的异同
[toc]
手表开发与手机开发的不同点
一、AndroidManifest.xml 中配置不同
手表中将应用标识为独立应用
Wear 应用的 Android 清单文件中必须包含 meta-data元素,作为 <application>
元素的子元素。meta-data
元素的名称为 com.google.android.wearable.standalone
,且值必须为 true
或 false
。该元素可指明您的手表应用是否为不需要手机端 Android 应用即可运行的独立应用。如果将该元素的值设为 true
,则可以在与 iPhone 配对的手表上的 Play 商店中提供您的应用,前提是所有发布渠道(例如,在测试版中)中目前使用的 APK 都将该元素设为 true
。如果您目前向用户提供的所有 APK(Alpha 版、Beta 版和正式版)并非都有上述设置,那么当用户在与 iPhone 配对的手表上搜索时,您的应用不会显示。
手表应用可以归入以下类别之一:
- 完全独立于手机应用
- 半独立(手机应用不是必需的,只提供可选功能)
- 依赖于手机应用
如果手表应用是完全独立应用或半独立应用,可以归入独立类别。您必须通过将此 meta-data 元素的值设为 true,向 Google Play 商店指明此分类:
<application>
...
<meta-data
android:name="com.google.android.wearable.standalone"
android:value="true" />
...
</application>
由于独立应用(即独立应用或半独立应用)可由 iPhone 用户或没有安装 Play 商店的 Android 手机用户安装,因此该类手表应用应该可以在没有手机应用的情况下使用。
如果手表应用依赖于手机应用,应将上面的 meta-data
元素的值设为 false
。将该元素的值设为 false
表示手表应用应仅安装在与具有 Play 商店的手机配对的手表上。
注意:即使该值为
false
,用户也可以在安装手机应用前安装手表应用。因此,如果手表应用检测到配对手机缺少必要的手机应用,则手表应用应提示用户安装该手机应用。
将应用定义为 Wear 应用
您必须确保在应用的 Android 清单文件中指定了 <uses-feature> 标记。该标记必须指明这是一个 watch
应用,如以下代码段中的 android:name="android.hardware.type.watch"
:
<manifest>
...
<uses-feature android:name="android.hardware.type.watch" />
...
</manifest>
小结:
手机应用是没有这个配置的!!!以上配置只有针对手表开发才配置,而且是手表的独立应用!
二、布局UI的不同
为Wear 创建自定义布局
借助 Wear OS by Google 谷歌,为手表创建布局与为手机创建布局很相似,区别
在于:设计时必须考虑到手表屏幕的尺寸,确保屏幕内容一览无余。请不要将手机应用的功能和界面移植到手表中,不要指望这样能带来良好的体验
。
仅当确有必要时,才应创建自定义布局。如需了解如何设计出色的手表应用,请阅读为 Wear OS 设计应用指南。
创建自定义通知
一般而言,您应在手机上创建通知,让它们自动同步到穿戴式设备。这样,您只需构建一次通知,即可让通知出现在多种类型的设备上(不只是手表,甚至还包括汽车和电视),而不必针对不同的设备类型分别设计通知。
如果标准通知样式(如 NotificationCompat.BigTextStyle
或 NotificationCompat.InboxStyle
)不能满足您的需求,您可以使用自定义布局显示 Activity。您只能在手表上创建和发出自定义通知,系统不会将这些通知同步到手机。
注意:在手表上创建自定义通知时,您可以用标准通知 API(API 级别 20)代替支持库。
如需创建自定义通知,请执行以下操作:
- 创建布局并将其设为您要显示的 Activity 的内容视图。
override fun onCreate(savedInstanceState: Bundle?) {
...
setContentView(R.layout.notification_activity)
}
- 在 Android 清单中定义该 Activity 的必要属性,以便在手表的上下文信息流进程中显示该 Activity。您需要将该 Activity 声明为可导出、可嵌入,并且具有一个空的 TaskAffinity。我们还建议将主题背景设为
Theme.DeviceDefault.Light
。例如:
<activity android:name="com.example.MyDisplayActivity"
android:exported="true"
android:allowEmbedded="true"
android:taskAffinity=""
android:theme="@android:style/Theme.DeviceDefault.Light" />
- 为您要显示的 Activity 创建 PendingIntent 。例如:
val notificationPendingIntent: PendingIntent =
Intent(this, NotificationActivity::class.java).let { notificationIntent ->
PendingIntent.getActivity(
this,
0,
notificationIntent,
PendingIntent.FLAG_UPDATE_CURRENT
)
}
- 构建一个 Notification 并调用提供 PendingIntent的 setDisplayIntent()。在用户查看您的通知时,系统将使用此 PendingIntent 启动该 Activity。
- 使用
notify()
方法发出通知。
注意:在 Wear 1.x 中,当通知出现在主屏幕上时,系统会用根据通知语义数据生成的标准模板显示该通知。此模板适用于所有表盘。当用户向上滑动通知时,他们会看到该通知的自定义 Activity。
使用 Wear 界面库创建布局
当您使用 Android Studio 项目向导创建手表应用时,会自动包含 Wear 界面库。您也可以通过以下依赖项声明将此库添加到您的 build.gradle
文件中:
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'androidx.wear:wear:1.0.0' //这个是必须的
//这个不是必须的,如果不使用Google的gms,则可以去除
compile 'com.google.android.gms:play-services-wearable:+'
}
此库可以帮助您构建针对手表设计的界面。如需了解详情,请参阅为 Wear 设备创建自定义界面。
下面是一些主要类:
BoxInsetLayout
一个 FrameLayout 对象,可识别屏幕形状并将其子对象包含在圆形屏幕的中央方形区域中。
ConfirmationActivity
一个 Activity,可在用户完成某项操作后显示确认动画。
AnimationSet
一组应一起播放的动画。
CircularProgressLayout
一种布局,可提供围绕子视图的圆形倒计时器。常用作在经过短暂延时后确认某项操作的自动计时器。
SnapHelper
SnapHelper 支持贴靠 RecyclerView 对象。
PagerSnapHelper
SnapHelper 实例的实现,支持以垂直或水平方向贴靠的分页器样式。
AlertDialog
Dialog 的一个子类,可显示一个、两个或三个按钮。
ProgressBar
向用户显示一个表示操作进度的进度条;在操作进行过程中,应用可以更改进度量(修改进度条的长度)。
WearableRecyclerView
RecyclerView 类的穿戴式设备版专用实现,用于在方形和圆形设备上显示滚动项目列表。
Wear 界面库 API 参考文档
参考文档详细介绍了如何使用每个界面微件。请浏览 Wear API 参考文档,了解上述类。
小结:
以上介绍的是手表中的UI,和手机上的UI不同,设计规范也可以参考Google的手表设计规范。
三、添加语音功能
语音操作是穿戴式设备体验的重要组成部分。借助此功能,用户无需动手就能快速执行操作。Wear OS by Google 谷歌提供了两种语音操作:
- 系统提供的语音操作
这种语音操作基于任务并内置于 Wear 平台。说出语音操作时,在您要启动的 Activity 中过滤它们。示例包括“添加记事”或“设置闹钟”。 - 应用提供的语音操作
这种语音操作基于应用,您可以像声明启动图标一样声明它们。用户可以说出“启动(您的应用名称)”以使用这些语音操作,此时您指定的 Activity 将会启动。
声明系统提供的语音操作
Wear OS 平台提供了一些基于用户操作(如“添加记事”或“设置闹钟”)的语音 intent。这可让用户说出他们想要执行的操作,然后让系统确定要启动的最佳 Activity。
当用户说出语音操作时,您的应用可以过滤为了启动 Activity 而触发的 intent。如果您要启动一项服务以在后台执行某项操作,可显示一个 Activity 作为视觉提示,并在此 Activity 中启动该服务。如果您想去除视觉提示,请务必调用 finish()。
例如,对于“添加记事”命令,可声明以下 intent 过滤器以启动一个名为 MyNoteActivity
的 Activity:
<activity android:name="MyNoteActivity">
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="com.google.android.voicesearch.SELF_NOTE" />
</intent-filter>
</activity>
下面列出了 Wear 平台支持的语音 Intent:
名称 | 示例语句 | Intent |
---|---|---|
叫辆车/出租车 | “Ok Google,叫辆出租车” “Ok Google,叫辆车” | action com.google.android.gms.actions.RESERVE_TAXI_RESERVATION |
添加记事 | “Ok Google,添加记事” “Ok Google,自我提醒” | action android.intent.action.SEND category com.google.android.voicesearch.SELF_NOTE EXTRA android.content.Intent.EXTRA_TEXT - 带有记事正文的字符串 |
格式调试的太费劲了,更多支持内容请参考Google文档。
声明应用提供的语音操作
如果没有适合您的平台语音 intent,您可以通过“启动 MyActivityName”语音操作直接启动您的应用。
在手持设备上,注册“启动”操作与注册启动器图标相同。但是,应用不会请求启动器中的应用图标,而会请求语音操作。
要指定在“启动”后要说的内容,可为您要启动的 Activtiy 指定 label 属性。例如,以下 Intent 过滤器可识别“启动 MyRunningApp”语音操作并启动 StartRunActivity。
<application>
<activity android:name="StartRunActivity" android:label="MyRunningApp">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
获取自由格式的语音输入
除使用语音操作启动 Activity 外,您还可以调用系统内置的语音识别程序 Activity 来获取用户的语音输入。这对于获取用户输入而后对其进行处理(如执行搜索或将其作为消息发送)非常有用。
在您的应用中,使用 ACTION_RECOGNIZE_SPEECH 操作调用 startActivityForResult()。这将启动语音识别 Activity,然后您可以在 onActivityResult() 中处理结果。
private const val SPEECH_REQUEST_CODE = 0
...
// Create an intent that can start the Speech Recognizer activity
private fun displaySpeechRecognizer() {
val intent = Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH).apply {
putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_FREE_FORM)
}
// Start the activity, the intent will be populated with the speech text
startActivityForResult(intent, SPEECH_REQUEST_CODE)
}
// This callback is invoked when the Speech Recognizer returns.
// This is where you process the intent and extract the speech text from the intent.
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent) {
if (requestCode == SPEECH_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
val spokenText: String? =
data.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS).let { results ->
results[0]
}
// Do something with spokenText
}
super.onActivityResult(requestCode, resultCode, data)
}
四、打造中国版 Wear OS 应用
打造中国版 Wear OS 应用时,您需要考虑未预安装 Google Play 服务的手机。本页介绍了国际开发者可能需要针对中国市场进行哪些常见的变更。
使用正确的 Google Play 服务版本
Google Play 服务 10.2.0 版为 Fused Location Provider API 和 Data Layer API 提供全球支持。如果开发者使用这些 API,则必须使用此版本。在其他情况下,此依赖项是可选的。
虽然 Google Play 服务现在包含 Wear 应用的新 API,但中国版 Wear OS 应用应继续使用与 GoogleApiClient
相关的 API;请参阅访问 Wearable API。
used Location Provider API
如果您使用的是 Fused Location Provider API,需要在 Wear 模块的 build.gradle 文件中添加以下依赖项:
dependencies {
...
compile 'com.google.android.gms:play-services-location:10.2.0'
}
Data Layer API
重要提示:自 Wear 2.0 以来,不再建议使用 Data Layer API。这是因为,应用现在可以直接访问互联网,从而简化了代码开发流程。此外,iOS 配对的 Wear 设备不支持 Data Layer API。
如果您的应用使用 Data Layer API,需要将以下行添加到 Wear 模块的 build.gradle 文件中。该行要求使用 10.2.0 版的客户端库:
dependencies {
...
compile 'com.google.android.gms:play-services-wearable:10.2.0'
...
}
此外,您还需要将以下行添加到 mobile 模块的 build.gradle 文件中。将 Google Play 服务依赖项替换为对 10.2.0 版本的引用。
dependencies {
...
compile 'com.google.android.gms:play-services-wearable:10.2.0'
}
身份验证
在实现身份验证之前,请检查您的用例,以查看是否确实需要身份验证。例如,对于提供天气预报的应用,可能不需要登录,因而无需进行身份验证。
如果您确实需要身份验证,我们建议您使用 OAuth 2.0,或使用设备上的输入作为后备措施。或者,您也可以使用 Data Layer 传递安全令牌。不过,建议您不要使用数据层,因为它不支持与 iOS 设备配对的 Wear OS 设备。
桥接通知
中国不支持桥接通知。仅当 Wear 设备通过蓝牙连接到手机时,才会将手机通知桥接到 Wear OS。
位置和地图坐标兼容性
您应使用 FusedLocationProvider 来检测用户在中国的位置,检测方式与在世界其他地方的检测方式一样。这样可确保您的应用获取最实用的信息,而不考虑手表硬件以及与手表配对的手机平台。此外,Wear OS 平台内置的电池优化功能也会发挥很大作用。
将 FusedLocationProvider
与第三方地图 SDK 集成时,您应考虑提供程序之间的坐标兼容性。FusedLocationProvider
按照 WGS84 标准报告位置信息。请务必视情况转换坐标系。
Google 健身支持
中国支持 Google 健身的累计计步器、活动时间和心肺强化分数,相关历史记录最多可保留七天。您可以在不提供用户凭据的情况下查看此历史记录。
模拟器支持
您可以使用中国版 Wear OS 模拟器映像来测试您的应用。Android Studio 3.0 及更高版本支持此功能。
五、在 Wear 上创建输入法
Wear OS by Google 谷歌通过扩展 Android 输入法 (IME) 框架,添加了对语音以外的输入法的支持。IME 框架提供了对虚拟屏幕键盘的支持,从而允许用户以点击按键、手写或手势的形式输入文本。
Wear 用户可从远程输入中选择各种输入选项。这些选项包括:
- 语音输入
- 表情符号
- 预设回复
- 智能回复
- 默认 IME
图 1. 输入法示例
Wear 随附系统默认输入法 (IME),并向第三方开发者开放 IME API,以便为 Wear 创建自定义 IME。用于 Wear 设备的 IME API 与用于其他设备类型的相同,但由于屏幕空间有限,因而在用法上略有不同。
本文档提供了可帮助您创建 Wear 专用 IME 的指导。
为 Wear 创建输入法
Android 平台提供了一个用于创建 IME 的标准框架。如需创建 Wear 专用 IME,您需要针对有限的屏幕尺寸优化 IME。
如需了解如何为 Wear 创建输入法,请按照在手机上创建输入法的指南进行操作,并在清单文件中添加以下 Google Play 过滤器,以使其成为 Wear 专用 IME。
Wear 专用 IME 过滤器
-
API 级别
如需为 Wear 开发 IME,请记住,只有 Android 6.0(API 级别 23)及更高版本的平台才支持此功能。为确保 IME 只能安装在支持语音以外的输入法的穿戴式设备上,请将以下代码添加到应用的清单中:
<uses-sdk android:minSdkVersion="23" />
-
这表示应用需要 Android 6.0 或更高版本。如需了解详情,请参阅 API 级别和 <uses-sdk> 元素的文档。
-
设备功能集
如需控制如何从不支持 Wear IME 的设备上(例如,在手机上)过滤应用,请将以下代码添加到应用的清单中:
<uses-feature android:required="true" android:name="android.hardware.type.watch" />
调用输入法
Wear 在手表上提供了用户设置,允许用户从已安装的 IME 列表中启用多个 IME。用户启用您的 IME 后,他们可通过以下方式调用您的 IME:
- 使用 RemoteInput API 的通知或应用。
- 具有 EditText 字段的 Wear 应用。触摸某个文本字段时,系统会将光标置于该字段中,并自动在焦点上显示 IME。
IME 一般注意事项
下面是为 Wear 实现 IME 时需要考虑的一些事项:
-
设置默认操作
RemoteInput
和 Wear 应用只能处理单行文本输入。ENTER 键应始终触发对 sendDefaultEditorAction 的调用,这会使应用关闭键盘,并继续执行下一步或下一项操作。 -
使用全屏模式 IME
Wear 上的输入法占用了屏幕的大部分空间,使得用户只能看到应用的很小一部分;使用全屏模式可确保最佳用户体验,而不管使用哪种应用界面。在全屏模式下,ExtractEditText 可提供所修改文本字段的镜像视图,并且可为其设置适当的样式,使其与输入法界面的其余部分融为一体。如需详细了解全屏模式,请参阅 InputMethodService。
-
处理 InputType 标记
出于隐私原因,您至少应处理您的 IME 中的
InputType
标记TYPE_TEXT_VARIATION_PASSWORD
。当 IME 处于密码模式时,请确保针对单次按键(停用自动拼写更正、自动完成和手势输入)优化键盘。最重要的是,处于密码模式的键盘应支持 ASCII 符号,而不管使用哪种输入语言。如需了解更多详情,请参阅指定输入法类型。 -
提供用于切换到下一个输入法的键
Android 允许用户在平台支持的所有 IME 之间轻松切换。在您的 IME 实现中,将布尔值设置为 supportsSwitchingToNextInputMethod = true,以使 IME 能够支持切换机制(以便应用可以切换到平台支持的下一个 IME)。如需详细了解如何实现 IME 之间的切换,请参阅在 IME 子类型之间切换。
六、在 Wear 上发送和同步数据
有了 Wear OS by Google 谷歌,手表可以直接与网络通信,无需访问 Android 或 iOS 手机。
本页提到的功能需要以下依赖项和前提条件:
- 最新版本的 Google Play 服务
- Wear OS 设备或 Wear AVD
另外,Wearable Data Layer API(Google Play 服务的一部分)为应用提供了一个可选的信道。尽管 Wear 应用可以使用 Wearable Data Layer API 与手机应用通信,但不建议使用此 API 连接到网络。
Data Layer API 由系统可以发送和同步的一组数据对象以及用于向应用通知某些事件的监听器组成,具体如下:
- 数据项
DataItem
提供在手持式设备和穿戴式设备之间自动同步的数据存储。
- 资源
Asset
对象用于发送二进制 blob,如图片。您将资源附加到数据项后,系统会自动为您处理资源传输,通过缓存大型资源来避免重复传输,从而节省蓝牙带宽。
- 消息
MessageClient
可以发送消息,适用于远程过程调用 (RPC),如从穿戴式设备控制手持式设备的媒体播放器,或者从手持式设备启动穿戴式设备上的 intent。消息也适用于单向请求或请求/响应通信模型。如果手持式设备和穿戴式设备已连接,系统会将消息加入队列以进行传递,并返回成功的结果代码。如果这些设备未连接,会返回错误。成功的结果代码并不能说明消息已成功传递,因为设备可能会在收到结果代码后断开连接。
- 信道
您可以使用 ChannelClient 将大型实体(如音乐和电影文件)从手持式设备传输到穿戴式设备。使用 ChannelClient 进行数据传输有以下好处:
- 当使用附加到 DataItem 对象的 Asset 对象时,在未提供自动同步的情况下,在两台或更多连接的设备之间传输大型数据文件。 ChannelClient 比 DataClient 更节省磁盘空间,后者在与连接的设备同步之前,会在本地设备上创建资源的副本。
- 可靠地发送因过大而无法使用
[MessageClient](https://developers.google.cn/android/reference/com/google/android/gms/wearable/MessageClient)
发送的文件。 - 传输流式数据,如从网络服务器提取的音乐或来自麦克风的语音数据。
- WearableListenerService(适用于服务)
通过扩展 WearableListenerService
,您可以在服务中监听重要的数据层事件。系统管理 WearableListenerService
的生命周期,在需要发送数据项或消息时绑定到该服务,而在不需要执行任何操作时取消绑定该服务。
- OnDataChangedListener(适用于前台 Activity)
通过在某个 Activity 中实现 OnDataChangedListener
,您可以在此 Activity 处于前台时侦听重要的数据层事件。使用此监听器代替 WearableListenerService
后,您可以仅在用户正在使用您的应用时监听相关更改。
警告
:由于这些 API 专为手持式设备与穿戴式设备之间的通信而设计,因此您只能使用这些 API 在这些设备之间建立通信。例如,不要尝试打开低级套接字来创建信道。
重要提示:您的手机和 Wear APK 签名以及签名方案必须完全相同,Data Layer API 才能在设备之间成功通信。这包括您使用的 v1 和/或 v2 签名。确保在
build.gradle
中或在使用 Generate Signed APK 向导时正确设置了签名方案。
Wear OS 支持多部穿戴式设备连接到一部手持式设备。例如,如果用户在手持式设备上保存了一条记事,它会自动显示在用户的所有 Wear 设备上。为了在设备之间同步数据,Google 服务器在设备网络中托管了一个云节点。系统会将数据同步到直接连接的设备、云节点,以及通过 WLAN 连接到云节点的穿戴式设备。
图 1. 包含手持式设备和穿戴式设备的节点网络示例。
网络访问和同步
本课向您介绍如何获取高带宽网络。本课还提供了云消息传递等方面的入门知识等。
访问穿戴式设备的数据层
本课向您介绍如何创建用于访问 Data Layer API 的客户端。
同步数据项
数据项是存储在会从手持式设备自动同步到穿戴式设备的重复数据存储中的对象。
传输资源
资源是通常用来传输图片或媒体的二进制 blob。
收发消息
消息专为可在穿戴式设备和手持式设备之间来回发送的“发后不理”消息而设计。
处理数据层事件
接收有关数据层更改和其他事件的通知。
将 Wear 应用迁移到 GoogleApi
停止使用 GoogleApiClient 类。
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 上,不要认为高带宽网络始终可用。对于需要高带宽网络访问的用例,如传输大型文件或流式传输媒体,我们建议您执行以下步骤:
- 检查活动网络,如果有活动网络,则检查其带宽。
- 如果没有活动网络或其带宽不足,则请求访问不按流量计费的 WLAN 或移动数据网络。
您可以使用 ConnectivityManager 类检查是否存在活动网络以及它是否具有足够的带宽:
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
}
}
您可以使用 ConnectivityManager 请求不按流量计费的高带宽网络。对于单次网络请求,您可以请求不按流量计费的 WLAN 或移动数据网络。当网络准备就绪时(例如,设备的 Wi-Fi 无线装置连接到保存的网络)时,会调用 NetworkCallback
实例的 onAvailable()
方法。如果未找到合适的网络,则不会调用 onAvailable()
方法。因此,您应手动将您的请求设为超时;请参阅等待网络可用。
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)
释放网络
当您的应用不再需要高带宽网络时,您必须使用 ConnectivityManager 类释放网络,以确保平台可以恢复网络访问管理。
connectivityManager.bindProcessToNetwork(null)
connectivityManager.unregisterNetworkCallback(networkCallback)
为了优化耗电量,网络连接应仅在 Activity 持续时间内保持注册状态。因此,您应考虑在 Activity 的 onStop() 方法中释放网络。
- 等待网络可用
可能无法瞬间获取网络,因为手表的 WLAN 或移动数据网络无线装置可能处于关闭状态以节省电池电量。此外,如果手表无法连接至网络,则不会调用 NetworkCallback 实例的 onAvailable() 方法。因此,您应在预先确定的时间长度过后将请求设为超时并释放所有关联资源。
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
}
}
}
}
- 监控网络状态
NetworkCallback 接口提供了一些方法来监控绑定网络的状态变化,如带宽变化和失去连接。
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
}
}
- 启动 WLAN 设置 Activity
请求 WLAN 网络时,如果已配置保存的网络且该网络在范围之内,系统会尝试连接到该网络。不过,如果没有保存的 WLAN 网络可用,则绝不会调用 NetworkCallback 实例的 onAvailable() 回调方法。如果您使用 Handler 将网络请求设置为超时,在发生超时后,您可以引导用户添加 WLAN 网络。您可以使用以下 intent 直接将用户带到用于添加 WLAN 网络的 Activity:
context.startActivity(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 网络。
云消息传递
对于发送通知,应用可以直接使用 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_ANY
或NETWORK_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 命令进行后续调用并使用同一值创建后续作业,将更新现有作业:
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)
}
下面是用于处理上述作业的 JobService 实现。执行作业时,会将一个 JobParameters
对象传入 onStartJob
方法。通过该 JobParameters
对象,您可以获得作业 ID 值以及在调度作业时提供的任何 extra 捆绑包。onStartJob
方法在主应用线程上进行调用,因此任何开销大的逻辑都应从单独的线程运行。在本例中,使用了 AsyncTask
在后台运行代码。工作完成后,您应调用 jobFinished
方法以通知 JobScheduler
任务已完成:
private class MyJobService : JobService() {
override fun onStartJob(params: JobParameters): Boolean {
JobAsyncTask().execute(params)
return true
}
private class JobAsyncTask : AsyncTask<...>() { ... }
}
其他更多内容,请参考Google文档。