Android笔记(十六):前台服务

设置服务为前台服务。前台服务会在状态栏显示一个通知。通知界面与服务进行关联。

一、什么是通知?

Notification通知是在移动应用APP提供给用户的消息提示,是在移动系统的通知栏中显示。当移动应用不在运行时或者在后台状态下,通过发布通知给用户,用户可以浏览通知的提示信息或者对通知进行某些操作。

图1 通知显示示例

如上图所示:

①小图标:为必要图标,通过setSmallIcon()设置

②应用名称:由系统提供。

③时间戳:由系统提供,可以通过setWhen()进行替换或使用setShowWhen(false)将其隐藏。

④标题:可选内容,通过setContentTitle()设置。

⑤文本:可选内容,通过setContentText()设置。

⑥大图标:可选图标(通常仅用于联系人照片),通过setLargeIcon设置。

⑦样式:通过样式设置大图片。

1.在AndroidManifest.xml中配置发布通知的权限

注意从Android13开始发布通知需要配置android.permission.POST_NOTIFICATIONS许可

  

2.请求发布通知权限

// 请求发布通知权限

if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.TIRAMISU) {

ActivityCompat.requestPermissions(

this,

arrayOf(android.Manifest.permission.POST_NOTIFICATIONS),

0)

}

3. 创建通知渠道和创建通知

(1)创建通知渠道

从 Android 8.0(API 级别 26)开始,所有通知都必须分配到相应的渠道。

 //定义通知管理器
    val notificationManager:NotificationManager =
        getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
    //定义通知渠道的标识
    val channelId = "com.example.ch07"
    //定义通知渠道的名称
    val channelName = "移动应用开发"
    //定义通知渠道:指定通知渠道的标识、名称和通知渠道的重要级别
    val channel = NotificationChannel(channelId,channelName, NotificationManager.IMPORTANCE_DEFAULT)
    //定义通知渠道的描述信息
    val channelDesc = "移动应用通知渠道描述"
    //创建并配置通知渠道
    notificationManager.createNotificationChannel(channel)

(2)创建通知

 //创建通知
    val notification = Notification.Builder(this,channelId) .apply{
        //设置通知标题
        setContentTitle("通知实例一")
        //设置通知内容
        setContentText("欢迎使用通知")
        //设置通知时间
        setWhen(System.currentTimeMillis())
        //设置通知的小图标
        setSmallIcon(R.mipmap.nature)
        //设置通知的大图标
        setLargeIcon(BitmapFactory.decodeResource(resources,R.mipmap.someone))
        //设置通知样式
        style = Notification.BigPictureStyle().bigPicture(BitmapFactory.decodeResource(resources,R.mipmap.honggutan))
    }.build()

(3)发布通知

 //创建通知标记
    val notificationID = 1
    //发布通知到通知栏
    notificationManager.notify(notificationID,notification)

4. 通知实例

接下来通过实例来说明发布通知:

(1)定义界面MainScreen

在MainScreen中定义发布通知的按钮,提供交互处理

@Composable
fun MainScreen(postAction:()->Unit){ Box(modifier = Modifier.fillMaxSize(),contentAlignment = Alignment.Center){ TextButton(onClick={ postAction.invoke()
        }){ Text("发布通知",fontSize = 24.sp)
        }
    }
}

(2)定义主活动MainActivity

class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState)
        //请求权限
        requestNotificationPermission()
        setContent { Ch07_DemoTheme { Surface(
                    modifier = Modifier.fillMaxSize(),
                    color = MaterialTheme.colorScheme.background
                ) { MainScreen { showNotification()
                    }
                }
            }
        }
    }
    private fun requestNotificationPermission(){ // 检查通知权限是否已经授予 注意:API 33以上版本需要检查POST_NOTIFICATIONS发布通知权限
        if(Build.VERSION.SDK_INT>= Build.VERSION_CODES.TIRAMISU) { ActivityCompat.requestPermissions(
                this,
                arrayOf(android.Manifest.permission.POST_NOTIFICATIONS),
                0
            )
        }
    }
    private fun showNotification(){ //定义通知管理器
        val notificationManager:NotificationManager =
            getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
        //定义通知渠道的标识
        val channelId = "com.example.ch07"
        //定义通知渠道的名称
        val channelName = "移动应用开发"
        //定义通知渠道:指定通知渠道的标识、名称和通知渠道的重要级别
        val channel = NotificationChannel(channelId,channelName, NotificationManager.IMPORTANCE_DEFAULT)
        //定义通知渠道的描述信息
        val channelDesc = "移动应用开发通知渠道描述"
        //创建并配置通知渠道
        notificationManager.createNotificationChannel(channel)
        //创建通知
        val notification =
            Notification.Builder(this,channelId)
                .apply{ //设置通知标题
            setContentTitle("通知实例一")
            //设置通知内容
            setContentText("欢迎使用通知")
            //设置通知时间
            setWhen(System.currentTimeMillis())
            //设置通知的小图标
            setSmallIcon(R.mipmap.nature)
            //设置通知的大图标
            setLargeIcon(BitmapFactory.decodeResource(resources,R.mipmap.someone))
            //设置通知样式
            style =
                Notification.BigPictureStyle().bigPicture(BitmapFactory.decodeResource(resources,R.mipmap.honggutan))
        }.build()
        //创建通知标记
        val notificationID = 1
        //发布通知到通知栏,注意从Android13开始发布通知需要配置android.permission.POST_NOTIFICATIONS许可
        notificationManager.notify(notificationID,notification)
    }
}

运行结果如下所示:

图2 运行结果

运行结果的图片来自网络,如有侵权,告知,会主动删除。

二、前台服务

设置服务为前台服务。前台服务会在状态栏显示一个通知。通知界面与服务进行关联。从Android 14版本(API Level 34)开始,每个前台服务需要声明合适的服务类型。也就是在启动前台服务时,系统会根据服务类型检查是否满足前提条件。

表2-1 常见服务类型

前台服务类型要声明的权限运行时要求前提条件运行时请求权限请求权限调用的方法说明
相机FOREGROUND_SERVICE_CAMERA请求CAMERA运行时权限在后台访问相机
连接设备FOREGROUND_SERVICE_CONNECTED_DEVICECHANGE_NETWORK_STATE或CHANGE_WIFI_STATE或CHANGE_WIFI_MULTICAST_STATE或NFC或TRANSMIT_IRBLUETOOTH_CONNECT或BLUETOOTH_ADVERTISE或BLUETOOTH_SCAN或UWEB_RANGING调用UsbManager.requestPermission()与需要的蓝牙、NFC、IR、USB或网络的外部设备互动
数据同步FOREGROUND_SERVICE_DATA_SYNC数据传输的操作
健康FOREGROUND_SERVICE_HEALTHHIGH_SAMPLING_RATE_SENSORSBORY_SENSORS或ACTIVITY_RECOGNITION为健身类别的应用提供支持
位置FOREGROUND_SERVICE_LOCATIONACESS__COARSE_LOCATION或ACESS_FINE_LOCATION为位置信息使用权的长时间运行用例
媒体FOREGROUND_SERVICE_MEDIA_PLAYBACK在后台继续播放音频或视频
媒体投影FOREGROUND_SERVICE_MEDIA_PROJECTION调用createScreenCaptureIntent()使用MediaProjection API将内容投影到外部
麦克风FOREGROUND_SERVICE_MICROPHONERECORD_AUDIO在后台继续捕获麦克风内容
致电FOREGROUND_SERVICE_PHONE_CALLMANAGE_OWN_CALLS使用COnnetionService API继续当前通话
远程消息传递FOREGROUND_SERVICE_REMOTE_MESSAGING将短信从一台设备转移另外一台设备
配置前台服务

启动前台服务

Context.startForeground(int,Notification)

例:创建一个前台服务,播放mp3文件,并在状态栏中显示相应的通知。

1.创建服务

自定义服务,可以控制音频的播放,代码如下:

class MusicService : Service() { lateinit var mediaPlayer: MediaPlayer
    override fun onCreate() { super.onCreate()
        mediaPlayer = MediaPlayer.create(this,R.raw.song3)
    }
    override fun onBind(intent: Intent): IBinder? { postNotification()
        mediaPlayer.setOnPreparedListener { mediaPlayer.start()
        }
        mediaPlayer.setOnCompletionListener { mediaPlayer.stop()
        }
        return null
    }
    override fun onUnbind(intent: Intent?): Boolean { if(mediaPlayer.isPlaying) { mediaPlayer.stop()
        }
        return super.onUnbind(intent)
    }
    /**
     * Request notification permission
     * 请求通知权限
     */
    private fun requestNotificationPermission(){ val notificationPermissionGranted =
            NotificationManagerCompat.from(this).areNotificationsEnabled()
        if(!notificationPermissionGranted){ val intent = Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS)
            intent.putExtra(Settings.EXTRA_APP_PACKAGE,packageName)
            startActivity(intent)
        }
    }
    private fun postNotification(){ //请求通知权限
        requestNotificationPermission()
        //创建通知管理器
        val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
        //定义通知渠道
        val channel = NotificationChannel("music_service","一剪梅",NotificationManager.IMPORTANCE_DEFAULT)
        //创建通知渠道
        notificationManager.createNotificationChannel(channel)
        //定义启动服务的意图
        val intent1 = Intent(this,NextActivity::class.java)
        //定义悬浮意图
        val pendingIntent = PendingIntent.getActivity(this,0,
            intent1,PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE)
        //创建通知
        val notification = NotificationCompat.Builder(this,"music_service").apply{ setOngoing(true)
            setOnlyAlertOnce(true)
            setContentTitle("播放音乐")
            setContentText("正在歌曲播放一剪梅...")
            setSmallIcon(R.mipmap.ic_launcher)
            setColorized(true)
            color = resources.getColor(R.color.teal_200,null)
            setContentIntent(pendingIntent)
        }.build()
        startForeground(1,notification)
    }
}

2.在AndroidManifest.xml配置服务

设置前台服务类型为mediaPlayback

3.在自定义应用中创建通知渠道

class MusicApp: Application() { override fun onCreate() { super.onCreate()
        //从API 26开始使用通知渠道
        if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.O){ //定义通知管理器
            val notificationManager: NotificationManager =
                getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
            //定义通知渠道的标识
            val channelId = "com.example.course.ch07"
            //定义通知渠道的名称
            val channelName = "移动应用开发"
            //定义通知渠道:指定通知渠道的标识、名称和通知渠道的重要级别
            val channel = NotificationChannel(
                            channelId,
                            channelName,
                            NotificationManager.IMPORTANCE_DEFAULT)
            //创建并配置通知渠道
            notificationManager.createNotificationChannel(channel)
        }
    }
}

需要在AndroidManifest.xml中指定自定义的应用为当前应用,配置自定义应用

 ....... 

4. 在主活动MainActivity中启动服务

要启动前台服务需要在AndroidManifest.xml配置前台服务的权限:

  

在主活动启动前台服务

class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState)
        val intent1 = Intent(this,MusicService::class.java)
        requestPermissions()
        setContent { Ch07_DemoTheme { // A surface container using the 'background' color from the theme
                Surface(
                    modifier = Modifier.fillMaxSize(),
                    color = MaterialTheme.colorScheme.background
                ) { Column(modifier = Modifier.fillMaxSize(),
                        verticalArrangement = Arrangement.Center,
                        horizontalAlignment = Alignment.CenterHorizontally){ Row{ TextButton(onClick = { startService(intent1)
                            }){ Text("播放",fontSize = 20.sp)
                            }
                            TextButton(onClick = { stopService(intent1)
                            }){ Text("停止",fontSize = 20.sp)
                            }
                        }
                    }
                }
            }
        }
    }
    private fun requestPermissions(){ //从API 33开始请求发布通知权限
        if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.TIRAMISU) { ActivityCompat.requestPermissions(
                this,
                arrayOf(android.Manifest.permission.POST_NOTIFICATIONS),
                0
            )
        }
    }
}

图3

参考文献

  1. 陈轶 《Android移动应用开发(微课版)》清华大学出版社 2022-9 P203-P238

  2. 前台服务启动限制

    https://developer.android.google.cn/about/versions/12/foreground-services?hl=zh-cn