Android 工业终端保活实战:前台服务 + 开机自启 + 更新自启的三重保障
适合场景:定制 Android 设备(工业平板、记录仪、自助终端等)需要 7×24 小时运行后台任务,定时上报设备状态。
一、背景
我们的应用运行在海康定制的 Android 工业终端上,核心职责是每 10 秒向服务器上报一次设备状态(电量、IP、序列号、定位等)。这类设备没有用户交互,App 必须"永远活着"。
消费级 App 谈"保活"是对抗系统,工业终端谈"保活"是合规地利用系统机制。本文介绍我们在生产环境中验证有效的三重保障方案。
二、整体方案
scss
开机 ──→ BootCompletedReceiver ──→ startForegroundService()
│
应用更新 ──→ AppUpdateReceiver ──→ startForegroundService()
│
▼
MyBackgroundService(前台服务)
START_STICKY + Handler 定时循环
三、第一重:前台服务 + START_STICKY
Android 8.0(API 26)之后,后台启动 Service 会被系统直接抛 IllegalStateException,必须走前台服务(Foreground Service),并在 5 秒内调用 startForeground() 挂出通知:
kotlin
class MyBackgroundService : Service() {
private val mHandler = Handler(Looper.getMainLooper())
private val mRunnable = Runnable {
try {
// 核心业务:上报设备状态 + 检查版本更新
viewModel?.sendMsg(this, longitude, latitude)
viewModel?.checkVersion(this)
} catch (e: Exception) {
// 注意:定时任务内必须 catch 所有异常,
// 否则一次崩溃会导致整个循环停止
}
restartCommand() // 无论成功失败,都调度下一次
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel(
"service_channel", "后台服务", NotificationManager.IMPORTANCE_HIGH
)
getSystemService(NotificationManager::class.java)
.createNotificationChannel(channel)
val notification = Notification.Builder(this, "service_channel")
.setContentTitle("服务运行中")
.setSmallIcon(R.mipmap.app_icon)
.build()
startForeground(1, notification)
}
restartCommand()
return START_STICKY // 关键:服务被系统杀掉后,系统会尝试重启
}
private fun restartCommand() {
mHandler.removeCallbacks(mRunnable)
// 间隔存数据库,支持远程/界面动态调整
mHandler.postDelayed(mRunnable, getDuration() * 1000L)
}
}
几个关键细节:
START_STICKY:系统内存不足杀掉服务后,会在资源允许时重建服务(intent 为 null,注意判空)。- 定时用
Handler.postDelayed循环而非Timer:Timer在异常后会终止整个调度,而 Runnable 末尾重新postDelayed,配合全局 try-catch,保证单次任务异常不会中断后续轮询。 - 轮询间隔存数据库(我们用 LitePal),可以随时改,不用发版。
四、第二重:开机自启
监听 BOOT_COMPLETED 广播。注意两点:需要声明 RECEIVE_BOOT_COMPLETED 权限;Android 8.0+ 必须用 startForegroundService():
kotlin
class BootCompletedReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
if (context == null || intent == null) return
if (Intent.ACTION_BOOT_COMPLETED == intent.action) {
val serviceIntent = Intent(context, MyBackgroundService::class.java)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
context.startForegroundService(serviceIntent)
} else {
context.startService(serviceIntent)
}
}
}
}
xml
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<receiver
android:name=".BootCompletedReceiver"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
注意:Android 12+ 对"后台启动前台服务"有限制,但开机广播属于豁免场景之一,仍然可以启动。前提是应用至少被用户打开过一次(处于非 force-stopped 状态)。
五、第三重:应用更新后自启(最容易被忽略的一环)
设备上的 App 会远程推送 APK 自动更新。覆盖安装后,进程必然被杀,前台服务也没了------如果只依赖开机广播,设备不重启服务就一直挂着,状态上报中断。
解决方案是监听 ACTION_MY_PACKAGE_REPLACED(这个广播只发给"被更新的应用自己",不需要额外权限):
kotlin
class AppUpdateReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
when (intent.action) {
Intent.ACTION_MY_PACKAGE_REPLACED -> {
// 应用覆盖安装完成,重新拉起后台服务
val serviceIntent = Intent(context, MyBackgroundService::class.java)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
context.startForegroundService(serviceIntent)
} else {
context.startService(serviceIntent)
}
}
}
}
}
xml
<receiver
android:name=".AppUpdateReceiver"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MY_PACKAGE_REPLACED" />
</intent-filter>
</receiver>
这个广播同样是后台启动前台服务的豁免场景,实测 Android 13 的定制设备上稳定可用。
六、资源释放与防抖
onDestroy() 里要做完整的清理,否则服务重启后会累积多个定时器:
kotlin
override fun onDestroy() {
mHandler.removeCallbacks(mRunnable) // 移除定时任务
HikLocationManager.stop() // 解绑外部 SDK 服务
KafkaLocationManager.stop() // 停掉 Kafka 消费线程
super.onDestroy()
}
另外 restartCommand() 里先 removeCallbacks 再 postDelayed,保证任何时刻最多只有一个待执行任务,避免 onStartCommand 被多次调用导致任务叠加。
七、总结
| 保障层 | 机制 | 覆盖场景 |
|---|---|---|
| 前台服务 | startForeground + 常驻通知 |
防 LMK 低优先级查杀 |
| START_STICKY | 系统重建服务 | 被系统杀掉后恢复 |
| 开机广播 | BOOT_COMPLETED |
设备重启 |
| 更新广播 | MY_PACKAGE_REPLACED |
APK 覆盖安装 |
这套方案面向的是自有硬件、自有签名的工业场景。如果你的 App 要上应用市场、跑在杂牌手机上,厂商的省电策略(自启动白名单、后台限制)才是最大的敌人,那就是另一个话题了。