如何在前台服务中安全地接收系统广播(如网络变化)?

解读

面试官想确认两点:

  1. 国内 ROM 对后台启动、常驻、省电的“三重围剿”下,前台服务是否真能长期存活;
  2. 在 Android 8.0 隐式广播限制、SELinux 强制、国内厂商自定义权限、用户手动关闭“自启动”等现实条件下,如何既合规又稳定地拿到网络变化事件。
    回答必须体现“前台服务保活 + 广播接收器注册方式 + 权限与省电策略 + 国内适配”四条主线,否则会被追问“为什么我的服务 5 分钟就被杀了”。

知识点

  1. Context.registerReceiver() 动态注册不受隐式广播限制,但存活周期与 Service 一致;
  2. 前台服务必须挂与业务相关的、用户可感知的通知,且 id 不能为 0,否则国内 ROM 直接抛 ForegroundServiceStartNotAllowedException;
  3. 网络变化广播:
    • CONNECTIVITY_ACTION(android.net.conn.CONNECTIVITY_CHANGE)已加入隐式广播黑名单,静态注册无法收到;
    • 替代方案:NetworkCallback + ConnectivityManager.registerNetworkCallback,7×24 有效且省电;
  4. 国内特殊权限:OPPO/vivo 的“后台弹出界面”、小米“自启动”、华为“应用启动管理”,需在设置中引导用户白名单;
  5. 高版本 Android(12+)对“启动前台服务”要求调用 startForegroundService() 后 5 s 内必须 startForeground(),否则 ANR;
  6. 安全侧:Intent 可能携带恶意 extra,接收后需校验 NetworkInfo != null、getType() 合法,再访问业务接口,防止拒绝服务攻击;
  7. 省电侧:网络变化频率极高,需在子线程或 Handler 中处理,避免阻塞 UI 线程或导致系统频繁唤醒。

答案

步骤一:启动前台服务
在 Activity 或 Application 中调用

ContextCompat.startForegroundService(context, Intent(context, NetMonitorService::class.java))

Service 的 onStartCommand() 里 1 ms 内执行

startForeground(1001, createNotification())

返回 START_STICKY,确保被杀后系统会重拉(国内 ROM 可能无效,需结合 JobScheduler 双重保活)。

步骤二:动态注册网络回调(推荐,Android 8.0+)
在 Service onCreate() 中:

val cm = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val request = NetworkRequest.Builder()
        .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
        .build()
cm.registerNetworkCallback(request, networkCallback)

networkCallback 对象实现 ConnectivityManager.NetworkCallback(),在 onAvailable()/onLost() 中发送本地广播或直接处理业务,避免使用全局广播。

步骤三:兼容低版本动态广播(可选,Android 7.0 及以下)

val filter = IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)
registerReceiver(netReceiver, filter)

并在 onDestroy() 中 unregisterReceiver(netReceiver),防止泄漏。

步骤四:权限声明与引导
Manifest 中仅保留最小权限:

<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>

Android 13+ 如需访问精细网络状态,可追加

<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>

首次启动用 Dialog+Intent 引导用户关闭“省电优化”并打开“自启动”,否则服务随时被冻结。

步骤五:安全防护
收到回调后先校验

val net = cm.activeNetwork
val caps = cm.getNetworkCapabilities(net)
caps?.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED) == true

再执行业务,防止空指针或伪网络事件;所有网络 I/O 放协程或线程池,避免耗时落在主线程。

步骤六:资源释放
Service 的 onDestroy() 中

cm.unregisterNetworkCallback(networkCallback)

确保不会留下悬空回调导致内存泄漏或系统拒绝再次注册。

通过以上流程,既满足 Android 8.0 隐式广播限制,又能在国内 ROM 的“高耗电”监控下长期存活,且对用户零骚扰、对系统零风险。

拓展思考

  1. 双进程保活:将 NetMonitorService 放在:remote 进程,主进程保活用 JobScheduler/WorkManager 每 15 min 拉一次,双进程互相唤醒,降低被系统一次性杀双的概率;
  2. 监听“用户手动关闭 Wi-Fi”场景:NetworkCallback 的 onCapabilitiesChanged() 中判断 TRANSPORT_WIFI 丢失,可即时提示“已切换至移动数据”;
  3. 折叠屏/多窗口场景:Service 生命周期与可见性无关,但网络请求需感知最大并发网络数,避免大屏同时下载导致资费激增;
  4. 隐私沙盒与后台定位新限制:Android 14 引入“仅前台可访问精确位置”,若网络变化后要上传带坐标日志,需动态申请 ACCESS_FINE_LOCATION 并确保 Service 在前台;
  5. 国内厂商“省电白名单”适配脚本:在 CI 阶段自动解析各大 ROM 的隐藏 Intent(如 miui.intent.action.POWER_HIDE_MODE_APP),打包时动态写入 Manifest,降低运营手动配置成本。