Manifest注册的Broadcast Receiver在Android Pie上失效问题
嘿,我来帮你搞定这个头疼的问题!你说添加了前台常驻通知后,Marshmallow设备正常,但Pie设备哪怕App开着也收不到蓝牙状态变化的广播,这大概率是Android 8.0+对广播和后台服务的新限制搞的鬼,结合你的代码,我给你几个针对性的解决方案:
1. 把静态注册的广播改成动态注册
Android 8.0(API 26)开始,系统对静态注册的隐式广播管得特别严,虽然官方说BluetoothAdapter.ACTION_STATE_CHANGED属于例外,但不少厂商的定制ROM可能有额外限制,而且静态注册的接收器在App进程状态波动时容易被系统忽略。
建议你把广播接收器移到前台服务里动态注册,只要服务一直在前台跑,接收器就能稳稳监听蓝牙状态:
修改OngoingService.kt:
import android.app.* import android.content.Intent import android.content.IntentFilter import android.os.IBinder import android.support.v4.app.NotificationCompat import android.content.Context import android.os.Build import android.support.annotation.RequiresApi import android.support.v4.app.NotificationCompat.PRIORITY_MIN class OngoingService : Service() { private lateinit var bluetoothReceiver: BReceivers override fun onCreate() { super.onCreate() // 动态注册蓝牙广播接收器 bluetoothReceiver = BReceivers() val filter = IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED) registerReceiver(bluetoothReceiver, filter) } override fun onBind(intent: Intent): IBinder? { return null } @RequiresApi(Build.VERSION_CODES.LOLLIPOP) override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int { startForeground() return START_STICKY // 确保服务被意外杀死后能自动重启 } @RequiresApi(Build.VERSION_CODES.LOLLIPOP) private fun startForeground() { val channelId = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { initChannels("fearApp_service", "Fear Appeals Service") } else { "" } val notificationIntent = Intent(this, MainActivity::class.java) val pendingIntent = PendingIntent.getActivity( this, 0, notificationIntent, if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) PendingIntent.FLAG_IMMUTABLE else 0 ) val notificationBuilder = NotificationCompat.Builder(this, channelId ) val notification = notificationBuilder .setOngoing(true) .setContentTitle(getString(R.string.app_name)) .setContentText("Fear Appeals is running background") .setSmallIcon(R.mipmap.ic_launcher) .setPriority(PRIORITY_MIN) .setCategory(Notification.CATEGORY_SERVICE) .setContentIntent(pendingIntent) .build() startForeground(101, notification) } @RequiresApi(Build.VERSION_CODES.O) private fun initChannels(channelId: String, channelName: String): String { val channel = NotificationChannel( channelId, channelName, NotificationManager.IMPORTANCE_LOW // 改成LOW,和通知优先级匹配 ) channel.lockscreenVisibility = Notification.VISIBILITY_PRIVATE channel.description = "Ongoing notification to keep the app opened." val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager notificationManager.createNotificationChannel(channel) return channelId } override fun onDestroy() { super.onDestroy() // 服务销毁时记得注销接收器,避免内存泄漏 unregisterReceiver(bluetoothReceiver) } }
改完后,记得把AndroidManifest.xml里静态注册的<receiver>标签删掉,别重复注册啦。
2. 修正前台服务的启动方式
在Android 8.0+,启动前台服务必须用startForegroundService(),不能再用startService()了,不然系统会直接抛出异常,甚至把服务杀掉。快改一下OngoingApp.kt:
import android.app.Application import android.content.Intent import android.os.Build class OngoingApp : Application() { override fun onCreate() { super.onCreate() val serviceIntent = Intent(this, OngoingService::class.java) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { startForegroundService(serviceIntent) } else { startService(serviceIntent) } } }
3. 补全必要的权限(可选但建议)
虽然监听蓝牙状态本身不需要位置权限,但有些Android 9设备的系统限制比较严,要求App有位置权限才能接收蓝牙相关广播。你可以在AndroidManifest.xml里加上位置权限,然后在运行时申请:
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
再在MainActivity里加个权限申请的逻辑:
class MainActivity : AppCompatActivity() { private val PERMISSION_REQUEST_LOCATION = 100 override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) // 运行时申请位置权限(Android 6+需要) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (checkSelfPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION) != android.content.pm.PackageManager.PERMISSION_GRANTED) { requestPermissions(arrayOf(android.Manifest.permission.ACCESS_COARSE_LOCATION), PERMISSION_REQUEST_LOCATION) } } } override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) { super.onRequestPermissionsResult(requestCode, permissions, grantResults) if (requestCode == PERMISSION_REQUEST_LOCATION) { if (grantResults.isNotEmpty() && grantResults[0] == android.content.pm.PackageManager.PERMISSION_GRANTED) { // 权限拿到了,正常使用就行 } else { // 用户拒绝了权限,可能影响蓝牙广播接收,建议提示用户开启 } } } }
4. 优化通知渠道的配置
你之前的通知渠道用了IMPORTANCE_DEFAULT,但通知优先级是PRIORITY_MIN,两者不匹配,可能导致通知显示不正常,进而影响前台服务的稳定性。我已经在上面的OngoingService代码里把渠道重要性改成了IMPORTANCE_LOW,和PRIORITY_MIN对应上了,这样就没问题啦。
最后总结
按照上面的步骤改完,应该就能解决Pie设备上蓝牙广播接收器失效的问题了。核心原因就是Android 8.0+对静态广播和后台服务的限制,改用动态注册广播接收器+正确启动前台服务是最靠谱的解决方案。
内容的提问来源于stack exchange,提问作者dmelton_

