Android foreground service explanation two custom notification view (RemoteViews) and data UI update

Notification supports text content display, vibration, three-color lights, ringtones and other forms of prompts. By default, Notification only displays message title, message content, and delivery time .

1. Update system notification Notification display data

1.1 Creating Notifications

 /**
     * 创建服务通知
     */
    private fun createForegroundNotification(): Notification {
        val builder: NotificationCompat.Builder = NotificationCompat.Builder(applicationContext, notificationChannelId)
        //通知小图标
        builder.setSmallIcon(R.mipmap.ic_launcher_round)
        //通知标题
        builder.setContentTitle("苏宁窖藏")
        //通知内容
        builder.setContentText("苏宁是国内优秀的跨国企业?$count")
        //设置通知显示的时间
        builder.setWhen(System.currentTimeMillis())
        //设定启动的内容
        val  activityIntent: Intent = Intent(this, MainActivity::class.java)
        activityIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
        val pendingIntent: PendingIntent = PendingIntent.getActivity(this,
                1,activityIntent, PendingIntent.FLAG_UPDATE_CURRENT)
        builder.setContentIntent(pendingIntent)
        builder.priority = NotificationCompat.PRIORITY_DEFAULT
        //设置为进行中的通知
        builder.setOngoing(true)

        //创建通知并返回
        return builder.build()
    }

1.2 Update notification display content

  • The notifyId is the unique identifier of the notification. When the notify of the notification is consistent, the original notification content will be overwritten if the notification is issued again; this method is also often used to update the notification content in real time;
  • The method for the foreground service to publish the notification is startForeground() , the usage method is similar to notificationManager.notify() , and there is no need to register the Manager ;

Main thread update function

    private var count: Int = 0;
    private val handler: Handler = Handler(Looper.getMainLooper());
    private val mRunnable: Runnable = object : Runnable {
        override fun run() {
            val notification: Notification = createForegroundNotification()
            //发送通知到状态栏
            val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
            //通知更新UI
//            notificationManager.notify(NOTIFICATION_ID, notification);
            //将服务置于启动状态 ,NOTIFICATION_ID指的是创建的通知的ID
            startForeground(NOTIFICATION_ID, notification)
            count++
            handler.postDelayed(this, 1000)
        }
    }

2. How to remove the close notification

1) By NotificationCompat.Buildersetting setAutoCancel(true), so that when the user clicks on the notification, the notification will be automatically deleted;
2) By the NotificationManager.cancel(id)method, delete the notification with the specified id;
3) By  NotificationManager.cancelAll()the method, delete all the notifications of the application;

3. Notification bar custom style

To customize the notification bar, we will use RemoteViews, which is only supported when the SDK is 16 and above :

    private fun getBigContentView(): RemoteViews{
        return RemoteViews(this.packageName, R.layout.notify_big_content_view)
    }

   private fun getContentView(): RemoteViews{
        return RemoteViews(this.packageName, R.layout.notify_content_view)
    }

The custom notification bar will have a large image style and a small image style, namely the normal style and the extended style . There will be restrictions on the height. The height of the normal style cannot exceed 64dp, and the extended height cannot exceed 256dp;
today we mainly talk about the display of the large and small image styles. Adaptation;
if our lovely product and design girl gave a beautiful big picture style, then our setting method is as follows:

/**
     * 创建服务通知
     */
    private fun createForegroundNotification(): Notification {
        val builder: NotificationCompat.Builder = NotificationCompat.Builder(applicationContext, notificationChannelId)
        //通知小图标
        builder.setSmallIcon(R.mipmap.ic_launcher_round)
        //通知标题
        builder.setContentTitle("苏宁窖藏")
        //通知内容
        builder.setContentText("苏宁是国内优秀的跨国企业?$count")
        //设置通知显示的时间
        builder.setWhen(System.currentTimeMillis())
        //设定启动的内容
        val  activityIntent: Intent = Intent(this, MainActivity::class.java)
        activityIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
        val pendingIntent: PendingIntent = PendingIntent.getActivity(this,
                1,activityIntent, PendingIntent.FLAG_UPDATE_CURRENT)
        builder.setContentIntent(pendingIntent)
        //普通样式(小图样式)
        builder.setCustomContentView(getContentView())
        //扩展样式(大图样式)
        builder.setCustomBigContentView(getBigContentView())
        builder.priority = NotificationCompat.PRIORITY_DEFAULT
        //设置为进行中的通知
        builder.setOngoing(true)
        //创建通知并返回
        return builder.build()
    }
    
    //普通样式视图
    private fun getContentView(): RemoteViews{
        val remoteViews: RemoteViews = RemoteViews(this.packageName, R.layout.notify_content_view)
        return remoteViews
    }
    
    //大图样式视图
    private fun getBigContentView(): RemoteViews{
        return RemoteViews(this.packageName, R.layout.notify_big_content_view)
    }

normal style

builder.setCustomContentView(getContentView())

Extended style
builder.setCustomBigContentView(getBigContentView())

The display effect is as follows:

 Long press on notification to show extended notification

 4. Realize music playback based on RemoteViews

4.1 Add a listener event for the button to play the previous episode when creating a notification

    private fun getBigContentView(): RemoteViews{
        bigNormalView = RemoteViews(this.packageName, R.layout.notify_big_content_view)
        val intent: Intent = Intent(PLAY_MUSIC)
        intent.component = ComponentName("com.yifan.service", "com.yifan.service.PlayBroadcastReceiver")
        val pendPreviousButtonIntent = PendingIntent.getBroadcast(this, count++, intent, PendingIntent.FLAG_UPDATE_CURRENT)
        //设置播放上一集按钮点击监听
        bigNormalView.setOnClickPendingIntent(R.id.play_last, pendPreviousButtonIntent);
        //设置播放相关按钮等相关监听操作...
        return bigNormalView
    }

4.2 Accept click notification events through BroadcastReceiver

class PlayBroadcastReceiver : BroadcastReceiver(){
    override fun onReceive(context: Context?, intent: Intent?) {
        if(intent?.action == ForegroundService.Companion.PLAY_MUSIC){
            Toast.makeText(context, "播放上一首音乐", Toast.LENGTH_SHORT).show()
            //更新UI按钮显示
            Single.exe()
        }
    }
}

Notes on using broadcast:

val intent: Intent = Intent(PLAY_MUSIC)
        intent.component = ComponentName("com.yifan.service", "com.yifan.service.PlayBroadcastReceiver")

        val pendPreviousButtonIntent = PendingIntent.getBroadcast(this, count++, intent, PendingIntent.FLAG_UPDATE_CURRENT)

Line 1 of the red code means that a broadcast is to be sent, and the name of the broadcast is specified, which corresponds to the broadcast name we registered before.

Line 2 of the red code is not required for Android 7.0 and below, but for Android 8.0 or later, the conditions for sending broadcasts are more stringent, and this line must be added. The created ComponentName instantiation object has two parameters, the first parameter refers to the package name of the receiving broadcast class, and the second parameter refers to the full class name of the receiving broadcast class.

Line 3 of the red code refers to sending a broadcast.

4.3 Update notification UI

    override fun callback() {
        //更新UI显示
        bigNormalView.setTextViewText(R.id.play_last, "PlayLast")
        val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
        //通知通知UI更新了,更新显示
        notificationManager.notify(NOTIFICATION_ID, notification)
    }

4.4 Complete foreground notification service code

/**
 * 前台服务通知
 */
class ForegroundService : Service(), UpdateUICallback{

    companion object{
        private const val TAG = "ForegroundService"
        //当前服务是否在运行
        var serviceIsLive: Boolean = false
        //通知ID
        private const val NOTIFICATION_ID = 1111
        //唯一的通知通道的ID,8.0及以上使用
        private const val notificationChannelId = "notification_channel_id_01"
        //广播监听器Action
        const val PLAY_MUSIC = "com.yifan.service.PLAY_MUSIC"
    }

    override fun onCreate() {
        super.onCreate()
        Log.d(TAG,"OnCreate")
        //标记服务启动
        serviceIsLive = true
        //开启前台服务通知
        startForegroundWithNotification()
        //监听UI更新回调
        UpdateSingleInstance.setCallBack(this)
    }

    override fun onBind(intent: Intent?): IBinder? {
        Log.d(TAG,"onBind")
        return null
    }

    override fun onUnbind(intent: Intent?): Boolean {
        Log.d(TAG,"onUnbind")
        return super.onUnbind(intent)
    }

    override fun onRebind(intent: Intent?) {
        super.onRebind(intent)
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        Log.d(TAG,"onStartCommand")
        //数据获取
        val data: String? = intent?.getStringExtra("Foreground") ?: ""
        Toast.makeText(this, data, Toast.LENGTH_SHORT).show()
        return super.onStartCommand(intent, flags, startId)
    }

    /**
     * 开启前景服务并发送通知
     */
    private fun startForegroundWithNotification(){
        //8.0及以上注册通知渠道
        createNotificationChannel()
        notification = createForegroundNotification()
//        notification.contentView
        //将服务置于启动状态 ,NOTIFICATION_ID指的是创建的通知的ID
        startForeground(NOTIFICATION_ID, notification)
//        handler.postDelayed(mRunnable, 1000)
    }
    private var count: Int = 0;
    private val handler: Handler = Handler(Looper.getMainLooper());
    private val mRunnable: Runnable = object : Runnable {
        override fun run() {
            notification = createForegroundNotification()
//            notification.defaults
            //发送通知到状态栏
            val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
            //通知更新UI
//            notificationManager.notify(NOTIFICATION_ID, notification);
            //将服务置于启动状态 ,NOTIFICATION_ID指的是创建的通知的ID
            startForeground(NOTIFICATION_ID, notification)
            count++
            handler.postDelayed(this, 1000)
        }
    }

    /**
     * 创建通知渠道
     */
    private fun createNotificationChannel(){
        val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager

        //Android8.0以上的系统,新建消息通道
        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){
            //用户可见的通道名称
            val channelName: String = "Foreground Service Notification"
            //通道的重要程度
            val importance: Int = NotificationManager.IMPORTANCE_HIGH
            //构建通知渠道
            val notificationChannel: NotificationChannel = NotificationChannel(notificationChannelId,
                    channelName, importance)
            notificationChannel.description = "Channel description"
            //LED灯
            notificationChannel.enableLights(true)
            notificationChannel.lightColor = Color.RED
            //震动
            notificationChannel.vibrationPattern = longArrayOf(0,1000,500,1000)
            notificationChannel.enableVibration(true)
            //向系统注册通知渠道,注册后不能改变重要性以及其他通知行为
            notificationManager.createNotificationChannel(notificationChannel)


        }
    }

    /**
     * 创建服务通知
     */
    private fun createForegroundNotification(): Notification {
        val notificationBuidler = NotificationCompat.Builder(applicationContext, notificationChannelId)
        //通知小图标
        notificationBuidler.setSmallIcon(R.mipmap.ic_launcher_round)
        //通知标题
        notificationBuidler.setContentTitle("苏宁窖藏")
        //通知内容
        notificationBuidler.setContentText("苏宁是国内优秀的跨国企业?$count")
        //设置通知显示的时间
        notificationBuidler.setWhen(System.currentTimeMillis())
        //设定启动的内容
        val  activityIntent: Intent = Intent(this, MainActivity::class.java)
        activityIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
        val pendingIntent: PendingIntent = PendingIntent.getActivity(this,
                1,activityIntent, PendingIntent.FLAG_UPDATE_CURRENT)
        notificationBuidler.setContentIntent(pendingIntent)
        notificationBuidler.setCustomContentView(getContentView())
        notificationBuidler.setCustomBigContentView(getBigContentView())
        notificationBuidler.priority = NotificationCompat.PRIORITY_DEFAULT
        //设置为进行中的通知
        notificationBuidler.setOngoing(true)

//        notificationManager.notify()
        //创建通知并返回
        return notificationBuidler.build()
    }

    private lateinit var normalView: RemoteViews
    private lateinit var bigNormalView: RemoteViews
    private lateinit var notification: Notification

    private fun getContentView(): RemoteViews{
        normalView = RemoteViews(this.packageName, R.layout.notify_content_view)
//        remoteViews.set
//        remoteViews.setOnClickFillInIntent()
//        remoteViews.setImageViewResource()
        //上一首图标添加点击监听
        val intent: Intent = Intent(PLAY_MUSIC)
        intent.component = ComponentName("com.yifan.service", "com.yifan.service.PlayBroadcastReceiver")
        val pendPreviousButtonIntent = PendingIntent.getBroadcast(this, count++, intent, PendingIntent.FLAG_UPDATE_CURRENT)
        normalView.setOnClickPendingIntent(R.id.play_last, pendPreviousButtonIntent);
//        PendingIntent.get
        return normalView
    }

    private fun getBigContentView(): RemoteViews{
        bigNormalView = RemoteViews(this.packageName, R.layout.notify_big_content_view)
        val intent: Intent = Intent(PLAY_MUSIC)
        intent.component = ComponentName("com.yifan.service", "com.yifan.service.PlayBroadcastReceiver")
        val pendPreviousButtonIntent = PendingIntent.getBroadcast(this, count++, intent, PendingIntent.FLAG_UPDATE_CURRENT)
        bigNormalView.setOnClickPendingIntent(R.id.play_last, pendPreviousButtonIntent);
        return bigNormalView
    }

    override fun onDestroy() {
        super.onDestroy()
        Log.d(TAG, "onDestroy")
        stopForeground(true)
        ForegroundService.serviceIsLive = false;
        handler.removeCallbacks(mRunnable)
    }

    override fun updateUI() {
//        Toast.makeText(this, "播放上一首音乐11111", Toast.LENGTH_SHORT).show()
        bigNormalView.setTextViewText(R.id.play_last, "PlayLast")
        val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
        notificationManager.notify(NOTIFICATION_ID, notification)
    }
}

/**
 * 更新UI回调
 */
interface UpdateUICallback{
    fun updateUI()
}

/**
 * 更新通信工具类,ForegroundService实现UpdateUICallback接口,
 * 广播接收者调用UpdateSingleInstance.updateUI()通知ForegroundService更新UI
 */
object UpdateSingleInstance{
    private var updateUICallback: UpdateUICallback? = null

    /**
     * 注册监听
     */
    fun setCallBack(updateUICallback: UpdateUICallback){
        this.updateUICallback = updateUICallback
    }

    fun cancelCallBack(){
        this.updateUICallback = null
    }

    /**
     * 通知UpdateSingleInstance更新UI
     */
    fun updateUI(){
        updateUICallback?.updateUI()
    }
}

/**
 * 广播接收者接受通知播放点击操作
 */
class PlayBroadcastReceiver : BroadcastReceiver(){
    override fun onReceive(context: Context?, intent: Intent?) {
        if(intent?.action == ForegroundService.Companion.PLAY_MUSIC){
            Toast.makeText(context, "播放上一首音乐", Toast.LENGTH_SHORT).show()
            UpdateSingleInstance.updateUI()
        }
    }
}

Notification notification layout

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal">
    <ImageView
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:src="@mipmap/ic_launcher"/>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginLeft="20dp"
        android:orientation="vertical">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="流浪的人在外想念你"/>
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal">
            <Button
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:id="@+id/play_last"
                android:text="上一首"/>
            <Button
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="暂停"/>
            <Button
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="下一首"/>
            <Button
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="收藏"/>

        </LinearLayout>
    </LinearLayout>
</LinearLayout>

Source code reference:

 service instance usage

refer to:

Android-based network music player - notification bar control (RemoteViews) (ten) - Programmer Sought

Android-based network music player - notification bar control (RemoteViews) (ten) - Programmer Sought

Android notification bar custom style

Guess you like

Origin blog.csdn.net/ahou2468/article/details/122523500