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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,634评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,951评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,427评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,770评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,835评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,799评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,768评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,544评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,979评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,271评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,427评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,121评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,756评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,375评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,579评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,410评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,315评论 2 352

推荐阅读更多精彩内容

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