You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android应用后台/空闲时收到推送无法启动Service播放MP3

问题根因

Android 8.0(API 级别 26)起系统引入后台执行限制:应用未处于前台可见状态时,直接调用startService()启动普通后台服务会抛出IllegalStateException导致启动失败,这就是应用切后台后Service无法正常运行的核心原因。你当前使用的普通Service启动逻辑仅在应用前台时合法,后台场景必须按系统规则适配前台服务。

适配方案

1. 声明所需权限与服务属性

首先在AndroidManifest.xml中补充前台服务相关权限,Android 12及以上版本需要额外声明媒体类前台服务类型,Android 13及以上需要补充通知运行时权限:

<!-- 基础前台服务权限,API26+必需 -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<!-- Android 12+ 声明媒体播放类型前台服务 -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
<!-- Android 13+ 通知运行时权限,需用户动态授权 -->
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />

同时在Manifest中注册MusicService时补充前台服务类型属性:

<service
    android:name=".MusicService"
    android:exported="false"
    android:foregroundServiceType="mediaPlayback" />

2. 修正Firebase推送中启动Service的逻辑

禁止直接调用startService(),按系统版本区分启动方式:API26及以上使用startForegroundService()启动服务,该方法允许应用在后台临时唤起服务,但要求服务启动后5秒内必须调用startForeground()绑定前台通知,否则服务会被系统直接杀死并触发ANR。
修改FirebaseMessaging类中的启动代码如下:

val resultIntent = Intent(applicationContext, MusicService::class.java).apply {
    putExtra("title", title)
    putExtra("body", body)
    putExtra("action_type", action_type)
    putExtra("deep_link_type", deep_link_type)
    putExtra("deep_link_id", deep_link_id)
    putExtra("masjid_Id", masjid_Id)
    putExtra("url", url)
    action = "com.example.mymasjid"
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    startForegroundService(resultIntent)
} else {
    startService(resultIntent)
}

3. 改造MusicService为合规前台服务

你当前的Service虽然会发送通知,但没有调用startForeground()将通知与前台服务绑定,不符合系统要求,需要做以下修改:

  • 服务启动后第一时间构建通知并调用startForeground()绑定,确保在5秒时限内完成
  • 替换同步阻塞的prepare()为异步prepareAsync(),避免主线程阻塞触发ANR
  • 音频播放完成/出错时主动停止前台服务、移除通知
  • 适配高版本PendingIntent的Flag要求,避免启动异常

修改onStartCommand代码:

override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
    title = intent?.getStringExtra("title").orEmpty()
    body = intent?.getStringExtra("body").orEmpty()
    Log.i("***TAG","From Background Service")

    // 立刻构建前台通知并绑定服务,必须在启动后5秒内执行
    val foregroundNotification = buildForegroundNotification(title, body)
    // 注意:startForeground的通知ID不能为0,此处固定使用1001即可
    startForeground(1001, foregroundNotification)

    val url = "https://cdn.islamic.network/quran/audio/64/ar.alafasy/9.mp3"
    if (mediaPlayer != null) {
        mediaPlayer?.stop()
        mediaPlayer?.release()
        mediaPlayer = null
    }
    mediaPlayer = MediaPlayer()
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
        mediaPlayer?.setAudioAttributes(
            AudioAttributes.Builder()
                .setUsage(AudioAttributes.USAGE_MEDIA)
                .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
                .setLegacyStreamType(AudioManager.STREAM_MUSIC)
                .build()
        )
    } else {
        mediaPlayer?.setAudioStreamType(AudioManager.STREAM_MUSIC)
    }
    try {
        mediaPlayer?.setDataSource(url)
        // 替换同步prepare为异步方法,避免主线程阻塞
        mediaPlayer?.prepareAsync()
        mediaPlayer?.isLooping = false
        mediaPlayer?.setOnPreparedListener(this)
        mediaPlayer?.setOnCompletionListener(this)
        mediaPlayer?.setOnErrorListener(this)
    } catch (e: IOException) {
        e.printStackTrace()
        // 初始化出错直接停止服务
        stopForeground(STOP_FOREGROUND_REMOVE)
        stopSelf()
    }
    return START_NOT_STICKY
}

将原来的adhanNotification方法重构为前台通知构建方法,修正通知配置:

private fun buildForegroundNotification(title: String, body: String): Notification {
    val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
    val channelId = "media_play_channel"
    // Android 8以上创建通知渠道
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        val channel = NotificationChannel(
            channelId,
            "音频播放通知",
            NotificationManager.IMPORTANCE_LOW
        ).apply {
            enableVibration(true)
            vibrationPattern = longArrayOf(100, 200, 300, 400, 500, 400, 300, 200, 400)
            setSound(null, null) // 关闭通知默认声音,避免与自定义MP3播放冲突
        }
        notificationManager.createNotificationChannel(channel)
    }

    val collapsedView = RemoteViews(packageName, R.layout.notification)
    val expandView = RemoteViews(packageName, R.layout.notification_expand)
    val time = Utility.getTime("LLL dd, KK:mm aaa")
    collapsedView.setTextViewText(R.id.tvNotifyTime, time)
    collapsedView.setTextViewText(R.id.title, title)
    collapsedView.setTextViewText(R.id.body, body)
    expandView.setTextViewText(R.id.tvNotifyTime, time)
    expandView.setTextViewText(R.id.title, title)
    expandView.setTextViewText(R.id.body, body)

    val onCancelIntent = Intent(this, CancelNotification::class.java)
    // 适配高版本PendingIntent Flag
    val pendingFlag = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
    } else {
        PendingIntent.FLAG_UPDATE_CURRENT
    }
    val onDismissPendingIntent = PendingIntent.getService(
        applicationContext,
        0,
        onCancelIntent,
        pendingFlag
    )

    return NotificationCompat.Builder(this, channelId)
        .setSmallIcon(R.drawable.ic_notification)
        .setDeleteIntent(onDismissPendingIntent)
        .setOngoing(true) // 前台服务通知需设为常驻,不可被滑动删除
        .setPriority(NotificationCompat.PRIORITY_LOW)
        .setCustomContentView(collapsedView)
        .setCustomBigContentView(expandView)
        .build()
}

修改播放器回调逻辑,播放结束/出错时正确释放资源、停止服务:

override fun onPrepared(p0: MediaPlayer?) {
    mediaPlayer?.start()
    Log.i("***play", "play")
}

override fun onCompletion(p0: MediaPlayer?) {
    mediaPlayer?.stop()
    mediaPlayer?.release()
    mediaPlayer = null
    Log.i("***play", "complete")
    // 停止前台服务并移除通知
    stopForeground(STOP_FOREGROUND_REMOVE)
    stopSelf()
}

override fun onError(p0: MediaPlayer?, p1: Int, p2: Int): Boolean {
    Log.i("***play", "error")
    mediaPlayer?.release()
    mediaPlayer = null
    stopForeground(STOP_FOREGROUND_REMOVE)
    stopSelf()
    return true
}

4. 额外适配注意事项

  • Android 13及以上版本需要在应用启动时主动申请POST_NOTIFICATIONS运行时权限,用户授权后才能正常显示前台通知,否则服务启动会失败。
  • 推送配置需注意:Firebase推送如果同时携带notification字段和data字段,应用在后台时Firebase会自动拦截推送、生成系统通知,不会触发你自定义的onMessageReceived逻辑,需要确保推送仅携带data字段,才能保证所有场景下都走自定义播放逻辑。
  • 针对国内定制ROM(小米、华为、OPPO、vivo等),需要引导用户手动将应用加入后台运行白名单、开启自启动权限,否则部分ROM会在应用后台时强制拦截服务启动。

内容的提问来源于stack exchange,提问作者Abdul Rasheed

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 09:22:01