如何在Kotlin中通过通知.addAction结合PendingIntent与BroadcastReceiver停止前台服务
解决方案
一、修正PendingIntent的创建
你的PendingIntent逻辑框架没问题,但在Android 12及以上系统中,必须指定PendingIntent.FLAG_IMMUTABLE或FLAG_MUTABLE(仅当需要动态修改Intent时使用后者),否则会抛出运行时异常。另外建议将action定义为全局常量,避免拼写错误:
// 在MyForegroundServices类外部定义常量,替换成你的应用包名前缀 const val ACTION_STOP_FOREGROUND = "com.your.app.package.STOP_FOREGROUND_SERVICE" // 修改MyForegroundServices中PendingIntent的创建代码: val foregroundIntent = Intent(this, NotificationReceiver::class.java).apply { action = ACTION_STOP_FOREGROUND } val foregroundPendingIntent = PendingIntent.getBroadcast( this, 0, foregroundIntent, PendingIntent.FLAG_IMMUTABLE // Android 12+ 必填 )
二、注册BroadcastReceiver
必须在AndroidManifest.xml中静态注册NotificationReceiver,否则系统无法识别并分发广播:
<receiver android:name=".NotificationReceiver" android:exported="false"> <!-- 仅内部使用,设为false提升安全性 --> <intent-filter> <action android:name="com.your.app.package.STOP_FOREGROUND_SERVICE" /> </intent-filter> </receiver>
三、实现NotificationReceiver的onReceive方法
BroadcastReceiver不能直接调用服务的内部方法,我们通过发送带特定action的Intent通知服务自行停止:
import android.content.Context import android.content.Intent import android.os.Build import android.content.BroadcastReceiver class NotificationReceiver : BroadcastReceiver() { override fun onReceive(context: Context?, intent: Intent?) { intent?.takeIf { it.action == ACTION_STOP_FOREGROUND }?.let { val stopServiceIntent = Intent(context, MyForegroundServices::class.java).apply { action = ACTION_STOP_FOREGROUND } // Android O及以上停止前台服务需调用startForegroundService if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { context?.startForegroundService(stopServiceIntent) } else { context?.startService(stopServiceIntent) } } } }
四、修改MyForegroundServices处理停止逻辑
- 将后台线程保存为成员变量,方便后续中断:
class MyForegroundServices : Service() { private var serviceThread: Thread? = null // ... 其他代码 }
- 完善
onStartCommand方法,处理停止指令并修正线程循环逻辑:
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { // 优先处理停止指令 intent?.takeIf { it.action == ACTION_STOP_FOREGROUND }?.let { stopForeground(true) // 移除前台通知 stopSelf() // 销毁服务 serviceThread?.interrupt() // 中断后台线程 return START_NOT_STICKY } // 仅当线程未启动时创建新线程 if (serviceThread == null || !serviceThread!!.isAlive) { serviceThread = Thread { try { // 用isInterrupted判断循环,确保线程能正常退出 while (!Thread.currentThread().isInterrupted) { Log.e("Service", "Service is running...") Thread.sleep(2000) } } catch (e: InterruptedException) { Log.e("Service", "Thread interrupted, exiting...") } } serviceThread?.start() } // 保留原有通知创建逻辑 val CHANNELID = "Foreground Service ID" val channel = NotificationChannel( CHANNELID, CHANNELID, NotificationManager.IMPORTANCE_LOW ) val foregroundIntent = Intent(this, NotificationReceiver::class.java).apply { action = ACTION_STOP_FOREGROUND } val foregroundPendingIntent = PendingIntent.getBroadcast( this, 0, foregroundIntent, PendingIntent.FLAG_IMMUTABLE ) getSystemService(NotificationManager::class.java).createNotificationChannel(channel) val notification = Notification.Builder(this, CHANNELID) .setContentText("Service is running") .setContentTitle("Service enabled") .setSmallIcon(R.drawable.ic_launcher_background) .addAction(R.drawable.ic_launcher_background, "Stop", foregroundPendingIntent) .build() startForeground(1001, notification) return START_STICKY }
- 重写
onDestroy方法,确保线程被中断:
override fun onDestroy() { super.onDestroy() serviceThread?.interrupt() }
五、额外注意事项
ACTION_STOP_FOREGROUND的前缀必须和你的应用包名一致,避免与其他应用广播冲突。- 后台线程的循环条件必须检查
Thread.currentThread().isInterrupted,否则调用interrupt()后线程会继续运行,导致内存泄漏。
内容的提问来源于stack exchange,提问作者new developer
相关产品推荐
相关产品推荐

