Android Oreo--Notifications


Android Oreo出来一段时间了,提供了很多新特性,比如通知栏样式、自动填充、电池寿命优化、自动调整TextView、XML中使用字体、可下载的字体和表情(有两篇文章推荐Android Oreo可下载字体android开发排版指南)、自适应图标、快捷方式固定、应用程序的宽色域颜色、WebView功能增强、Java 8语言API和运行时优化,等等。
本文介绍Oreo 8.0对通知栏带来了的变化以及用户体验。

1,通知渠道

作为android开发者,肯定能很熟悉地写出弹出通知栏的代码:

var id = 1000
private fun showNotification() {
    var message = "message:${Math.random()}"
    var mNotificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager//初始化要用到的系统服务
    var mBuilder = NotificationCompat.Builder(this)
    mBuilder.setContentTitle(getString(R.string.app_name))
            .setContentText("内容")
//                .setContentIntent(getDefalutIntent(Notification.FLAG_AUTO_CANCEL))
            //              .setNumber(number)//显示数量
            .setTicker("测试通知来啦")//通知首次出现在通知栏,带上升动画效果的
            .setWhen(System.currentTimeMillis())//通知产生的时间,会在通知信息里显示
            .setPriority(Notification.PRIORITY_MAX)//设置该通知优先级
            .setAutoCancel(true)//设置这个标志当用户单击面板就可以让通知将自动取消
            .setOngoing(false)//ture,设置他为一个正在进行的通知。他们通常是用来表示一个后台任务,用户积极参与(如播放音乐)或以某种方式正在等待,因此占用设备(如一个文件下载,同步操作,主动网络连接)
            //                .setDefaults(Notification.DEFAULT_VIBRATE)//向通知添加声音、闪灯和振动效果的最简单、最一致的方式是使用当前的用户默认设置,使用defaults属性,可以组合:
            //Notification.DEFAULT_ALL  Notification.DEFAULT_SOUND 添加声音 // requires VIBRATE permission
            .setSmallIcon(R.mipmap.icon_anim_plane_1)
    mBuilder.setAutoCancel(true)//点击后让通知将消失
            .setContentTitle(getString(R.string.app_name))
            .setContentText(message)
            .setTicker(message)
    mNotificationManager.notify(id, mBuilder.build())
}

这是Oreo 8.0 (API 26)之前规范的写法,但是这种写法在Oreo 8.0手机上不能唤起通知栏,而且NotificationCompat.Builder(Context context)方法已经被标记为deprecated,取而代之的是NotificationCompat.Builder(Context context, String channelId)。可以看到,构造器添加了一个新的参数String channelId,这个是是渠道ID,每个通知都必须有一个渠道ID,其允许您为要显示的每种通知类型创建用户可自定义的渠道。用户界面将通知渠道称之为通知类别。

//NotificationBuilder.kt
@TargetApi(Build.VERSION_CODES.O)
class NotificationBuilder(
        private val context: Context,
        private val safeContext: Context = context.safeContext(),
        private val notificationManager: NotificationManagerCompat = NotificationManagerCompat.from(safeContext),
        private val channelBuilder: NotificationChannelBuilder = NotificationChannelBuilder(context, CHANNEL_IDS),
        private val random: Random = Random()
) {

    private var notificationId: Int = 62//by bindSharedPreference(context, KEY_NOTIFICATION_ID, 0)

    fun sendBundledNotification(message: Message) =
            with(notificationManager) {
                channelBuilder.ensureChannelsExist(createChannel)
                randomChannelId.also {
                    notify(notificationId++, buildNotification(message, it))
                    notify(SUMMARY_ID, buildSummary(message, it))
                }
            }

    private val randomChannelId
        get() = CHANNEL_IDS[random.nextInt(CHANNEL_IDS.size)]

    private val createChannel: (channelId: String) -> NotificationChannel? = { channelId ->
        when (channelId) {
            IMPORTANT_CHANNEL_ID -> NotificationChannel(channelId,
                    context.getString(R.string.important_channel_name),
                    NotificationManager.IMPORTANCE_HIGH)
                    .apply {
                        description = context.getString(R.string.important_channel_description)
                    }
            NORMAL_CHANNEL_ID -> NotificationChannel(channelId,
                    context.getString(R.string.normal_channel_name),
                    NotificationManager.IMPORTANCE_DEFAULT)
                    .apply {
                        description = context.getString(R.string.normal_channel_description)
                    }
            LOW_CHANNEL_ID -> NotificationChannel(channelId,
                    context.getString(R.string.low_channel_name),
                    NotificationManager.IMPORTANCE_LOW)
                    .apply {
                        description = context.getString(R.string.low_channel_description)
                    }
            else -> null
        }
    }

    private fun buildNotification(message: Message, channelId: String): Notification =
            with(NotificationCompat.Builder(context, channelId)) {
                message.apply {
                    setContentTitle(sender)
                    setContentText(text)
                    setWhen(timestamp?.toEpochMilli() ?: System.currentTimeMillis())
                }
                setSmallIcon(getIconId(channelId))
                setShowWhen(true)
                setGroup(GROUP_KEY)
                build()
            }

    private fun getIconId(channelId: String) =
            when (channelId) {
                IMPORTANT_CHANNEL_ID -> R.drawable.ic_important
                LOW_CHANNEL_ID -> R.drawable.ic_low
                else -> R.drawable.ic_message
            }

    private fun buildSummary(message: Message, channelId: String): Notification =
            with(NotificationCompat.Builder(context, channelId)) {
                setContentTitle(SUMMARY_TITLE)
                setContentText(SUMMARY_TEXT)
                setWhen(message.timestamp?.toEpochMilli() ?: System.currentTimeMillis())
                setSmallIcon(R.drawable.ic_message)
                setShowWhen(true)
                setGroup(GROUP_KEY)
                setGroupSummary(true)
                build()
            }

    companion object {
        private const val KEY_NOTIFICATION_ID = "KEY_NOTIFICATION_ID"
        private const val GROUP_KEY = "Messenger"
        private const val SUMMARY_ID = 0
        private const val SUMMARY_TITLE = "Nougat Messenger"
        private const val SUMMARY_TEXT = "You have unread messages"
        private const val IMPORTANT_CHANNEL_ID = "IMPORTANT_CHANNEL_ID"
        private const val NORMAL_CHANNEL_ID = "NORMAL_CHANNEL_ID"
        private const val LOW_CHANNEL_ID = "LOW_CHANNEL_ID"
        private val CHANNEL_IDS =
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                    listOf(IMPORTANT_CHANNEL_ID, NORMAL_CHANNEL_ID, LOW_CHANNEL_ID)
                } else {
                    listOf(NORMAL_CHANNEL_ID)
                }
    }
}

//NotificationChannelBuilder.kt
class NotificationChannelBuilder(
        context: Context,
        private val channelIds: List<String>,
        private val notificationManager: NotificationManager =
        context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
) {

    fun ensureChannelsExist(createChannel: (channelId: String) -> NotificationChannel?) =
            ifAtLeast(Build.VERSION_CODES.O) {
                notificationManager.ensureChannelsExist(createChannel)
            }

    @TargetApi(Build.VERSION_CODES.O)
    private fun NotificationManager.ensureChannelsExist(createChannel: (channelId: String) -> NotificationChannel?) {
        channelIds
                .filter { !notificationChannelIds().contains(it) }
                .forEach {
                    createChannel(it)?.also {
                        notificationManager.createNotificationChannel(it)
                    }
                }
    }

    @TargetApi(Build.VERSION_CODES.O)
    private fun NotificationManager.notificationChannelIds() =
            notificationChannels.map { it.id }

}

上面代码有点长,其实和API 26之前的写法差别大概只有三点:1,NotificationCompat.Builder使用了新的带channelId参数的构造器,分别在函数buildNotification()和函数buildSummary()中调用;2,根据channelId设置不同的smallIcon,参考方法getIconId();3,有了一个新的调用ChannelBuilder.ensureChannelsExist,它检查不存在的channelId并创建相应的渠道。

通知渠道提供了一些属性:id(唯一标识)、name(名字)、importance(重要性)、description(描述)、sound(声音)、light(光)、vibrate(震动)、LockscreenVisibility(锁屏可见性)、bypassDnd(免打扰)、showBadge(类似iOS的3DTouch)等等。

通过创建渠道组可以对渠道进行分组,调用 setGroup()方法将某个渠道关联到某个渠道组。需要注意的是,只能在将渠道提交给通知管理器NotificationManager之前修改渠道与渠道组之间的关联,这所以因为NotificationManager调用createNotificationChannel()方法之后,再对NotificationChannel进行的任何修改都将无效。代码如下:

// 通知渠道组的id.
val group = "channel_group_01"
// 用户可见的通知渠道组名称.
var name = "name"
notificationManager.createNotificationChannelGroup(NotificationChannelGroup(group, name))

如果长按一条通知栏,会出现它的渠道名字和其它相关信息,如图所示。



点击“ALL CATEGORIES”按钮进入渠道相关属性设置页面。
使用新的API创建通知栏基本就这么多,貌似东西也不是很多哦。

2,通知标志

Android 8.0中,长按app的启动图标会显示通知标志,有点类似iOS的3DTouch。只有通知渠道设置属性setShowBadge(true)(默认是true)的时候,通知标志才会起作用。通知标志可反映某个应用是否存在与其关联、并且用户尚未予以清除也未对其采取行动的通知。通知标志也称为通知点。


3,休眠

用户可以将通知置于休眠状态,以便稍后重新显示它。重新显示时通知的重要程度与首次显示时相同。应用可以移除或更新已休眠的通知,但更新休眠的通知并不会使其重新显示。操作方式是左滑或者右滑,这时会出现两个按钮:时钟按钮和设置按钮,点击时钟按钮。

4,通知超时

现在,使用 setTimeoutAfter() 创建通知时您可以设置超时。您可以使用此函数指定一个持续时间,超过该持续时间后,通知应取消。如果需要,您可以在指定的超时持续时间之前取消通知。

5,通知设置

当您使用Notification.INTENT_CATEGORY_NOTIFICATION_PREFERENCESIntent 从通知创建指向应用通知设置的链接时,您可以调用 setSettingsText() 来设置要显示的文本。此系统可以提供以下 Extra 数据和 Intent,用于过滤应用必须向用户显示的设置:EXTRA_CHANNEL_ID、NOTIFICATION_TAG 和 NOTIFICATION_ID。

6,通知清除

系统现在可区分通知是由用户清除,还是由应用移除。要查看清除通知的方式,您应实现 NotificationListenerService 类的新 onNotificationRemoved() 函数。

7,背景颜色

您现在可以设置和启用通知的背景颜色。只能在用户必须一眼就能看到的持续任务的通知中使用此功能。例如,您可以为与驾车路线或正在进行的通话有关的通知设置背景颜色。您还可以使用 Notification.Builder.setColor() 设置所需的背景颜色。这样做将允许您使用 Notification.Builder.setColorized() 启用通知的背景颜色设置。

8,消息样式

现在,使用 MessagingStyle 类的通知可在其折叠形式中显示更多内容。对于与消息有关的通知,您应使用 MessagingStyle 类。您还可以使用新的 addHistoricMessage() 函数,通过向与消息相关的通知添加历史消息为会话提供上下文。

参考文献:

  1. https://blog.stylingandroid.com/oreo-notifications-channels-part-1/
  2. https://blog.stylingandroid.com/oreo-notifications-channels-part-2/
  3. https://developer.android.com/reference/android/app/NotificationChannel.html
  4. https://www.androidauthority.com/android-8-0-oreo-app-implementing-notification-channels-801097/
  5. https://developer.android.com/about/versions/oreo/index.html
  6. http://www.jianshu.com/p/92afa56aee05
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 173,687评论 25 708
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 135,026评论 19 139
  • 原文出处: http://www.androidchina.net/6174.html Notification在...
    木木00阅读 12,398评论 3 32
  • 补第十六次作业 厉害的妈妈会养出什么样的孩子 孩子的性格其实就是妈妈的体现,强势和太过自我的妈妈,孩子难免会自...
    墨行者阅读 237评论 0 1
  • Java 1.5引入了自动装箱和自动拆箱。这在有些时候很方便,但是要谨慎选择。 它们的三个主要区别: 基本数据类型...
    DrunkPian0阅读 263评论 0 0