通知与后台任务:让App在后台也能“活”起来

各位同学,今天我们来聊聊Android开发中一个非常“接地气”的话题——通知与后台任务。说白了,就是你的App在用户没打开它的时候,怎么跟用户“打招呼”,以及怎么在后台偷偷干活。

我刚开始做Android开发那会儿,对这块儿也是一头雾水。记得有一次,我写了个下载功能,用户一按Home键,下载就断了。用户跑来骂我:“你这App是不是有毛病?” 嗯,从那以后,我就把后台任务这块儿研究透了。

本章知识体系:通知与后台任务 Android 后台能力 通知渠道与样式 Android 8.0+ 必须设置 前台服务 + 通知 高优先级任务必备 WorkManager 延迟/可靠后台任务 JobScheduler & AlarmManager 系统级调度与定时任务 核心原则:省电、保活、用户体验

1. 通知渠道:Android 8.0 的分水岭

先说说通知。Android 8.0(API 26)之后,通知系统来了个大改版。你想想看,以前的通知多乱啊,所有App的通知都挤在一起,用户想关掉某个烦人的通知,只能一刀切——把整个App的通知全关了。

Google 觉得这样不行,于是引入了通知渠道(Notification Channel)。说白了,就是给通知分门别类。比如你的App有“订单通知”、“促销通知”、“系统通知”,每个都可以单独设置重要性、声音、震动等。

核心要点:创建通知必须先创建渠道,否则通知发不出来。这是强制要求。

来看代码怎么写:

// 创建通知渠道(一般在 Application 的 onCreate 中执行)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    val channel = NotificationChannel(
        "order_channel",      // 渠道ID
        "订单通知",            // 用户可见的渠道名称
        NotificationManager.IMPORTANCE_HIGH  // 重要性级别
    ).apply {
        description = "包含订单状态变更的通知"
        enableVibration(true)
        setSound(
            RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION),
            Notification.AUDIO_ATTRIBUTES_DEFAULT
        )
    }
    
    val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
    notificationManager.createNotificationChannel(channel)
}

这里有个坑,我踩过。渠道一旦创建,就不能再修改重要性级别了。用户可以在系统设置里改,但你的代码改不了。所以创建渠道前,一定要想好每个渠道的定位。

我的习惯:在 Application 的 onCreate 里统一创建所有渠道。如果渠道已经存在,系统会忽略重复创建,所以放心调用。

2. 通知样式:别让用户觉得你Low

通知不只是显示一行文字。Android 提供了好几种样式,让你的通知看起来更专业。

样式类型 适用场景 关键类
大文本样式 长消息、邮件正文 BigTextStyle
大图样式 图片分享、商品展示 BigPictureStyle
收件箱样式 聊天记录、邮件列表 InboxStyle
媒体样式 音乐播放、视频控制 MediaStyle
消息样式 即时通讯(带头像) MessagingStyle

举个例子,如果你要做个聊天App,用 MessagingStyle 会非常合适:

val message = Notification.MessagingStyle.Message(
    "你好,明天下午三点开会",
    System.currentTimeMillis(),
    "张三"
)

val style = Notification.MessagingStyle("我")
    .setConversationTitle("项目讨论组")
    .addMessage(message)

val notification = NotificationCompat.Builder(this, "chat_channel")
    .setStyle(style)
    .setSmallIcon(R.drawable.ic_chat)
    .build()

我个人建议,能用样式就别用默认的。用户第一眼看到的就是通知,样式好看了,用户对App的印象也会好很多。

3. 前台服务:让任务“光明正大”地跑

后台任务有个大问题:系统会杀进程。Android 从 8.0 开始,对后台执行限制越来越严。那如果你的App需要长时间运行一个任务,比如播放音乐、记录运动轨迹,怎么办?

答案就是前台服务(Foreground Service)。前台服务会显示一个持续的通知,告诉用户:“嘿,我正在后台干活呢,别杀我。”

注意:前台服务必须有一个对应的通知,而且这个通知不能被用户划掉。这是系统的硬性要求。

代码实现分三步:

  1. 创建通知渠道和通知
  2. 在 Service 的 onStartCommand 中调用 startForeground()
  3. onDestroy 中调用 stopForeground()
class MusicService : Service() {
    
    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        val notification = createNotification()
        startForeground(NOTIFICATION_ID, notification)
        // 开始播放音乐...
        return START_STICKY
    }
    
    private fun createNotification(): Notification {
        return NotificationCompat.Builder(this, "music_channel")
            .setContentTitle("正在播放")
            .setContentText("晴天 - 周杰伦")
            .setSmallIcon(R.drawable.ic_music)
            .setOngoing(true)  // 不可划掉
            .build()
    }
    
    override fun onDestroy() {
        stopForeground(true)  // true 表示移除通知
        super.onDestroy()
    }
    
    override fun onBind(intent: Intent?): IBinder? = null
}

我曾经在一个运动App里用过前台服务。用户开始跑步后,服务一直在后台记录GPS数据。如果不做前台服务,跑着跑着系统就把进程杀了,用户数据就丢了。那用户体验得多差啊。

4. WorkManager:最优雅的后台任务方案

说到后台任务,我得好好夸夸 WorkManager。这是 Google 官方推荐的方案,也是我目前在项目中最常用的。

为什么推荐它?因为它太聪明了。它会根据系统状态(电量、网络、空闲等)来决定什么时候执行任务。而且它保证任务一定会执行,哪怕App被杀了、手机重启了。

适用场景:上传日志、同步数据、定期清理缓存等不需要立即执行的任务。

来看一个简单的例子:

// 1. 定义任务
class UploadWorker(context: Context, params: WorkerParameters) : Worker(context, params) {
    override fun doWork(): Result {
        // 执行上传操作
        return try {
            uploadLogs()
            Result.success()
        } catch (e: Exception) {
            Result.retry()  // 失败后重试
        }
    }
}

// 2. 调度任务
val workRequest = OneTimeWorkRequestBuilder<UploadWorker>()
    .setConstraints(
        Constraints.Builder()
            .setRequiredNetworkType(NetworkType.CONNECTED)  // 需要网络
            .setRequiresBatteryNotLow(true)                  // 电量不能太低
            .build()
    )
    .setBackoffCriteria(
        BackoffPolicy.LINEAR,
        WorkRequest.MIN_BACKOFF_MILLIS,
        TimeUnit.MILLISECONDS
    )
    .build()

WorkManager.getInstance(this).enqueue(workRequest)

你想想看,以前我们做这种任务,得自己写Service、自己处理网络监听、自己管理重试逻辑。现在 WorkManager 全包了。我现在的项目里,所有非紧急的后台任务都用它。

避坑指南:WorkManager 的 doWork() 运行在后台线程,所以别在里面做UI操作。另外,任务默认是异步的,如果你需要同步执行,可以用 ListenableWorker

5. JobScheduler 与 AlarmManager:老牌选手的用武之地

虽然 WorkManager 很强大,但有些场景还是得用 JobSchedulerAlarmManager

JobScheduler 是 Android 5.0 引入的,它和 WorkManager 有点像,但更底层。如果你需要更精细地控制调度条件,可以用它。不过说实话,现在 WorkManager 底层就是用的 JobScheduler,所以大多数情况下直接用 WorkManager 就行。

AlarmManager 就不一样了。它是用来做精确的定时任务的。比如闹钟App,必须在指定时间响铃,这个 WorkManager 做不到。

// 设置一个30秒后的精确闹钟
val alarmManager = getSystemService(Context.ALARM_SERVICE) as AlarmManager
val intent = Intent(this, AlarmReceiver::class.java)
val pendingIntent = PendingIntent.getBroadcast(
    this, 0, intent, PendingIntent.FLAG_IMMUTABLE
)

alarmManager.setExactAndAllowWhileIdle(
    AlarmManager.RTC_WAKEUP,
    System.currentTimeMillis() + 30_000,
    pendingIntent
)

注意:从 Android 12 开始,精确闹钟需要申请 SCHEDULE_EXACT_ALARM 权限,而且用户可以在设置里关闭。所以别太依赖它。

我记得有一次,我做一个定时提醒的App,一开始用的 WorkManager,结果发现它不精确,有时候会延迟几分钟。后来换成 AlarmManager,问题就解决了。所以选工具要看场景。

总结一下

通知和后台任务这块儿,说白了就是平衡用户体验和系统限制。我的建议是:

  • 通知:一定要用渠道,样式尽量丰富
  • 前台服务:长时间运行的任务必须用,别偷懒
  • WorkManager:90%的后台任务用它就够了
  • AlarmManager:只有精确定时才用它

嗯,今天就讲到这里。这些知识点在实际项目中天天都会用到,大家一定要动手试试。代码写多了,自然就熟了。


公众号:蓝海资料掘金营,微信deep3321