Android后台Service使用OkHttp-SSE接收SSE事件异常求助
Android后台Service接收SSE事件失效的解决方案
问题根源
- Android 8.0+后台限制:系统会主动限制后台非前台Service的资源占用,甚至直接杀死进程,导致SSE长连接中断。
- 未启用前台Service:当前Service没有通过前台通知声明为重要后台任务,不符合系统后台运行要求。
- 代码冗余:创建EventSource后又发起了额外的
client.newCall(request).enqueue请求,既浪费资源又可能干扰SSE连接。 - 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
相关产品推荐
相关产品推荐

