18、Service基础:启动服务、绑定服务、IntentService、前台服务与通知栏
Service,说白了就是Android里跑在后台的“隐形打工人”。
它没有界面,但能干很多脏活累活——下载文件、播放音乐、处理网络请求。我刚开始学Android时,总觉得Service和线程差不多,后来踩了坑才明白,这两者压根不是一回事。
18.1 什么是Service?
Service是Android四大组件之一。它设计用来执行长时间运行的后台操作。注意,它运行在主线程,这一点很多人会搞混。
你想想看,如果Service里直接写个网络请求,那不就ANR了吗?嗯,这里要注意:Service本身不创建线程,你得自己在Service里开子线程干活。
核心要点:Service ≠ 后台线程。Service是组件,线程是执行单元。两者配合使用才是正解。
18.2 启动服务 vs 绑定服务
Service有两种使用方式,我习惯把它们比作“临时工”和“长期顾问”。
18.2.1 启动服务(Started Service)
通过 startService() 启动。一旦启动,Service会一直运行,直到调用 stopSelf() 或外部调用 stopService()。调用者和Service之间没有直接通信。
我在项目中遇到过这样一个场景:用户上传图片,我启动一个Service去处理。用户切到别的App,Service还在跑。这就是启动服务的典型用法。
// 启动服务
Intent intent = new Intent(this, MyUploadService.class);
startService(intent);
// Service内部
public class MyUploadService extends Service {
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// 这里要开线程干活
new Thread(() -> {
// 执行上传逻辑
stopSelf(); // 干完活自己停掉
}).start();
return START_STICKY; // 被杀死后自动重启
}
@Override
public IBinder onBind(Intent intent) {
return null; // 启动服务不需要绑定
}
}
我的习惯:返回 START_STICKY 还是 START_NOT_STICKY?如果任务必须完成,用STICKY;如果任务可以中断,用NOT_STICKY。我曾经因为用了STICKY导致Service反复重启,浪费了不少流量。
18.2.2 绑定服务(Bound Service)
通过 bindService() 绑定。调用者和Service之间可以双向通信。当所有调用者都解绑后,Service自动销毁。
说白了,绑定服务就是“你和我说话,我回你话”。适合做音乐播放器这种需要交互的场景。
// 绑定服务
Intent intent = new Intent(this, MyMusicService.class);
bindService(intent, connection, Context.BIND_AUTO_CREATE);
private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
// 拿到Binder,可以调用Service的方法
MyBinder binder = (MyBinder) service;
binder.play();
}
@Override
public void onServiceDisconnected(ComponentName name) {
// 服务意外断开
}
};
// Service内部
public class MyMusicService extends Service {
private final IBinder binder = new MyBinder();
public class MyBinder extends Binder {
public void play() {
// 播放音乐
}
}
@Override
public IBinder onBind(Intent intent) {
return binder;
}
}
注意:绑定服务是异步的。你调用 bindService() 后,不能立刻拿到Binder。我曾经犯过这个错——在 onCreate 里直接调用绑定服务的方法,结果空指针了。
18.3 IntentService:省心版Service
还记得我刚才说的吗?Service里要自己开线程。但IntentService帮你做了这件事。
IntentService内部维护了一个工作线程和一个队列。每次启动,它会创建一个新的Intent,放到队列里,然后依次处理。处理完一个,自动停掉。
我特别喜欢IntentService的一点:你不用操心线程管理,也不用担心忘记 stopSelf()。
public class MyDownloadService extends IntentService {
public MyDownloadService() {
super("MyDownloadService");
}
@Override
protected void onHandleIntent(Intent intent) {
// 这里已经在子线程了,放心干活
String url = intent.getStringExtra("url");
downloadFile(url);
}
}
避坑指南:IntentService在Android 8.0(API 26)之后被标记为废弃。官方推荐用 JobIntentService 或直接使用 WorkManager。但我个人觉得,理解IntentService的设计思想,对理解后台任务模型很有帮助。
18.4 前台服务与通知栏
从Android 8.0开始,后台Service的限制越来越严。你想想看,如果每个App都在后台乱跑,用户的手机电量撑得住吗?
所以,Google推出了前台服务。前台服务会显示一个通知栏,告诉用户“我正在干活”。这样用户就知道你的App在后台做什么。
我记得有一次,用户反馈说App播放音乐时突然停了。查了半天,原来是Service被系统杀掉了。后来改成前台服务,问题就解决了。
// 创建通知渠道(Android 8.0+ 必须)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel channel = new NotificationChannel(
"music_channel",
"音乐播放",
NotificationManager.IMPORTANCE_LOW
);
NotificationManager manager = getSystemService(NotificationManager.class);
manager.createNotificationChannel(channel);
}
// 构建通知
Notification notification = new NotificationCompat.Builder(this, "music_channel")
.setContentTitle("正在播放")
.setContentText("周杰伦 - 七里香")
.setSmallIcon(R.drawable.ic_music)
.setOngoing(true) // 不可滑动删除
.build();
// 启动前台服务
startForeground(1, notification);
我的经验:前台服务的通知一定要设置 setOngoing(true)。否则用户不小心滑掉通知,Service可能被系统杀掉。另外,通知的 importance 建议用 LOW 或 MIN,别打扰用户。
18.5 知识体系总览
下面这张图,是我梳理的Service核心知识结构。你看一眼,心里就有谱了。
18.6 生命周期对比
不同Service的生命周期差异很大。我整理了一张表,方便你对照。
| 类型 | 启动方式 | 生命周期 | 通信方式 | 典型场景 |
|---|---|---|---|---|
| 启动服务 | startService() | 独立于调用者,需手动停止 | 无直接通信 | 文件下载、上传 |
| 绑定服务 | bindService() | 随调用者生命周期 | Binder双向通信 | 音乐播放器、计时器 |
| IntentService | startService() | 任务完成后自动停止 | 无直接通信 | 简单后台任务队列 |
| 前台服务 | startForeground() | 高优先级,不易被杀死 | 通知栏展示 | 音乐播放、位置追踪 |
18.7 避坑总结
最后,分享几个我踩过的坑,你遇到了能少走弯路。
- 不要在Service里直接做耗时操作。Service跑在主线程,你写个网络请求,几秒后ANR就来了。
- Android 8.0以上,后台Service限制严格。能上前台服务就上前台服务,别硬扛。
- IntentService已废弃,但思想值得学。新项目建议用WorkManager或JobIntentService。
- 绑定服务记得解绑。在Activity的
onDestroy里调用unbindService(),否则会泄漏。 - 前台服务的通知一定要有渠道。Android 8.0以上,不创建渠道,通知不显示。
嗯,Service这块内容就这些。说白了,它就是Android后台能力的基石。你把它搞明白了,后面学WorkManager、JobScheduler都会轻松很多。
公众号:蓝海资料掘金营,微信deep3321