Jetpack Compose中使用Notification

Posted 川峰

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Jetpack Compose中使用Notification相关的知识,希望对你有一定的参考价值。


发送通知相关的主要有两个关键的类 NotificationCompat.BuilderNotificationManagerCompat
为方便使用,首先定义一个扩展工具类来管理通知

const val MAIN_CHANNEL_ID = "MainChannel ID"
const val MAIN_CHANNEL = "MainChannel"

fun Context.buildNotification(
    id: Int,
    title: String,
    message: String,
    action: String? = null,
    actionMessage: String? = null,
    visibility: Int = VISIBILITY_PUBLIC,
    activityIntent: Intent? = null,
    isDeepLink: Boolean = false
): Notification 
    val notification = Notification(id, title, message, action, actionMessage,
        visibility, activityIntent,isDeepLink)
    notification.builder = notification.builder(this)
    notification.manager = getNotificationManager()
   return notification


data class Notification(
    val id: Int,
    var title: String,
    var message: String,
    var action: String? = null,
    var actionMessage: String? = null,
    var visibility: Int = VISIBILITY_PUBLIC,
    var activityIntent: Intent? = null,
    val isDeepLink: Boolean = false,
    var builder: Builder? = null,
    var manager: NotificationManagerCompat? = null
)

fun Notification.show(): Notification 
    builder?.let  manager?.notify(id, it.build()) 
    return this


fun Notification.update(
    context: Context,
    titleNew: String? = null,
    messageNew: String? = null,
    action1: String? = null,
    visibleType: Int = VISIBILITY_PUBLIC
): Notification  
    titleNew?.let  title = titleNew 
    messageNew?.let  message = messageNew
    action1?.let  action = action1  
    if (visibleType != visibility) visibility = visibleType
    manager?.notify(id, builder(context).build())
    return this


fun Notification.builder(context: Context): Builder 
    val builder = Builder(context, MAIN_CHANNEL_ID)
        .setContentTitle(title)
        .setContentText(message)
        .setSmallIcon(R.mipmap.ic_launcher)
        .setLargeIcon(context.bitmap(R.drawable.ic_head3,200, 200))
        .setPriority(PRIORITY_DEFAULT)
        .setVisibility(visibility)
        .setAutoCancel(true)
    if (visibility == VISIBILITY_PRIVATE) 
        builder.setPublicVersion(
            Builder(context, MAIN_CHANNEL_ID)
                .setContentTitle("收到一条新的消息")
                .setContentText("请解锁屏幕后查看!")
                .build()
        )
    
    val flg = if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) PendingIntent.FLAG_IMMUTABLE else 0
    action?.let 
        val intent = Intent(context, NotificationReceiver::class.java).apply 
            putExtra(KEY_MESSAGE, actionMessage)
            putExtra(KEY_NOTIFICATION_ID, id)
        
        PendingIntent.getBroadcast(context, 0, intent, flg)
    ?.let  builder.addAction(0, action, it) 

    if (isDeepLink) 
        activityIntent?.let 
            TaskStackBuilder.create(context).run 
                addNextIntentWithParentStack(it)
                getPendingIntent(1, flg)
            
        ?.let  builder.setContentIntent(it) 
     else 
        activityIntent?.let  PendingIntent.getActivity(context, 1, it, flg) 
            ?.let  builder.setContentIntent(it) 
    
    return builder


fun Context.getNotificationManager(): NotificationManagerCompat 
    val notificationManager = NotificationManagerCompat.from(applicationContext)
    // API 26 android 8.0开始必须为每个通知指定一个channel才会显示
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) 
        val channel = NotificationChannel(MAIN_CHANNEL_ID, MAIN_CHANNEL,
            NotificationManager.IMPORTANCE_DEFAULT
        )
        notificationManager.createNotificationChannel(channel)
    
    return notificationManager


fun Context.cancelNotification(id: Int) = getNotificationManager().cancel(id)

然后定义一个ViewModel,在其中负责具体的发送通知业务


class NotificationTestViewModel: ViewModel() 
    var notification: Notification? = null

    private fun buildNotification(context: Context, title: String, message: String) 
        val clickIntent = Intent(context, NotificationTestActivity::class.java)
        notification = context.buildNotification(
            id = 1,
            title = title,
            message = message,
            action = "Action按钮",
            actionMessage = "点击了按钮",
            visibility = VISIBILITY_PUBLIC,
            activityIntent = clickIntent,
        )
    

    fun showNotification(context: Context, title: String, message: String) 
        buildNotification(context, title, message)
        notification?.show()
    

    fun updateNotification(context: Context, titleNew: String, messageNew: String) 
        notification?.update(context, titleNew, messageNew)
    

最后在Composable中调用viewmodel的方法发送通知:

@Composable
fun NotificationTest(viewModel: NotificationTestViewModel = viewModel()) 
    val context = LocalContext.current
    Column(
        modifier = Modifier.fillMaxSize(),
        horizontalAlignment = Alignment.CenterHorizontally
    ) 
        Button(onClick = 
            viewModel.showNotification(context,"外卖提醒", "您好,您的外卖到了!")
        ) 
            Text(text = "创建一个新通知")
        
        Button(onClick = 
            viewModel.updateNotification(context,"订单提醒", "您有一条新的外卖订单,请及时接单!")
        ) 
            Text(text = "更新通知")
        
    

更新通知

发送通知后通过NotificationManagerCompat.notify(id, notification)对相同的通知id进行再次调用,就会更新通知中对应的属性

通知的可见性

通知有三种可见性规则,分别是:

  • NotificationCompat.VISIBILITY_PUBLIC: 默认所有屏幕可见
  • NotificationCompat.VISIBILITY_SECRET: 锁屏下不可见
  • NotificationCompat.VISIBILITY_PRIVATE:锁屏可见,但是隐藏敏感或私人信息

VISIBILITY_PUBLIC的效果:

VISIBILITY_SECRET的效果:

VISIBILITY_PRIVATE的效果:

要测试 VISIBILITY_PRIVATE的效果需要先将系统设置中通知管理里的锁屏显示敏感信息的选项关闭:

然后在创建 NotificationCompat.Builder 时,需要通过 setPublicVersion 设置在锁屏界面时展示的信息:

  if (visibility == VISIBILITY_PRIVATE) 
        builder.setPublicVersion(
            Builder(context, MAIN_CHANNEL_ID)
                .setContentTitle("收到一条新的消息")
                .setContentText("请解锁屏幕后查看!")
                .build()
        )
    

通知中的Action

通知中最多可以添加三个Action按钮,点击时,可以执行对应的pendingIntent,比如在前面的代码中,构建 NotificationCompat.Builder 时判断如果 Action 按钮的文字不为空,就为builder设置一个action选项:

    val flg = if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) PendingIntent.FLAG_IMMUTABLE else 0
    action?.let 
        val intent = Intent(context, NotificationReceiver::class.java).apply 
            putExtra(KEY_MESSAGE, actionMessage)
            putExtra(KEY_NOTIFICATION_ID, id)
        
        PendingIntent.getBroadcast(context, 0, intent, flg)
    ?.let  builder.addAction(0, action, it) 

这里使用PendingIntent.getBroadcast构建了一个用于触发广播的PendingIntent,builder.addAction的第一个参数还可以设置一个图标的资源id。

当用户点击通知中的action按钮时,就会发送广播,然后在NotificationReceiver中从intent查询信息进行显示即可:

const val KEY_MESSAGE = "Notification_Message"
const val KEY_NOTIFICATION_ID = "Notification_Id"

class NotificationReceiver: BroadcastReceiver() 
    override fun onReceive(context: Context?, intent: Intent?) 
        intent?.run 
            val msg = getStringExtra(KEY_MESSAGE)
            msg?.let  context?.showToast(msg) 
            val id = getIntExtra(KEY_NOTIFICATION_ID, 0)
            context?.cancelNotification(id) // 根据需求决定要不要取消
        
    


这里收到通知后在广播接收器中弹出一个toast提示同时取消了通知,实际业务中可以根据需求决定要不要关闭通知(可能是需要后台常驻的就不要取消)

点击通知跳转具体页面

很简单,就是在构建 NotificationCompat.Builder 时,设置一个Activity类型的PendingIntent即可

val activityIntent= Intent(context, NotificationTestActivity::class.java)
...
activityIntent?.let  PendingIntent.getActivity(context, 1, it, flg) 
            ?.let  builder.setContentIntent(it) 

点击通知后就会跳转的具体的Activity页面

触发DeepLink页面

可以配合Compose导航路由的DeepLink,在点击通知时,跳转到某个导航图中的某个子路由页面中
首先需要配置Compose导航路由的DeepLink,这里使用开源库 compose-destinations 进行配置路由:

// NotificationTest.kt
@Composable
fun NotificationNavHostScreen() 
    Box(modifier = Modifier.fillMaxSize()) 
        DestinationsNavHost(navGraph = NavGraphs.root)  
    


@RootNavGraph(start = true)
@Destination
@Composable
fun NotificationTest(navigator: DestinationsNavigator,
                     viewModel: NotificationTestViewModel = viewModel()) 
    ...

// DetailScreen.kt
const val APP_URI ="http://my.app.com/detail/"

@Destination(deepLinks = [DeepLink(uriPattern = "$APP_URI/message")])
@Composable
fun DetailScreen(message: String) 
 ...

在Activity中使用DestinationsNavHost作为根布局显示

class NotificationMainActivity: ComponentActivity() 
    override fun onCreate(savedInstanceState: Bundle?) 
        super.onCreate(savedInstanceState)
        setContent 
            MyComposeApplicationTheme 
                Surface(Modifier.fillMaxSize(),
                    color=MaterialTheme.colorScheme.background) 
                    NotificationNavHostScreen()
                
            
        
    

然后在构建 NotificationCompat.Builder 时,通过TaskStackBuilder来构建pendingIntent

	if (isDeepLink) 
        activityIntent?.let 
            TaskStackBuilder.create(context).run 
                addNextIntentWithParentStack(it)
                getPendingIntent(1, flg)
            
        ?.let  builder.setContentIntent(it) 
    

NotificationTestViewModel中添加一个专门用于构建Deeplink的方法:

    private fun buildDeepLinkNotification(context: Context, title: String, message: String) 
        val clickIntentDeepLink = Intent(
            Intent.ACTION_VIEW,
            "$APP_URI/message from NotificationTest".toUri(),
            context, NotificationMainActivity::class.java
        )
        notification = context.buildNotification(
            id = 1,
            title = title,
            message = message,
            action = "Action按钮",
            actionMessage = "点击了按钮",
            visibility = VISIBILITY_PUBLIC,
            activityIntent = clickIntentDeepLink,
            isDeepLink = true
        )
    

    fun showNotification(context: Context, title: String, message: String) 
        buildDeepLinkNotification(context, title, message)
        notification?.show()
    

这样就可以了,Manifest中无需为Activity标签配置任何额外的属性
效果如下

在通知中显示进度条

首先在构建NotificationManagerCompat时,需要再添加一个channel2, 并且将其importance参数设置为NotificationManager.IMPORTANCE_LOW(这是因为我们不想在每次更新进度条时都会发出系统提示音)

fun Context.getNotificationManager(): NotificationManagerCompat 
    val notificationManager = NotificationManagerCompat.from(applicationContext)
    // API 26 Android 8.0开始必须为每个通知指定一个channel才会显示
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) 
        val channel = NotificationChannel(MAIN_CHANNEL_ID, MAIN_CHANNEL,
            NotificationManager.IMPORTANCE_DEFAULT
        )
        val channel2 = NotificationChannel(SECOND_CHANNEL_ID, SECOND_CHANNEL,
            NotificationManager.IMPORTANCE_LOW
        )
        notificationManager.createNotificationChannel(channel)
        notificationManager.createNotificationChannel(channel2)
    
    return notificationManager

然后也需要单独为这个channel创建Builder:

fun Notification.secondChannelBuilder(context: Context): Builder 
    return Builder(context, SECOND_CHANNEL_ID) // 这里要使用SECOND_CHANNEL_ID
        .setContentTitle("下载中")
        .setContentText("$progress/$max")
        .setSmallIcon(R.mipmap.ic_launcher)
        .setProgress(max, progress, false)
        .setPriority(PRIORITY_LOW) // 设置低优先级
        .setOngoing(true) // 不允许用户取消

fun Notification.updateProgress(
    context: Context,
    progress: Int,
    max: Int,
): Notification  
    this.progress = progress
    this.max = max
    manager?.notify(id, secondChannelBuilder(context).build())
    return this

fun Notification.showFinished(context: Context): Notification  
    this.title = "下载完成!"
    this.message = ""
    // 下载完后使用MAIN_CHANNEL的builder构建更新通知
    manager?.notify(id, builder(context).apply 
        setContentIntent(null)
        clearActions()
        setProgress(0, 0, false)
    .build())
    return this

在NotificationTestViewModel中添加一个showProgress方法:

fun showProgressJetpack Compose 中使用 Lottie 动画

jetpack compose 接收返回参数

Android Jetpack Compose学习—— Jetpack compose基础布局

Android Jetpack Compose学习—— Jetpack compose基础布局

Android Jetpack Compose学习—— Jetpack compose基础布局

Jetpack Compose中使用Notification