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

Android后台Service使用OkHttp-SSE接收SSE事件异常求助

Android后台Service接收SSE事件失效的解决方案

问题根源

  1. Android 8.0+后台限制:系统会主动限制后台非前台Service的资源占用,甚至直接杀死进程,导致SSE长连接中断。
  2. 未启用前台Service:当前Service没有通过前台通知声明为重要后台任务,不符合系统后台运行要求。
  3. 代码冗余:创建EventSource后又发起了额外的client.newCall(request).enqueue请求,既浪费资源又可能干扰SSE连接。
  4. EventSource无全局引用:局部变量的EventSource容易被GC回收,导致连接意外断开。

具体解决方案

1. 将Service升级为前台Service

在Android 8.0及以上版本,只有前台Service能长期在后台运行,必须显示一个持续的通知。修改NotificationService.kt:

class NotificationService : Service() {
    private val notifChannelId = "RD_N_D_C"
    private var eventSource: EventSource? = null
    private val TAG = "NotificationService"

    override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
        createNotificationChannel()
        
        // 构建前台服务通知(必须持续显示,不能手动取消)
        val foregroundNotification = NotificationCompat.Builder(this, notifChannelId)
            .setSmallIcon(R.drawable.notification_no_bg)
            .setContentTitle("后台通知服务")
            .setContentText("正在接收服务器推送")
            .setPriority(NotificationCompat.PRIORITY_LOW)
            .setOngoing(true)
            .build()
        
        // 启动前台服务
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            startForeground(1001, foregroundNotification)
        } else {
            startForeground(1001, foregroundNotification)
        }

        // SSE事件监听逻辑
        val eventSourceListener = object : EventSourceListener() {
            override fun onEvent(eventSource: EventSource, id: String?, type: String?, data: String) {
                super.onEvent(eventSource, id, type, data)
                Log.e(TAG, "收到通知事件")
                val dataObj = JSONTokener(data).nextValue() as JSONObject

                val builder = NotificationCompat.Builder(this@NotificationService, notifChannelId)
                    .setSmallIcon(R.drawable.notification_no_bg)
                    .setContentTitle(dataObj.optString("title"))
                    .setContentText(dataObj.optString("text"))
                    .setPriority(NotificationCompat.PRIORITY_DEFAULT)
                    .setAutoCancel(true)

                NotificationManagerCompat.from(this@NotificationService)
                    .notify(Random.nextInt(100000, 999999), builder.build())
            }

            override fun onClosed(eventSource: EventSource) {
                Log.e(TAG, "SSE连接关闭,准备重连")
                super.onClosed(eventSource)
                reconnectSSE()
            }

            override fun onFailure(eventSource: EventSource, t: Throwable?, response: Response?) {
                Log.e(TAG, "SSE连接失败:${t?.message},准备重连")
                super.onFailure(eventSource, t, response)
                reconnectSSE()
            }
        }

        // 初始化OkHttpClient和SSE请求
        val client = OkHttpClient.Builder()
            .connectTimeout(5, TimeUnit.SECONDS)
            .readTimeout(10, TimeUnit.MINUTES)
            .writeTimeout(10, TimeUnit.MINUTES)
            .build()

        val request = Request.Builder()
            .url("https://random.website.that/sends/sse/events")
            .header("Accept", "text/event-stream") // 只保留SSE所需的Accept头
            .build()

        // 全局持有EventSource,避免被GC回收
        eventSource = EventSources.createFactory(client)
            .newEventSource(request, eventSourceListener)

        // 移除冗余的enqueue请求,这会发起额外的非SSE HTTP请求
        // client.newCall(request).enqueue(...) 此行删除

        return START_STICKY
    }

    // SSE重连逻辑
    private fun reconnectSSE() {
        Handler(Looper.getMainLooper()).postDelayed({
            val client = OkHttpClient.Builder()
                .connectTimeout(5, TimeUnit.SECONDS)
                .readTimeout(10, TimeUnit.MINUTES)
                .writeTimeout(10, TimeUnit.MINUTES)
                .build()

            val request = Request.Builder()
                .url("https://random.website.that/sends/sse/events")
                .header("Accept", "text/event-stream")
                .build()

            val eventSourceListener = object : EventSourceListener() {
                // 复用之前的事件处理逻辑
                override fun onEvent(eventSource: EventSource, id: String?, type: String?, data: String) {
                    super.onEvent(eventSource, id, type, data)
                    Log.e(TAG, "收到通知事件(重连后)")
                    val dataObj = JSONTokener(data).nextValue() as JSONObject

                    val builder = NotificationCompat.Builder(this@NotificationService, notifChannelId)
                        .setSmallIcon(R.drawable.notification_no_bg)
                        .setContentTitle(dataObj.optString("title"))
                        .setContentText(dataObj.optString("text"))
                        .setPriority(NotificationCompat.PRIORITY_DEFAULT)
                        .setAutoCancel(true)

                    NotificationManagerCompat.from(this@NotificationService)
                        .notify(Random.nextInt(100000, 999999), builder.build())
                }

                override fun onClosed(eventSource: EventSource) {
                    Log.e(TAG, "SSE连接关闭,准备重连")
                    super.onClosed(eventSource)
                    reconnectSSE()
                }

                override fun onFailure(eventSource: EventSource, t: Throwable?, response: Response?) {
                    Log.e(TAG, "SSE连接失败:${t?.message},准备重连")
                    super.onFailure(eventSource, t, response)
                    reconnectSSE()
                }
            }

            eventSource = EventSources.createFactory(client)
                .newEventSource(request, eventSourceListener)
        }, 5000) // 5秒后重试,可根据需求调整
    }

    override fun onDestroy() {
        super.onDestroy()
        // 销毁时关闭SSE连接
        eventSource?.close()
    }

    // 应用从最近任务移除后重启Service
    override fun onTaskRemoved(rootIntent: Intent?) {
        super.onTaskRemoved(rootIntent)
        val intent = Intent(this, NotificationService::class.java)
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            startForegroundService(intent)
        } else {
            startService(intent)
        }
    }

    // 原有createNotificationChannel和onBind方法保留
    private fun createNotificationChannel() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            val channel = NotificationChannel(
                notifChannelId,
                "后台通知服务",
                NotificationManager.IMPORTANCE_LOW
            ).apply {
                description = "用于接收服务器推送通知的后台服务"
            }
            val notificationManager = getSystemService(NotificationManager::class.java)
            notificationManager.createNotificationChannel(channel)
        }
    }

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

2. 更新AndroidManifest.xml配置

添加必要权限,指定前台服务类型,优化Service安全性:

<uses-permission android:name="android.permission.INTERNET"/>
<!-- Android 9.0+需要前台服务权限 -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<!-- Android 13+需要通知权限 -->
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />

<service
    android:name=".NotificationService"
    android:enabled="true"
    android:exported="false" <!-- 不需要对外暴露,设为false更安全 -->
    android:foregroundServiceType="dataSync" <!-- Android 12+必须指定服务类型 -->
    android:process=":MyApp_Notifications"/> <!-- 若无需独立进程可删除此行 -->

3. 关键注意事项

  • 前台服务的通知必须设置为setOngoing(true),防止用户手动关闭导致Service被销毁。
  • 全局持有EventSource引用,避免被垃圾回收机制回收。
  • 添加重连逻辑处理网络波动或连接中断的情况,保证通知接收的连续性。
  • Android 12+必须指定foregroundServiceType,明确服务用途。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.04 03:25:35