第13章 Service服务详解
Service,说白了就是Android里的「后台打工人」。它没有界面,默默在后台干活。我刚开始学Android时,总觉得Service很神秘——它到底怎么启动?怎么销毁?为什么有时候APP被杀掉,Service还在跑?
这一章,咱们就把Service彻底讲透。从生命周期到前台服务,从IntentService到Android 8+的后台限制,我会把我在项目中踩过的坑、积累的经验,全都告诉你。
核心要点:Service不是新线程,它默认运行在主线程。如果你在Service里做耗时操作,必须自己开子线程。这个坑,我当年可是实实在在踩过的。
13.1 Service生命周期
Service的生命周期,比Activity简单多了。它只有三个主要阶段:创建、运行、销毁。但根据启动方式不同,回调方法也有区别。
| 回调方法 | 说明 | 触发时机 |
|---|---|---|
| onCreate() | Service第一次创建时调用 | 只调用一次 |
| onStartCommand() | 每次startService()都会调用 | 可多次调用 |
| onBind() | bindService()时调用 | 返回IBinder对象 |
| onUnbind() | 所有客户端解绑时调用 | 返回boolean |
| onDestroy() | Service销毁时调用 | 清理资源 |
我个人习惯,在onCreate里做初始化,在onDestroy里做清理。千万别在onStartCommand里做耗时初始化——每次启动都执行一遍,性能会很难看。
public class MyService extends Service {
@Override
public void onCreate() {
super.onCreate();
// 初始化操作,只执行一次
Log.d("MyService", "Service创建");
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// 每次startService都会执行
Log.d("MyService", "收到启动命令");
// 如果被系统杀死,自动重启
return START_STICKY;
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onDestroy() {
super.onDestroy();
Log.d("MyService", "Service销毁");
}
}
小技巧:onStartCommand的返回值很重要。START_STICKY表示Service被杀死后会重启,START_NOT_STICKY则不会。我一般用START_STICKY,除非有特殊需求。
13.2 Started Service vs Bound Service
这两种Service,说白了就是「启动方式」不同。Started Service通过startService启动,Bound Service通过bindService绑定。它们的使用场景完全不同。
Started Service:适合执行一次性任务,比如下载文件、播放音乐。启动后即使调用者退出,Service仍然运行。
Bound Service:适合需要与调用者交互的场景,比如音乐播放器需要传递播放进度。调用者退出时,Service自动销毁。
我记得有一次做即时通讯APP,需要后台保持长连接。一开始用的Bound Service,结果用户切到后台,Service就被销毁了,连接也断了。后来改成Started Service + 手动管理生命周期,才解决问题。
// Started Service 启动方式
Intent intent = new Intent(this, MyService.class);
startService(intent);
// Bound Service 绑定方式
Intent bindIntent = new Intent(this, MyBoundService.class);
bindService(bindIntent, connection, Context.BIND_AUTO_CREATE);
注意:同一个Service可以同时被start和bind。这种情况下,需要同时调用stopService和unbindService才能销毁Service。我曾经因为这个bug,导致Service在后台一直运行,用户手机电量哗哗地掉。
13.3 IntentService使用
IntentService是Service的一个子类,它帮你处理了「在子线程执行任务」这件事。你想想看,普通Service跑在主线程,如果直接做网络请求,会报ANR。IntentService自带工作线程,任务执行完自动销毁。
不过,Android 8.0之后,IntentService被标记为废弃了。官方推荐用JobIntentService或者直接使用协程+普通Service。但我还是想讲讲IntentService,因为它的设计思路很经典。
public class MyIntentService extends IntentService {
public MyIntentService() {
super("MyIntentService");
}
@Override
protected void onHandleIntent(Intent intent) {
// 这个方法运行在子线程
String action = intent.getStringExtra("action");
// 执行耗时操作
Log.d("MyIntentService", "处理任务: " + action);
}
}
嗯,这里要注意:IntentService只能处理顺序任务,不能并发。如果你需要同时处理多个任务,还是用普通Service + 线程池吧。
13.4 前台服务与Notification
从Android 8.0开始,后台Service的限制越来越严。如果你的APP在后台运行,系统会很快杀掉你的Service。怎么办?用前台服务。
前台服务,说白了就是「告诉用户我在后台干活」。它必须在通知栏显示一个Notification,用户能看到你的APP正在运行。比如音乐播放器、导航软件,都用的是前台服务。
public class ForegroundService extends Service {
@Override
public void onCreate() {
super.onCreate();
// 创建通知渠道(Android 8.0+必需)
createNotificationChannel();
// 构建通知
Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("服务运行中")
.setContentText("正在后台执行任务")
.setSmallIcon(R.drawable.ic_service)
.build();
// 启动前台服务
startForeground(1, notification);
}
private void createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel channel = new NotificationChannel(
CHANNEL_ID,
"后台服务",
NotificationManager.IMPORTANCE_LOW
);
NotificationManager manager = getSystemService(NotificationManager.class);
manager.createNotificationChannel(channel);
}
}
}
避坑指南:我曾经在项目里忘记创建通知渠道,结果在Android 8.0以上的手机上,前台服务直接崩溃。记住:Android 8.0+必须创建NotificationChannel,否则通知不会显示。
13.5 Android 8+后台限制
Android 8.0(API 26)是一个分水岭。从这版本开始,系统对后台Service的限制非常严格。具体来说:
- 后台应用不能通过startService启动Service
- 系统会在一段时间后停止后台Service
- 只有前台应用或特定白名单应用才能自由启动Service
为什么会这样?说白了,就是Google想省电。以前很多APP滥用Service,用户手机一晚上掉电30%。现在系统帮你管着,不让你乱来。
那怎么办?有几种方案:
- 使用前台服务:显示通知,告诉用户你在干活
- 使用JobScheduler:系统统一调度任务,省电高效
- 使用WorkManager:Google推荐的方案,兼容性最好
我个人习惯,能用WorkManager就用WorkManager。它帮你处理了各种兼容性问题,而且支持链式任务、周期性任务,非常方便。
重要提醒:不要试图绕过后台限制。有些开发者用「双进程守护」或者「定时唤醒」的方式,这些在Android 10+基本都失效了。老老实实用官方方案,才是正道。
13.6 知识体系总览
下面这张图,把Service的核心知识点串起来了。你可以看到,Service分为Started和Bound两种,IntentService是Started Service的特殊形式,前台服务则是为了解决后台限制而生的。
这张图里,我把Service分成了两大派系。左边是Started Service,适合独立运行的任务;右边是Bound Service,适合与组件交互的场景。底部是Android 8+的后台限制,提醒你:新项目尽量用WorkManager。
总结一下:Service是Android后台任务的基础,但Google正在逐步收紧权限。我的建议是:能用WorkManager就用WorkManager,必须用Service时,优先考虑前台服务。别想着绕过后台限制,老老实实适配新版本,用户才会买账。