13、Service组件原理:启动与绑定流程、前台服务、进程保活与ANR预防

Service,说白了就是Android里跑后台任务的组件。很多新手觉得它简单,不就是startService和bindService嘛。但真到了线上,各种被系统杀、ANR、保活失败的问题就全冒出来了。

我个人习惯把Service理解成「没有界面的Activity」。它不跟用户交互,但可以在后台干活。嗯,这里要注意——Service默认跑在主线程,你要是直接在里面做耗时操作,ANR分分钟教你做人。

13.1 启动流程:startService到底干了啥?

你调用startService(intent),系统会走这么几步:

  1. AMS收到请求,检查Service是否已创建
  2. 没创建?那就调onCreate()
  3. 然后调onStartCommand(),把Intent传进去
  4. Service进入运行状态,直到有人调stopService或自己调stopSelf

我在项目中遇到过一个问题:连续多次startService,onCreate只调一次,但onStartCommand会调多次。这个细节很多人忽略,结果在onCreate里初始化了资源,以为每次都会走,导致第二次启动时状态不对。

核心要点:onCreate只执行一次,onStartCommand每次start都会执行。
// 典型启动写法
Intent intent = new Intent(this, MyService.class);
startService(intent);

// Service内部
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    // 这里处理每次启动的任务
    return START_STICKY; // 被杀后自动重启
}

13.2 绑定流程:bindService的玄机

绑定跟启动是两套逻辑。你调用bindService,系统会:

  1. 检查Service是否存在,不存在则创建
  2. 调onBind(),返回一个IBinder对象
  3. 客户端通过这个Binder跟Service通信
  4. 所有客户端都解绑后,Service销毁

说白了,启动模式适合「只管干活,不管谁在调用」的场景。绑定模式适合「需要跟Service交互,拿数据」的场景。

我的建议:如果既要启动又要绑定,记得先startService再bindService。这样即使所有客户端解绑,Service也不会销毁,而是继续运行。
// 绑定写法
Intent intent = new Intent(this, MyService.class);
bindService(intent, connection, Context.BIND_AUTO_CREATE);

// ServiceConnection
private ServiceConnection connection = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        // 拿到Binder,开始通信
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {
        // 连接意外断开
    }
};

13.3 前台服务:为什么必须用?

Android 8.0以后,后台Service的限制越来越严。你想想看,如果每个App都在后台乱跑,用户的电量撑不过半天。

前台服务就是「告诉用户:我在干活,别杀我」。它必须在通知栏显示一条通知,用户能明确看到。

注意:Android 12以后,前台服务启动还有额外限制。后台启动前台服务会被系统拦截,除非用户主动操作。
// 启动前台服务
Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID)
        .setContentTitle("正在下载")
        .setContentText("下载进度: 50%")
        .setSmallIcon(R.drawable.ic_download)
        .build();

startForeground(NOTIFICATION_ID, notification);

我曾经遇到一个坑:某些厂商ROM对前台服务的通知有特殊要求,比如必须带进度条或者必须显示具体内容。否则系统会直接杀掉你的Service。嗯,适配起来确实头疼。

13.4 进程保活:别做梦了

很多人问我:「怎么让Service永远不被杀?」

我的回答是:别想了。Android系统设计的目标就是保证用户体验和续航,不是让你App常驻后台的。

常见的保活手段:

  • 双进程守护:两个进程互相拉起。但Android 5.0以后效果越来越差
  • 前台服务:最正规的方式,但用户能手动关掉通知
  • JobScheduler:系统统一调度,省电又稳定
  • WorkManager:Google推荐的方案,兼容性好
我的经验:与其花精力保活,不如把业务设计成「被杀后能快速恢复」。用WorkManager做定期任务,用JobScheduler做条件触发,这才是正道。

13.5 ANR预防:Service里最容易踩的坑

Service默认跑在主线程。你在onStartCommand里做个网络请求,或者写个数据库操作,20秒没返回——ANR就来了。

为什么会这样?因为系统认为Service应该在短时间内完成工作。如果超时,它就认为你的App卡死了。

场景 超时时间 建议
onCreate 20秒 不要做任何耗时操作
onStartCommand 20秒 启动线程或协程处理
onBind 20秒 直接返回Binder,别阻塞
onDestroy 20秒 清理资源要快
// 正确做法:开线程
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    new Thread(() -> {
        // 耗时操作在这里做
        // 做完记得调stopSelf
    }).start();
    return START_STICKY;
}
避坑指南:我曾经在onStartCommand里直接做了文件下载,结果用户网络差,下载超时,ANR弹出来了。后来改成IntentService(现在推荐用WorkManager),问题解决。

13.6 知识体系总览

下面这张图是我整理的Service核心知识结构,你看一眼就能明白整体脉络:

Service组件 启动流程 • startService → AMS • onCreate → onStartCommand • 多次start只调一次onCreate 绑定流程 • bindService → onBind • 返回IBinder通信 • 解绑后销毁 前台服务 • 必须显示通知 • Android 8+ 后台限制 • 12+ 后台启动受限 进程保活 • 双进程守护(已过时) • 前台服务(推荐) • WorkManager(最佳) ANR预防 • 主线程超时20秒 • 耗时操作开线程 • 用IntentService/协程

这张图把Service的五个核心维度都串起来了。你从中间开始,往四周看,每个分支都是一个独立的知识点。我个人建议先掌握启动和绑定,这两个是基础。前台服务是保活的正规手段,ANR预防是保命技能。

总结一句话:Service不是万能的后台工具,理解它的生命周期和限制,才能写出稳定不崩的代码。
公众号:蓝海资料掘金营,微信deep3321