Administrator
发布于 2026-08-25 / 25 阅读
0
0

系列文章《Android 工程师的 100 篇进阶笔记》第 66 篇:Android 前台服务与后台限制:能让 App 不死的代价

上一篇讲了 Systrace/Perfetto 性能分析,这篇切到 Android 后台治理——前台服务(Foreground Service)、后台限制、WorkManager 选型。Android 14/15 在这一块越来越严格,是 App 上线被驳回的高频原因之一。

Android 后台策略演变

版本

关键变化

Android 4.4

App 进后台还能继续 5 秒

Android 6

Doze 模式首次引入

Android 7

隐式广播静态注册失效

Android 8

后台执行限制(Implicit Broadcast Limitation),FOO 必须用 FGS

Android 12

Foreground Service 启动限制(不能从后台启动 FGS)

Android 13

部分 FGS 类型需要额外权限

Android 14

FGS 类型强制声明

Android 15

health、dataSync 等限制使用时长

前台服务原理

FGS 和普通 Service 的区别:

普通 Service:
  用户看不到,资源紧张时被系统杀

前台服务(Foreground Service):
  1. 系统显示通知(用户可感知)
  2. 优先级 = 重要性高,被杀概率低
  3. 但必须声明 type

Android 14 的类型强制声明

Android 14 之前,FGS 没有 type 也能跑。Android 14 开始编译时强制:

<!-- AndroidManifest.xml -->
<service
    android:name=".MusicPlaybackService"
    android:foregroundServiceType="mediaPlayback"
    android:exported="false" />

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

所有 type 对应权限:

type

权限

场景

mediaPlayback

FOREGROUND_SERVICE_MEDIA_PLAYBACK

音乐播放

mediaProjection

FOREGROUND_SERVICE_MEDIA_PROJECTION

录屏

location

FOREGROUND_SERVICE_LOCATION

位置上报

dataSync

FOREGROUND_SERVICE_DATA_SYNC

数据同步

camera

FOREGROUND_SERVICE_CAMERA

自定义相机

phoneCall

FOREGROUND_SERVICE_PHONE_CALL

通话

health

FOREGROUND_SERVICE_HEALTH

健康数据

remoteMessaging

FOREGROUND_SERVICE_REMOTE_MESSAGING

消息(推送)

systemExempted

FOREGROUND_SERVICE_SYSTEM_EXEMPTED

系统级

microphone

FOREGROUND_SERVICE_MICROPHONE

录音

connectedDevice

FOREGROUND_SERVICE_CONNECTED_DEVICE

蓝牙设备

shortService

-

3 分钟内的短期任务

混合类型用 |:

android:foregroundServiceType="mediaPlayback|dataSync"

启动 FGS 的代码

// 启动前台服务
val intent = Intent(context, MusicService::class.java).apply {
    putExtra("track_id", trackId)
}

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    context.startForegroundService(intent)
} else {
    context.startService(intent)
}

// Service 内部
class MusicService : Service() {
    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        val trackId = intent?.getStringExtra("track_id")

        // Android 14+ 必须立即调用 startForeground(5 秒内)
        val notification = createNotification(
            title = "正在播放",
            text = "歌曲标题",
            channelId = "media_playback"
        )

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
            startForeground(
                1,
                notification,
                ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK
            )
        } else {
            startForeground(1, notification)
        }

        // 继续工作
        // playMusic(trackId)
        return START_STICKY
    }
}

5 秒不调用 startForeground 直接 ForegroundServiceDidNotStartInTimeException。

Android 12:后台启动限制

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
    // Android 12+:从后台启动 FGS 受限制
    val canStart = context.packageManager.isForegroundServiceAllowed()
    if (!canStart) {
        // 后台启动被禁
    }
}

只有以下情况能从后台启动 FGS:

  • App 还在前台 / 刚从前台退到后台

  • 屏幕关 / 但 App 是 active 时

  • 后台白名单(OEM 设置)

  • 用户操作触发(点击通知)

否则系统抛异常。

WorkManager 替代 FGS

WorkManager 是 Google 推荐的"后台工作"框架,特点:

  • 不需要 FGS(不需要通知)

  • 能保证最终执行

  • 受系统约束,重启后继续

// 一次性同步
val syncRequest = OneTimeWorkRequestBuilder<SyncWorker>()
    .setConstraints(
        Constraints.Builder()
            .setRequiredNetworkType(NetworkType.CONNECTED)
            .setRequiresBatteryNotLow(true)
            .build()
    )
    .build()

WorkManager.getInstance(context).enqueue(syncRequest)

class SyncWorker(ctx: Context, params: WorkerParameters) : CoroutineWorker(ctx, params) {
    override suspend fun doWork(): Result {
        // 同步逻辑
        return if (success) Result.success()
        else Result.retry()
    }
}

常用 task 类型:

类型

API

一次性任务

OneTimeWorkRequest

周期任务

PeriodicWorkRequest

链式任务

WorkContinuation

急停非实时

Expedited Work

Expedited Work(Android 12+)

如果工作量紧急但又不想弹 FGS 通知:

val urgentRequest = OneTimeWorkRequestBuilder<UrgentWorker>()
    .setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
    .build()

WorkManager.getInstance(context).enqueue(urgentRequest)

不用 FGS 通知,不受 FGS 限制。但配额有限,多次触发会被系统降级。

不同场景的选型

场景

推荐方案

音乐/视频播放

FGS (mediaPlayback)

后台位置(导航)

FGS (location)

数据同步

WorkManager 或 FGS (dataSync)

用户操作后续工作

WorkManager 普通任务

推送接收

FCM,不开 FGS

定期清理缓存

WorkManager Periodic

上传文件

WorkManager 或 FGS

即时聊天语音

FGS (microphone)

实战中的常见反模式

1. 用 FGS 保活

// 反模式
class KeepAliveService : Service() {
    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        startForeground(1, Notification())
        
        // 启动一个 alarm,每 1 分钟拉一次自己
        Handler(Looper.getMainLooper()).postDelayed({
            startService(Intent(this, KeepAliveService::class.java))
        }, 60_000)
        
        return START_STICKY
    }
}

这种代码以前能跑,Android 12+ 大多数 case 直接 ANR 或异常。

合理替代:

老做法

新做法

周期拉(service)

WorkManager Periodic

Alarm 启动

AlarmManager + WorkManager

后台接收广播

FCM 推送

服务互保活

不要做,让用户自然使用

2. 不必要的 FGS

很多场景不需要 FGS:

// 错误:用 FGS 跑下载
class DownloadService : Service() {
    override fun onStartCommand(...): Int {
        startForeground(1, notification())
        downloadFile() // 500MB 文件下载
        return START_STICKY
    }
}

// 正确:用 WorkManager
class DownloadWorker(...) : CoroutineWorker(...) {
    override suspend fun doWork(): Result {
        downloadFile()
        return Result.success()
    }
}

下载只是 WorkManager 任务,不需要在前台跑。

3. 后台场景启动 Service

// 错误:App 在后台,启动 FGS
val intent = Intent(this, MyFGS::class.java)
startForegroundService(intent) // Android 12+ 抛异常

判断自己是否在前台:

if (ProcessLifecycleOwner.get().lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) {
    startForegroundService(intent)
}

或者用 WorkManager 一劳永逸。

Android 13+ 的权限变化

通知权限(Android 13+)

// Android 13+ 必须声明通知权限
val permission = android.Manifest.permission.POST_NOTIFICATIONS
if (ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) {
    requestPermissions(arrayOf(permission), 100)
}

FGS 通知显示在状态栏,必须申请通知权限。

健康数据权限(Android 14+)

<uses-permission android:name="android.permission.HEALTH_DATA" /> 是健康数据权限,包括运动、身体数据。需要 USER_FITNESS 申请:

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

实战案例:定位打卡

App 用户打卡有"位置"和"后台"两个要求。怎么做?

class LocationCheckinWorker(
    context: Context,
    params: WorkerParameters
) : CoroutineWorker(context, params) {
    override suspend fun doWork(): Result {
        val fusedLocationClient = LocationServices.getFusedLocationProviderClient(applicationContext)

        val location: Location? = try {
            suspendCancellableCoroutine { continuation ->
                fusedLocationClient.lastLocation
                    .addOnSuccessListener { loc ->
                        continuation.resume(loc)
                    }
                    .addOnFailureListener { e ->
                        continuation.resume(null)
                    }
            }
        } catch (e: SecurityException) {
            null
        }

        if (location == null) return Result.retry()

        // 验证位置是否在打卡范围内
        return if (isInCheckinRange(location)) {
            api.checkin(location.latitude, location.longitude)
            Result.success()
        } else {
            Result.failure()
        }
    }
}

// 调度
val checkinWork = PeriodicWorkRequestBuilder<LocationCheckinWorker>(
    30, TimeUnit.MINUTES
)
    .setConstraints(
        Constraints.Builder()
            .setRequiresLocationAccuracy(LocationAccuracy.HIGH)
            .build()
    )
    .build()

WorkManager.getInstance(context)
    .enqueueUniquePeriodicWork(
        "location_checkin",
        ExistingPeriodicWorkPolicy.KEEP,
        checkinWork
    )

这套做法:

  • 不弹 FGS 通知:用户不需要被打扰

  • 不依赖前台应用:App 杀掉也会按周期执行

  • 权限要求低:只需要 ACCESS_BACKGROUND_LOCATION

监控 ANR 和 90 死亡

ANR 在 Android 14 提升到 5 秒,背景下 FGS ANR 直接崩溃:

ForegroundServiceDidNotStartInTimeException

避免办法:

  1. 不做超过 5 秒的工作在 FGS 里,超过就用 WorkManager

  2. onStartCommand 立即 startForeground,复杂工作异步处理

  3. 不要在 FGS onStartCommand 里网络/IO

小结

Android 后台治理经历了 8 年演变,变得非常严格。FGS 类型、通知权限、后台启动限制、ANR 触发,组合起来能把"想保活"的 App 全部干掉。正确思路:用 WorkManager 做不需要实时的工作;用 FGS 仅做必须的用户可见工作(音乐、定位、相机)。不要被旧文档误导,Android 14+ 已经把所有"流氓后台"的路堵了。

下一篇聊聊 WebView 性能优化与 JSBridge,这是 H5 嵌入原生场景的硬技能。


评论