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

Android 14中使用startForegroundService无法更新通知问题求助

Android 14 前台通知无法更新问题解决

问题背景

用户发起视频上传时流程如下:

  • 启动服务,通过startForeground显示搜索待上传视频的通知
  • 找到视频后,启动Amazon TransferService,使用startForegroundService显示带进度条的上传通知

该流程在Android 13及以下版本可正常更新通知,但在Android 14中,通知会卡在初始的搜索状态,无法切换为上传进度通知。测试发现改用startService搭配startForeground启动上传通知时,通知可正常更新,但这是Android Oreo之前的用法。

原搜索通知实现代码:

private fun showNotification(notification: NotificationCompat.Builder) {
    startForeground(NOTIFICATION_ID, notification.build())
}

原上传通知启动代码:

private fun startTransferService(notification: NotificationCompat.Builder) {
    try {
        tsIntent = Intent(this, TransferService::class.java)
        tsIntent?.putExtra(TransferService.INTENT_KEY_NOTIFICATION, notification.build())
        tsIntent?.putExtra(
            TransferService.INTENT_KEY_NOTIFICATION_ID,
            NOTIFICATION_ID
        )
        tsIntent?.putExtra(TransferService.INTENT_KEY_REMOVE_NOTIFICATION, true)
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            startForegroundService(tsIntent)
        } else {
            startService(tsIntent)
            startForeground(NOTIFICATION_ID, notification.build())
        }
    } catch (exc: Exception) {
        if (exc is CancellationException) {
            throw exc
        }
        stopForeground()
        exc.printStackTrace()
    }
}

原因分析

Android 14对前台服务的通知管理新增了严格限制:

  1. 同一个通知ID被多个前台服务占用时,系统不会自动覆盖原有通知状态,导致新通知无法替换旧通知
  2. 通过Intent传递序列化后的Notification对象,在Android 14中可能出现状态丢失,无法正确同步通知更新

而使用startService+startForeground的方式,相当于在当前服务中先处理了通知状态的切换,再启动TransferService,避免了通知ID冲突和状态同步问题,但不符合Oreo及以上版本的前台服务规范。

解决方案

1. 释放原服务的前台通知ID

启动TransferService前,先停止当前服务的前台状态,释放通知ID的占用,避免多个服务同时使用同一个ID。

2. 让TransferService自行构建并管理通知

不要通过Intent传递已构建的Notification,而是传递构建通知所需的参数(如文件名、通知ID等),由TransferService内部构建通知并调用startForeground,后续进度更新直接使用NotificationManager刷新通知。

调整后的代码示例

原服务中启动TransferService的逻辑修改:

private fun startTransferService(uploadInfo: UploadInfo) {
    try {
        // 停止当前服务前台状态,释放通知ID
        stopForeground(STOP_FOREGROUND_REMOVE)
        
        tsIntent = Intent(this, TransferService::class.java)
        tsIntent?.putExtra(TransferService.INTENT_KEY_NOTIFICATION_ID, NOTIFICATION_ID)
        tsIntent?.putExtra(TransferService.INTENT_KEY_REMOVE_NOTIFICATION, true)
        // 传递上传所需的信息,让TransferService自己构建通知
        tsIntent?.putExtra(TransferService.INTENT_KEY_UPLOAD_INFO, uploadInfo)
        
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            startForegroundService(tsIntent)
        } else {
            startService(tsIntent)
        }
    } catch (exc: Exception) {
        if (exc is CancellationException) {
            throw exc
        }
        stopForeground(STOP_FOREGROUND_REMOVE)
        exc.printStackTrace()
    }
}

TransferService内部实现修改:

class TransferService : Service() {
    companion object {
        const val INTENT_KEY_NOTIFICATION_ID = "notification_id"
        const val INTENT_KEY_REMOVE_NOTIFICATION = "remove_notification"
        const val INTENT_KEY_UPLOAD_INFO = "upload_info"
        // 确保通知渠道在Oreo及以上版本已创建
        private const val UPLOAD_CHANNEL_ID = "upload_channel"
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        intent?.let {
            val notificationId = it.getIntExtra(INTENT_KEY_NOTIFICATION_ID, 0)
            val uploadInfo = it.getParcelableExtra<UploadInfo>(INTENT_KEY_UPLOAD_INFO) ?: return START_NOT_STICKY

            // 构建初始上传通知
            val notificationBuilder = NotificationCompat.Builder(this, UPLOAD_CHANNEL_ID)
                .setContentTitle("正在上传:${uploadInfo.fileName}")
                .setSmallIcon(R.drawable.ic_upload)
                .setProgress(100, 0, false)
                .setOngoing(true)

            // 启动前台服务
            startForeground(notificationId, notificationBuilder.build())

            // 启动上传任务并更新进度
            startUploadTask(uploadInfo, notificationId)
        }
        return START_STICKY
    }

    private fun startUploadTask(uploadInfo: UploadInfo, notificationId: Int) {
        val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
        // 模拟上传进度,实际替换为Amazon TransferService的进度监听逻辑
        CoroutineScope(Dispatchers.IO).launch {
            for (progress in 0..100 step 10) {
                delay(500)
                // 更新通知进度
                val updatedBuilder = NotificationCompat.Builder(this@TransferService, UPLOAD_CHANNEL_ID)
                    .setContentTitle("正在上传:${uploadInfo.fileName}")
                    .setSmallIcon(R.drawable.ic_upload)
                    .setProgress(100, progress, false)
                    .setOngoing(true)
                notificationManager.notify(notificationId, updatedBuilder.build())
            }
            // 上传完成后更新通知
            val finishedBuilder = NotificationCompat.Builder(this@TransferService, UPLOAD_CHANNEL_ID)
                .setContentTitle("上传完成:${uploadInfo.fileName}")
                .setSmallIcon(R.drawable.ic_upload_done)
                .setOngoing(false)
            notificationManager.notify(notificationId, finishedBuilder.build())

            // 停止前台服务并移除通知(如果需要)
            if (intent?.getBooleanExtra(INTENT_KEY_REMOVE_NOTIFICATION, false) == true) {
                stopForeground(STOP_FOREGROUND_REMOVE)
                stopSelf()
            }
        }
    }

    override fun onBind(intent: Intent?): IBinder? = null
}

// 自定义上传信息类,实现Parcelable用于Intent传递
data class UploadInfo(val fileName: String, val fileSize: Long) : Parcelable {
    constructor(parcel: Parcel) : this(
        parcel.readString() ?: "",
        parcel.readLong()
    )

    override fun writeToParcel(parcel: Parcel, flags: Int) {
        parcel.writeString(fileName)
        parcel.writeLong(fileSize)
    }

    override fun describeContents(): Int = 0

    companion object CREATOR : Parcelable.Creator<UploadInfo> {
        override fun createFromParcel(parcel: Parcel): UploadInfo {
            return UploadInfo(parcel)
        }

        override fun newArray(size: Int): Array<UploadInfo?> {
            return arrayOfNulls(size)
        }
    }
}

关键说明

  • 停止原服务前台状态时使用STOP_FOREGROUND_REMOVE,确保原通知被移除,避免与新通知冲突
  • 让TransferService自行管理通知,直接使用NotificationManager.notify()更新进度,确保Android 14中通知状态能正确同步
  • 遵守Oreo及以上版本的前台服务规范,使用startForegroundService启动服务,同时在TransferService的onStartCommand中及时调用startForeground(需在5秒内完成,避免ANR)

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.27 22:46:10