4、App Standby机制:App Standby的分级策略、待机桶管理与应用适配

App Standby,说白了就是Android系统用来管住那些「不老实」的应用的。我刚开始接触这个机制时,觉得它不就是个简单的后台限制嘛。后来深入一看,才发现里面的门道真不少。

你想想看,手机里装了上百个App,每个都想在后台搞点小动作。有的定时唤醒,有的频繁联网,有的疯狂拉活。系统要是放任不管,电池早就扛不住了。App Standby就是干这个的——它把应用分门别类,区别对待。

4.1 待机桶的分级策略

Android把应用分成了五个等级,每个等级对应一个「桶」。桶的编号越低,受到的约束越少。我习惯把这五个桶想象成五个「待遇区」:

桶名称 优先级 典型行为 后台限制
ACTIVE 最高 用户正在使用的应用 无限制
WORKING_SET 经常使用但当前不在前台 轻微延迟
FREQUENT 定期使用,但不是每天 网络访问受限
RARE 很久没用过的应用 严格限制,延迟作业
NEVER 最低 从未使用或已卸载残留 几乎完全禁止后台

这里有个细节要注意——系统不是一成不变地把应用扔进某个桶就不管了。它会根据用户的使用习惯动态调整。比如你每天早上打开天气App,系统就会把它从RARE桶提到FREQUENT桶。反过来,一个App你一个月没碰,它就会慢慢沉到RARE桶里去。

核心逻辑:系统通过「使用频率 + 最近使用时间」两个维度来判定应用该进哪个桶。频率越高、时间越近,桶的优先级就越高。

4.2 待机桶的管理机制

系统是怎么管理这些桶的呢?我给大家拆解一下内部流程。

首先,系统会维护一个「使用记录表」。每次用户与应用交互——打开、点击通知、使用widget——都会更新这个记录。然后系统会定期跑一个评估任务,重新计算每个应用的「活跃度分数」。

这个分数怎么算?嗯,其实挺直观的:

  • 前台使用:+10分/次
  • 后台服务运行:+3分/次
  • 接收广播:+1分/次
  • 超过24小时未使用:-5分/天

分数越高,桶的等级就越高。低于某个阈值,就会被降级到更低的桶。

我在项目中遇到过一个问题:有个社交App的用户反馈说消息推送经常延迟。查了半天,发现是系统把这个App扔进了RARE桶。为什么?因为用户虽然每天打开App,但每次都是通过通知栏点击进入的——系统把这种「被动打开」的权重算得很低。后来我们调整了推送策略,改用高优先级的FCM消息,才解决了这个问题。

避坑指南:我曾经以为只要用户经常打开App,系统就不会把它降级。实际上,系统更看重「主动打开」的行为。如果你的App主要靠推送唤醒用户,那就要小心了——系统可能认为用户并不「需要」这个App。

4.3 应用如何适配待机桶

讲完了系统层面的机制,咱们聊聊开发者该怎么做。说白了,就是让你的App在低优先级桶里也能正常工作。

我总结了几个关键点:

4.3.1 使用JobScheduler替代后台服务

很多开发者习惯用Service来做后台任务。但在App Standby机制下,低优先级桶里的Service会被延迟甚至杀死。正确的做法是用JobScheduler。

// 错误示范:直接启动后台Service
Intent intent = new Intent(this, MyService.class);
startService(intent);

// 正确做法:使用JobScheduler调度任务
JobInfo jobInfo = new JobInfo.Builder(JOB_ID, new ComponentName(this, MyJobService.class))
    .setMinimumLatency(1000)  // 延迟1秒执行
    .setOverrideDeadline(5000) // 最晚5秒后执行
    .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
    .build();

JobScheduler scheduler = getSystemService(JobScheduler.class);
scheduler.schedule(jobInfo);

你想想看,JobScheduler是系统级的调度器,它知道当前App在哪个桶里。如果App在RARE桶,系统就会把Job的触发时间往后推。这样既省电,又不会让你的任务完全失效。

4.3.2 合理使用AlarmManager

有些App需要定时执行任务,比如闹钟、日历提醒。这时候要用AlarmManager,但要注意设置类型。

// 不推荐:使用ELAPSED_REALTIME,可能被延迟
AlarmManager alarm = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
alarm.set(AlarmManager.ELAPSED_REALTIME, triggerTime, pendingIntent);

// 推荐:使用setAlarmClock,系统会尊重闹钟类任务
alarm.setAlarmClock(new AlarmManager.AlarmClockInfo(triggerTime, pendingIntent));

为什么setAlarmClock更靠谱?因为系统知道这是用户主动设置的闹钟,不是App自己瞎搞的定时器。即使App在RARE桶,闹钟也会准时触发。

注意:不要滥用setAlarmClock!我见过有些App把普通的定时任务伪装成闹钟,结果被系统检测到后直接拉黑。系统不是傻子,它会统计闹钟的触发模式。频繁触发非用户交互的「假闹钟」,你的App会被标记为恶意应用。

4.3.3 处理待机桶变化的回调

Android 9之后,系统提供了一个回调接口,让App能知道自己被分配到了哪个桶。

// 注册待机桶变化监听
UsageStatsManager usageStatsManager = 
    (UsageStatsManager) getSystemService(Context.USAGE_STATS_SERVICE);

// 查询当前桶
int bucket = usageStatsManager.getAppStandbyBucket();

// 监听桶变化(需要权限)
BroadcastReceiver receiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        int newBucket = intent.getIntExtra(
            UsageStatsManager.EXTRA_APP_STANDBY_BUCKET, 
            UsageStatsManager.STANDBY_BUCKET_ACTIVE);
        // 根据新桶调整策略
        adjustBehavior(newBucket);
    }
};
registerReceiver(receiver, 
    new IntentFilter(UsageStatsManager.ACTION_APP_STANDBY_BUCKET_CHANGED));

我个人习惯在App启动时查询一次当前桶,然后根据桶的等级调整行为。比如在RARE桶里,我就减少网络请求频率,推迟非关键的数据同步。

4.4 实战中的适配策略

说了这么多理论,咱们来点实际的。我给大家整理了一个适配清单:

  1. 减少后台唤醒:能用JobScheduler就别用Service,能用WorkManager就别用AlarmManager
  2. 合并网络请求:把零散的小请求合并成一次大请求,减少唤醒次数
  3. 使用高优先级推送:FCM的高优先级消息可以绕过待机桶限制
  4. 引导用户加入白名单:对于关键应用(如即时通讯),可以引导用户到设置里关闭电池优化
  5. 测试不同桶的行为:用adb命令手动设置App的待机桶,验证功能是否正常
# 测试命令:手动设置待机桶
adb shell am set-standby-bucket com.example.app active
adb shell am set-standby-bucket com.example.app rare

# 查看当前桶
adb shell am get-standby-bucket com.example.app

嗯,这里要注意。测试时别只测ACTIVE桶。我见过太多App只在ACTIVE桶下测试,一上线就被用户骂「耗电」「卡顿」。你想想看,用户手机里几十个App,你的App大概率不在ACTIVE桶里。

总结一下:App Standby不是来搞你的,它是来帮你的。适配好了,你的App在低优先级桶里也能优雅运行。适配不好,用户只会觉得你的App又耗电又流氓。我做了这么多年系统优化,最大的感触就是——尊重系统的规则,系统才会尊重你的App。