前台服务保活相关兼容Android14

286 阅读4分钟

前台服务保活常用在直播、即时通讯等场景。在api34版本以及更高版本,对前台服务做了限制。通过音频或者录像权限保活效率更高。如果在api34中不使用音频或者录像权限进行保活在一些手机上回到后后台可能很快就断掉声音以及socket长链接等场景。

/**
 * 前台保活服务
 */
private const val TAG = "KeepLiveService"

class KeepLiveService : Service() {
    private val NOTIFY_ID = 1000
    override fun onBind(intent: Intent?): IBinder? {
        return null
    }
    /**
    *这个代码内部不要尝试捕获全局异常,如果startForeground得不到执行会报异常(KeepLiveService启动后,5秒内未调用startForeground则会异常:android.app.RemoteServiceException$ForegroundServiceDidNotStartInTimeException: Context.startForegroundService() did not then call Service.startForeground(): ServiceRecord{929d27e u0 com.george.test/com.george.test.KeepLiveService)
    * 特别是在8.0以及以上的设备上。
    **/
    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        val builder = NotificationCompat.Builder(this, packageName)
        getSystemService(NOTIFICATION_SERVICE)?.let {
            if (it is NotificationManager) {
                if (SDK_INT >= Build.VERSION_CODES.O) {
                    val channel = NotificationChannel(
                        TAG,
                        resources.getString(R.string.app_name),
                        NotificationManager.IMPORTANCE_LOW // 通知只在状态栏展示(避免打扰用户,一般直播前台服务保活常用)
                    )
                    channel.setShowBadge(true)
                    channel.lockscreenVisibility = NotificationCompat.VISIBILITY_PUBLIC
                    it.deleteNotificationChannel(packageName)
                    it.createNotificationChannel(channel)
                }
            }
            builder.setSmallIcon(R.mipmap.ic_launcher)
            builder.setContentText("click to back")
            builder.setTicker("ticker")
            builder.setAutoCancel(true)
            builder.setChannelId(TAG)
            // 一般设置当前内容标题
            builder.setContentTitle("title")
            // 点击通知栏回到MainActivity
            val backIntent = Intent(this, MainActivity::class.java)
            // 可以放一些MainActivity需要的参数
            backIntent.putExtra("info", "info")
            var pendFlag = PendingIntent.FLAG_UPDATE_CURRENT
            if (SDK_INT >= Build.VERSION_CODES.S) {
                pendFlag = PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE
            }
            val pendIntent = PendingIntent.getActivity(this, 0, backIntent, pendFlag)
            builder.setContentIntent(pendIntent)
        }
        val notificaiton = builder.build()
        if (SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
            // 初始化
            hasOpenCameraOrAudio = false
            // 兼容34前台服务新特性
            var serviceType = ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK
            // 检查录音权限, 如果没有录音权限使用FOREGROUND_SERVICE_TYPE_MICROPHONE 则会异常
            if (hasAudioPermission()) {
                serviceType = serviceType or ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE
                hasOpenCameraOrAudio = true
            }
            // 检查摄像头权限, 如果没有录音权限使用FOREGROUND_SERVICE_TYPE_MICROPHONE 则会异常
            if (hasCameraPermission()) {
                serviceType = serviceType or ServiceInfo.FOREGROUND_SERVICE_TYPE_CAMERA
                hasOpenCameraOrAudio = true
            }
            startForeground(NOTIFY_ID, notificaiton, serviceType)
        } else {
            startForeground(NOTIFY_ID, notificaiton)
        }
        return super.onStartCommand(intent, flags, startId)
    }

    private fun hasAudioPermission(): Boolean {
        return ContextCompat.checkSelfPermission(
            this,
            Manifest.permission.RECORD_AUDIO
        ) == PackageManager.PERMISSION_GRANTED
    }

    private fun hasCameraPermission(): Boolean {
        return ContextCompat.checkSelfPermission(
            this,
            Manifest.permission.CAMERA
        ) == PackageManager.PERMISSION_GRANTED
    }

    override fun onTaskRemoved(rootIntent: Intent?) {
        super.onTaskRemoved(rootIntent)
        stopForeground(STOP_FOREGROUND_REMOVE)
        stopSelf()
    }

    /**
     * 针对34版本的新特性, 使用这个标识判断是否打开了摄像头或者录音权限 进行前台服务保活
     */
    private var hasOpenCameraOrAudio = false

    fun startKeepService() {
        val intent = Intent(this, KeepLiveService::class.java)
        if (SDK_INT >= Build.VERSION_CODES.O) {
            // 兼容26以及以上版本 前台服务
            startForegroundService(intent)
        } else {
            startService(intent)
        }
    }

    /**
     * 针对34版本的新特性,对于摄像头或者录音权限变化的处理
     */
    fun restartKeepService() {
        if (SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
            if (hasOpenCameraOrAudio) {
                stopKeepService()
                startKeepService()
            }
        }
    }

    /**
     * 停止前台服务保活
     */
    fun stopKeepService() {
        val intent = Intent(this, KeepLiveService::class.java)
        stopService(intent)
    }
}
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />

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

<uses-permission android:name="android.permission.FOREGROUND_SERVICE_CAMERA"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MICROPHONE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />


<service android:name=".KeepLiveService"
    android:foregroundServiceType="microphone|mediaPlayback|camera"/>

注意:对于34以及以上版本使用录音或者摄像头保活需要检查权限才能使用,否则会异常。

Google市场要求

在Google应用商店上架abb的时候对于Camera保活的需要提供链接以及视频等信息(如果嫌麻烦,则只保留录音保活足够了)。

对于Google的前台服务:了解前台服务和全屏 intent 要求 - Play 管理中心帮助

IMPORTANCE_类型

IMPORTANCE_LOW适用于直播保活,IMPORTANCE_HIGH适用于支付通知。具体场景如下:

Android 通知渠道的 6 种重要级别(IMPORTANCE_*)适用于不同的场景,以下是每种级别的适用场景说明:

1、IMPORTANCE_NONE 场景:完全不希望用户看到通知,但需要记录某些后台事件 示例:日志记录、后台任务的状态跟踪

2、IMPORTANCE_MIN 场景:需要显示通知,但不需要打扰用户 示例:后台同步完成、低优先级的系统状态更新

3、IMPORTANCE_LOW 场景:需要显示通知,但不需要声音或震动 示例:新消息提醒(非紧急)、应用更新可用、直播

4、IMPORTANCE_DEFAULT 场景:需要显示通知并发出声音,但不希望过于打扰用户 示例:普通消息通知、社交媒体互动提醒

5、IMPORTANCE_HIGH 场景:需要立即引起用户注意的通知 示例:紧急消息、重要事件提醒、支付成功通知

6、IMPORTANCE_MAX 场景:需要立即引起用户注意的紧急通知 示例:安全警报、系统崩溃、紧急电话

华为设备

对于华为设备华为手机/平板应用无法后台运行怎么办? | 华为官网

声网参考为什么部分 Android 版本 App 锁屏或切后台音视频采集或播放(渲染)无效? | 文档中心 | 声网