Android前台服务讲解一

1.服务是什么(Service)

Service 是一种可在后台执行长时间运行操作而不提供界面的应用组件。服务可由其他应用组件启动,而且即使用户切换到其他应用,服务仍将在后台继续运行。此外,组件可通过绑定到服务与之进行交互,甚至是执行进程间通信 (IPC)。例如,服务可在后台处理网络事务、播放音乐,执行文件 I/O 或与内容提供程序进行交互

2.前台服务(ForegroundService)是什么?

前台服务执行一些用户能注意到的操作。例如,音频应用会使用前台服务来播放音频曲目。前台服务必须显示通知。即使用户停止与应用的交互,前台服务仍会继续运行。

应用场景

最常见的表现形式就是音乐播放服务,应用程序后台运行时,用户可以通过通知栏,知道当前播放内容,并进行暂停、继续、切歌等相关操作。

3.为什么用前台服务

后台运行的Service系统优先级相对较低,当系统内存不足时,在后台运行的Service就有可能被回收,为了保持后台服务的正常运行及相关操作,可以选择将需要保持运行的Service设置为前台服务,从而使APP长时间处于后台或者关闭(进程未被清理)时,服务能够保持工作

4.前台服务使用

4.1定义前台服务

class ForegroundService : Service() {

    companion object{
        private const val TAG = "ForegroundService"
    }

    override fun onCreate() {
        super.onCreate()
        Log.d(TAG,"OnCreate")
    }

    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")
        return super.onStartCommand(intent, flags, startId)
    }

    override fun onDestroy() {
        super.onDestroy()
        Log.d(TAG, "onDestroy")
    }

}

执行日志

2022-01-16 09:46:27.430 22461-22461/com.yifan.service D/ForegroundService: OnCreate
2022-01-16 09:46:27.430 22461-22461/com.yifan.service D/ForegroundService: onStartCommand

4.2在AndroidManifest.xml注册服务

<service android:name=".ForegroundService"  />

需要在Android 9(API级别28)或者以上使用前台服务需要请求FOREGROUND_SERVICE权限,FOREGROUND_SERVICE这个安装权限,因此系统自动授权给请求的APP;

<manifest xmlns:android="http://schemas.android.com/apk/res/android" ...>

    <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>

    <application ...>
        ...
        <service android:name=".ForegroundService"  />
    </application>
</manifest>

注意:

需要在Android 9(API级别28)或者以上使用前台服务需要请求FOREGROUND_SERVICE权限,若没有请求FOREGROUND_SERVICE权限,系统会抛出SecurityException异常;

4.3创建服务通知内容,例如音乐播放,蓝牙设备正在连接等

   companion object{
        //通知ID
        private const val NOTIFICATION_ID = 1111
        //唯一的通知通道的ID
        private const val notificationChannelId = "notification_channel_id_01"
    }

/**
     * 开启前台服务并发送通知
     */
    private fun startForegroundWithNotification(){
        //8.0及以上注册通知渠道
        createNotificationChannel()
        val notification: Notification = createForegroundNotification()
        //将服务置于启动状态 ,NOTIFICATION_ID指的是创建的通知的ID
        startForeground(NOTIFICATION_ID, notification)
        //发送通知到状态栏
        val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
        notificationManager.notify(NOTIFICATION_ID, notification);
    }


    /**
     * 创建通知渠道
     */
    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 builder: NotificationCompat.Builder = NotificationCompat.Builder(applicationContext, notificationChannelId)
        //通知小图标
        builder.setSmallIcon(R.mipmap.ic_launcher_round)
        //通知标题
        builder.setContentTitle("苏宁窖藏")
        //通知内容
        builder.setContentText("苏宁是国内优秀的跨国企业?")
        //设置通知显示的时间
        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()
    }

注意事项:

创建和管理通知渠道,从Android8.0开始,需要为发送的美中不同类型的通知创建一个渠道,如果在Android8.0及以上在未指定通知频道的情况下发送通知,通知不会显示,会记录错误;

创建通知渠道的步骤:

1)通过一个唯一的channel ID ,对用户可见的channel name通知重要程度importance level构建一个通知渠道;
2)可选的使用setDescription()指定用户在系统设置页面看到的关于通知的相关描述;
3)通过createNotificationChannel()注册通知渠道。

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

        //唯一的通知通道的ID
        val notificationChannelId = "notification_channel_id_01"

        //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)
        }
    }

importance level主要有七种层次:

  • IMPORTANCE_DEFAULT: 默认notification importance,可以在任何地方显示,有声音。
  • IMPORTANCE_HIGH:可以在任何地方显示,有声音.
  • IMPORTANCE_LOW:可以在任何地方显示,没有声音.
  • IMPORTANCE_MAX:重要程度最高,可以在任何地方显示,有声音,可以在用户当前屏幕上显示通知,可以使用full screen intents.比如来电。
  • IMPORTANCE_MIN:无声音,不会出现在状态栏中。
  • IMPORTANCE_NONE:在任何地方都不会显示,被阻塞。
  • IMPORTANCE_UNSPECIFIED:表示用户没有表示重要性的值。这个值是为了持久的首选项,并且永远不应该与实际的通知相关联。

4.4在Application或需要开启服务的地方调用

//启动服务 
if(!ForegroundService.Companion.serviceIsLive){
            mForegroundService = Intent(this, ForegroundService::class.java)
            mForegroundService.putExtra("Foreground", "This is a foreground service.");
            // Android 8.0使用startForegroundService在前台启动新服务
            if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){
                startForegroundService(mForegroundService)
            }else{
                startService(mForegroundService)
            }
        }else{
            Toast.makeText(this, "前台服务正在运行中...", Toast.LENGTH_SHORT).show();
        }

注意

Android 8.0使用startForegroundService()在前台启动新服务

4.5在Application或其他地方停止服务

//停止服务
mForegroundService = Intent(this, ForegroundService::class.java);
stopService(mForegroundService)

4.6启动前台服务时创建通知

ForegroundService
override fun onCreate() {
        super.onCreate()
         //标记服务启动
        serviceIsLive = true
        val notification: Notification = createForegroundNotification()
        //将服务置于启动状态 ,NOTIFICATION_ID指的是创建的通知的ID
        startForeground(NOTIFICATION_ID, notification)
}

4.7停止服务,关闭通知

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

4.8完成整前台服务类

class ForegroundService : Service() {

    companion object{
        private const val TAG = "ForegroundService"
        var serviceIsLive: Boolean = false
        private const val NOTIFICATION_ID = 1111
        //唯一的通知通道的ID
        private const val notificationChannelId = "notification_channel_id_01"
    }

    override fun onCreate() {
        super.onCreate()
        Log.d(TAG,"OnCreate")
        startForegroundWithNotification()
    }

    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()
        val notification: Notification = createForegroundNotification()
        //将服务置于启动状态 ,NOTIFICATION_ID指的是创建的通知的ID
        startForeground(NOTIFICATION_ID, notification)
        //发送通知到状态栏
        val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
        notificationManager.notify(NOTIFICATION_ID, notification);
    }


    /**
     * 创建通知渠道
     */
    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 builder: NotificationCompat.Builder = NotificationCompat.Builder(applicationContext, notificationChannelId)
        //通知小图标
        builder.setSmallIcon(R.mipmap.ic_launcher_round)
        //通知标题
        builder.setContentTitle("苏宁窖藏")
        //通知内容
        builder.setContentText("苏宁是国内优秀的跨国企业?")
        //设置通知显示的时间
        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()
    }

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

}

5.总结

  • Android8.0及以上通知需要添加通知渠道,否则无法显示;
  • Android9.0前台服务通知需要添加<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>权限;
  • Android 8.0使用startForegroundService在前台启动新服务;

Android8.0后台执行限制
为提高设备性能,系统会限制未在前台运行的应用的某些行为。具体而言:

  • 在后台运行的应用对后台服务的访问受到限制
  • 应用无法使用其清单注册大部分隐式广播

默认情况下,这些限制仅适用于针对O的应用。不过用户可以从Settings屏幕为任意应用启用这些限制,即使应用并不是以O为目标平台。

Android8.0还对特定函数做出了如下变更:

  • 如果针对Android8.0的应用尝试在不允许创建其后台服务的情况下使用startService()函数,则该函数将引发一个IllegalStateException.
  • Context.startForegroundService()函数将启动一个前台服务。即使应用在后台运行,系统也允许其调用Context.startForegroundService().不过,应用必须在创建服务后的5秒内调用改服务的startForegroun()函数,否则将报ANR(Application Not Responding)错误。

参考:

​​​​​​Android通知栏前台服务 - 几圈年轮 - 博客园

Android8.0使用通知创建前台服务_Haienzi的博客-CSDN博客_android 创建前台服务

Foreground services  |  Android Developers

猜你喜欢

转载自blog.csdn.net/ahou2468/article/details/122521035