在 Wear OS 上创建通知

本页介绍了如何为手表创建通知,还介绍了如何为从关联手机桥接到手表的每条通知提供附加内容。

手表上的通知采用的结构与手机上的通知相同。 此外,为了给用户提供最佳体验,Wear OS by Google 谷歌还提供了用于向通知中添加穿戴式设备专属功能的 API。

从应用发出通知时,每条通知都显示为通知流中的一张卡片。

图 1. 在手机和手表上显示的同一通知。

手表和手机都可以作为通知的来源。您可以使用 NotificationCompat.Builder 类为穿戴式设备创建通知。当您使用此类构建通知时,系统负责正确显示通知。

注意:使用 RemoteViews 的通知的自定义布局会被去掉,穿戴式设备仅显示文本和图标。但是,您可以通过创建在手表上运行的穿戴式设备应用,创建使用自定义卡片布局的自定义通知

通常,所有通知均从手机桥接至手表。但是,在以下情况下,系统不会桥接通知:

导入必要的类

要导入必要的软件包,请将以下代码行添加到 build.gradle 文件中:

    implementation 'androidx.core:core:1.2.0'
    

现在您的项目可以访问必要的软件包,接下来从支持库导入必要的类:

Kotlin

    import androidx.core.app.NotificationCompat
    import androidx.core.app.NotificationManagerCompat
    import androidx.core.app.NotificationCompat.WearableExtender
    

Java

    import androidx.core.app.NotificationCompat;
    import androidx.core.app.NotificationManagerCompat;
    import androidx.core.app.NotificationCompat.WearableExtender;
    

使用通知构建器创建通知

借助 Jetpack 核心库,您可以使用操作按钮和图标等最新通知功能创建通知,同时保持与 Android 1.6(API 级别 4)及更高版本的兼容性。

注意:从 Android 8.0(API 级别 26)开始,您需要为要显示的每种类型的通知创建通知渠道

如需创建通知,请执行以下操作:

  1. 创建一个 NotificationCompat.Builder 实例。

    Kotlin

        val notificationId = 1
        // The channel ID of the notification.
        val id = "my_channel_01"
        // Build intent for notification content
        val viewPendingIntent = Intent(this, ViewEventActivity::class.java).let { viewIntent ->
            viewIntent.putExtra(EXTRA_EVENT_ID, eventId)
            PendingIntent.getActivity(this, 0, viewIntent, 0)
        }
    
        // Notification channel ID is ignored for Android 7.1.1
        // (API level 25) and lower.
        val notificationBuilder = NotificationCompat.Builder(this, id)
                .setSmallIcon(R.drawable.ic_event)
                .setContentTitle(eventTitle)
                .setContentText(eventLocation)
                .setContentIntent(viewPendingIntent)
        

    Java

        int notificationId = 001;
        // The channel ID of the notification.
        String id = "my_channel_01";
        // Build intent for notification content
        Intent viewIntent = new Intent(this, ViewEventActivity.class);
        viewIntent.putExtra(EXTRA_EVENT_ID, eventId);
        PendingIntent viewPendingIntent =
                PendingIntent.getActivity(this, 0, viewIntent, 0);
    
        // Notification channel ID is ignored for Android 7.1.1
        // (API level 25) and lower.
        NotificationCompat.Builder notificationBuilder =
            new NotificationCompat.Builder(this, id)
                .setSmallIcon(R.drawable.ic_event)
                .setContentTitle(eventTitle)
                .setContentText(eventLocation)
                .setContentIntent(viewPendingIntent);
        
  2. 通过将带通知 ID 的 Notification 对象传递给 notify() 以发出通知。

    Kotlin

        NotificationManagerCompat.from(this).apply {
            notify(notificationId, notificationBuilder.build())
        }
        

    Java

        // Get an instance of the NotificationManager service
        NotificationManagerCompat notificationManager =
                NotificationManagerCompat.from(this);
    
        // Issue the notification with notification manager.
        notificationManager.notify(notificationId, notificationBuilder.build());
        

当该通知显示在手机上时,用户可以轻触通知,以调用由 setContentIntent() 方法指定的 PendingIntent。当此通知出现在穿戴式设备上时,它会出现在通知流中。对于桥接通知,用户可以点击通知以查看展开式通知并触发任何定义的操作,如打开操作。通常,这些操作会打开您的手机应用上的某个 Activity

构建展开式通知

展开式通知可以为每条通知提供大量的附加内容和操作。 如果您为通知指定附加内容页面和操作,这些页面和操作会在展开式通知中呈现给用户。每条展开式通知都遵循适用于 Wear OS 的 Material Design 规范,因此用户会获得类似应用的体验。

如果展开式通知中的第一项操作具有 RemoteInput(例如,回复操作),您使用 setChoices() 设置的选项会出现在展开式通知中的第一项操作下。

当符合以下任一条件时,用户可以点按通知以查看展开式通知:

  • 通知由配对手机上的应用生成并桥接到 Wear。
  • 通知没有 contentIntent

注意:仅当您展开通知时,才会显示使用 setColor() 方法为通知设置的应用专属背景颜色。

展开式通知最佳做法

如需决定何时使用展开式通知,请遵循以下准则:

  • 从配对手机桥接到 Wear 设备的所有通知都使用展开式通知。
  • 如果通知是由在 Wear 设备上本地运行的应用生成的,就应通过调用 setContentIntent()让通知的轻触目标在您的应用内启动带通知 ID 的 Notification 对象。我们建议您不要对由在 Wear 设备上本地运行的应用生成的通知使用展开式通知。

添加展开式通知

通过展开式通知,您可以为通知添加附加内容和操作。您可以选择应用通知所提供信息的详细程度;但是,请慎重考虑要在通知中包含多少详细信息。

添加附加内容

如需在展开式通知中显示附加文本,请使用 BigTextStyle

如需在展开式通知中添加图片,可以使用 BigPictureStyle。如果要在展开式通知中添加多张图片,请使用 addPage() 方法以及 BigPictureStyle

主要操作

展开式通知将包含一项主要操作,除非使用 setContentAction() 指定了其他操作,否则该操作将是通知中的第一项操作。

附加操作

如需指定附加操作,请使用 addAction()addActions()。展开式通知的操作抽屉式导航栏包含所有可执行的操作。

添加通知操作

除由 setContentIntent() 定义的主要内容操作之外,您还可以通过将 PendingIntent 传递给 addAction() 方法以添加其他操作。

例如,以下代码展示的通知类型与上述通知相同,但添加了一项用于在地图上查看事件地点的操作。

Kotlin

    // Build an intent for an action to view a map
    val mapIntent = Intent(Intent.ACTION_VIEW)
    // The channel ID of the notification.
    val id = "my_channel_01"
    val mapPendingIntent = Intent(Intent.ACTION_VIEW).let { mapIntent ->
        mapIntent.data = Uri.parse("geo:0,0?q=" + Uri.encode(location))
        PendingIntent.getActivity(this, 0, mapIntent, 0)
    }

    val notificationBuilder = NotificationCompat.Builder(this, id)
            .setSmallIcon(R.drawable.ic_event)
            .setContentTitle(eventTitle)
            .setContentText(eventLocation)
            .setContentIntent(viewPendingIntent)
            .addAction(R.drawable.ic_map, getString(R.string.map), mapPendingIntent)
    

Java

    // Build an intent for an action to view a map
    Intent mapIntent = new Intent(Intent.ACTION_VIEW);
    // The channel ID of the notification.
    String id = "my_channel_01";
    Uri geoUri = Uri.parse("geo:0,0?q=" + Uri.encode(location));
    mapIntent.setData(geoUri);
    PendingIntent mapPendingIntent =
            PendingIntent.getActivity(this, 0, mapIntent, 0);

    NotificationCompat.Builder notificationBuilder =
            new NotificationCompat.Builder(this, id)
            .setSmallIcon(R.drawable.ic_event)
            .setContentTitle(eventTitle)
            .setContentText(eventLocation)
            .setContentIntent(viewPendingIntent)
            .addAction(R.drawable.ic_map,
                    getString(R.string.map), mapPendingIntent);
    

在手机上,操作以附加到通知的附加按钮的形式显示。在穿戴式设备上,操作出现在展开式通知中的内容文本后面。当用户点按操作时,系统会调用手机上的关联 intent。

提示:如果通知包含“回复”操作(例如,对于短信应用),您可以启用直接从穿戴式设备进行语音输入回复的功能,从而增强这一行为。如需了解详情,请参阅将语音输入添加为通知操作

添加内嵌操作

通过内嵌操作,用户可以从通知流卡片中对通知执行操作。在 Wear 设备上,内嵌操作以附加按钮的形式显示在通知底部。

内嵌操作是可选的,但如果用户在查看通知流卡片中的内容后可能会对通知执行操作(而不转到展开式通知),建议添加该操作。例如,适合在通知中添加内嵌操作的用例包括:回复短信、停止健身运动或归档电子邮件。

一条通知只能提供一项内嵌操作。要将内嵌操作显示为通知中的附加按钮,请将 setHintDisplayActionInline() 方法设为 true。当用户点按内嵌操作时,系统会调用您在通知操作中指定的 Intent。

以下代码段添加了说明以内嵌方式显示某项操作的提示,并使用 addAction 方法将该内嵌操作添加到通知中。

Kotlin

    //Wear OS requires a hint to display the reply action inline.
    val actionExtender = NotificationCompat.Action.WearableExtender()
            .setHintLaunchesActivity(true)
            .setHintDisplayActionInline(true)
    wearableExtender.addAction(actionBuilder.extend(actionExtender).build())
    

Java

    //Wear OS requires a hint to display the reply action inline.
    Action.WearableExtender actionExtender =
        new Action.WearableExtender()
            .setHintLaunchesActivity(true)
            .setHintDisplayActionInline(true);
    wearableExtender.addAction(actionBuilder.extend(actionExtender).build());
    

向通知中添加穿戴式设备专属功能

如果您需要向通知中添加穿戴式设备专属功能(如在穿戴式设备通知中隐藏应用图标,或让用户使用语音输入口述文本回复),可以使用 NotificationCompat.WearableExtender 类指定选项。如需使用此 API,请执行以下操作:

  1. 创建一个 WearableExtender 实例,为通知设置穿戴式设备专属选项。
  2. 创建一个 NotificationCompat.Builder 实例,为通知设置所需属性,如本课程前面所述。
  3. 对通知调用 extend() 并传入 WearableExtender。该操作会将穿戴式设备选项应用于通知。
  4. 调用 build() 以构建通知。

注意:如果您使用框架的 NotificationManagerNotificationCompat.WearableExtender 的某些功能将不起作用,因此请务必使用 NotificationCompat

您可以在用户的设备之间同步通知的关闭(取消)行为。要同步关闭行为,请使用 setDismissalId() 方法。当您调用 setDismissalId() 方法时,为每条通知传递一个字符串形式的全局唯一 ID。相应通知关闭后,手表和配套手机上的其他所有具有同一关闭 ID 的通知也会关闭。如需检索关闭 ID,请使用 getDismissalId()

注意:Wear 2.0 不支持 setBackground() 方法。您可以对包含图片的通知使用 NotificationCompat.BigPictureStyle

Wear 2.0 也不支持 setHintHideIcon() 方法。

指定仅限穿戴式设备的操作

如果您希望穿戴式设备上提供的操作不同于手机上的操作,请使用 WearableExtender.addAction()。您使用该函数添加操作后,穿戴式设备不会显示任何其他使用 NotificationCompat.Builder.addAction() 添加的操作。使用 WearableExtender.addAction() 添加的操作只会出现在穿戴式设备上,而不会出现在手机上。

将语音输入添加为通知操作

语音操作是穿戴式设备体验的重要组成部分。要创建支持语音输入的操作,请创建一个可添加到通知操作的 RemoteInput.Builder 实例。 此类的构造函数接受一个字符串,系统将该字符串用作语音输入的键,您稍后将使用该字符串检索手机应用中的输入文本。

例如,以下代码段展示了如何创建一个为语音输入提示提供自定义标签的 RemoteInput 对象:

Kotlin

    // Key for the string that's delivered in the action's intent
    const val EXTRA_VOICE_REPLY = "extra_voice_reply"
    ...
    val remoteInput = resources.getString(R.string.reply_label).let { replyLabel ->
        RemoteInput.Builder(EXTRA_VOICE_REPLY)
                .setLabel(replyLabel)
                .build()
    }
    

Java

    // Key for the string that's delivered in the action's intent
    private static final String EXTRA_VOICE_REPLY = "extra_voice_reply";

    String replyLabel = getResources().getString(R.string.reply_label);

    RemoteInput remoteInput = new RemoteInput.Builder(EXTRA_VOICE_REPLY)
            .setLabel(replyLabel)
            .build();
    

添加预定义的文本回复

除允许语音输入之外,您还可以提供多达五条文本回复,以供用户快速回复。您可以调用 setChoices() 并向其传递一个字符串数组。

例如,您可以在资源数组中定义一些回复:

res/values/strings.xml

    <?xml version="1.0" encoding="utf-8"?>
    <resources>
        <string-array name="reply_choices">
            <item>Yes</item>
            <item>No</item>
            <item>Maybe</item>
        </string-array>
    </resources>
    

然后,扩充该字符串数组,并将其添加到 RemoteInput 中:

Kotlin

    // Key for the string that's delivered in the action's intent
    const val EXTRA_VOICE_REPLY = "extra_voice_reply"
    ...
    val remoteInput = resources.getString(R.string.reply_label).let { replyLabel ->
        resources.getStringArray(R.array.reply_choices).let { replyChoices ->
            RemoteInput.Builder(EXTRA_VOICE_REPLY)
                    .setLabel(replyLabel)
                    .setChoices(replyChoices)
                    .build()
        }
    }
    

Java

    public static final String EXTRA_VOICE_REPLY = "extra_voice_reply";
    ...
    String replyLabel = getResources().getString(R.string.reply_label);
    String[] replyChoices = getResources().getStringArray(R.array.reply_choices);

    RemoteInput remoteInput = new RemoteInput.Builder(EXTRA_VOICE_REPLY)
            .setLabel(replyLabel)
            .setChoices(replyChoices)
            .build();
    

接收字符串形式的语音输入

如需在您于回复操作的 Intent 中声明的 Activity 中接收用户的转录消息,请调用 getResultsFromIntent(),传入“回复”操作的 Intent。此方法将返回一个包含文本回复 Bundle。然后,您可以查询该 Bundle 以获取回复。

注意:请勿使用 Intent.getExtras() 获取语音结果,因为语音输入以 ClipData 形式存储。getResultsFromIntent() 方法提供了一种便捷的方式,让您可以接收字符序列,而不必自行处理 ClipData

以下代码展示的方法接受一个 Intent 并返回语音回复,该语音回复由前面示例中使用的 EXTRA_VOICE_REPLY 键引用:

Kotlin

    /**
     * Obtain the intent that started this activity by calling
     * Activity.getIntent() and pass it into this method to
     * get the associated voice input string.
     */

    private fun getMessageText(intent: Intent): CharSequence? =
            RemoteInput.getResultsFromIntent(intent)?.run {
                getCharSequence(EXTRA_VOICE_REPLY)
            }
    

Java

    /**
     * Obtain the intent that started this activity by calling
     * Activity.getIntent() and pass it into this method to
     * get the associated voice input string.
     */

    private CharSequence getMessageText(Intent intent) {
        Bundle remoteInput = RemoteInput.getResultsFromIntent(intent);
        if (remoteInput != null) {
            return remoteInput.getCharSequence(EXTRA_VOICE_REPLY);
        }
        return null;
    }
    

Kotlin

    // Create an intent for the reply action
    val actionPendingIntent = Intent(this, ActionActivity::class.java).let { actionIntent ->
        PendingIntent.getActivity(this, 0, actionIntent,
                PendingIntent.FLAG_UPDATE_CURRENT)
    }
    // Create the action
    val action = NotificationCompat.Action.Builder(
            R.drawable.ic_action,
            getString(R.string.label),
            actionPendingIntent
    ).build()

    // Build the notification and add the action via WearableExtender
    var notification = NotificationCompat.Builder(context)
            .setSmallIcon(R.drawable.ic_message)
            .setContentTitle(getString(R.string.title))
            .setContentText(getString(R.string.content))
            .extend(NotificationCompat.WearableExtender().addAction(action))
            .build()
    

Java

    // Create an intent for the reply action
    Intent actionIntent = new Intent(this, ActionActivity.class);
    PendingIntent actionPendingIntent =
            PendingIntent.getActivity(this, 0, actionIntent,
                    PendingIntent.FLAG_UPDATE_CURRENT);

    // Create the action
    NotificationCompat.Action action =
            new NotificationCompat.Action.Builder(R.drawable.ic_action,
                    getString(R.string.label), actionPendingIntent)
                    .build();

    // Build the notification and add the action via WearableExtender
    Notification notification =
            new NotificationCompat.Builder(context)
                    .setSmallIcon(R.drawable.ic_message)
                    .setContentTitle(getString(R.string.title))
                    .setContentText(getString(R.string.content))
                    .extend(new WearableExtender().addAction(action))
                    .build();
    

从独立穿戴式设备应用启动通知

从独立手表应用创建通知与创建桥接通知没有什么不同。来自独立 Wear 应用的通知与桥接通知看起来相似,但它们的行为略有差异。如果未设置 contentIntent 或者通知从配对手机桥接而来,则点按通知会打开展开式通知。 然而,如果通知来自独立手表应用,点按通知会触发 contentIntent 以打开您的 Wear 应用。如需了解如何从独立应用创建通知并模拟展开式通知的行为,请参阅 Wear 通知示例。

默认情况下,通知从配对手机上的应用桥接到配对手表。如果您构建了独立手表应用,并且还有配套手机应用,这些应用可能会创建重复的通知。如需了解如何处理重复通知的问题,请参阅通知的桥接模式