一、核心区别对比
| 特性 | startService() |
bindService() |
|---|---|---|
| 启动方式 | 直接启动,无需组件绑定 | 需要与组件(Activity/Fragment)绑定 |
| 生命周期 | onCreate() → onStartCommand() |
onCreate() → onBind() |
| 与调用者关系 | 调用者与 Service 无直接关联 | 调用者与 Service 强绑定 |
| Service 存活 | 调用者销毁后,Service 仍可独立运行 | 所有绑定者解绑后,Service 自动销毁 |
| 通信方式 | 单向:Intent 传入,无直接回调 | 双向:通过 IBinder 接口直接通信 |
| 典型用途 | 后台长期任务(音乐播放、文件下载) | 需要与 UI 交互的后台服务(音乐控制、定位) |
| 多次调用 | 每次调用都触发 onStartCommand() |
首次绑定触发 onBind(),后续复用同一连接 |
二、生命周期对比
startService 生命周期
调用 startService()
↓
onCreate() [仅首次]
↓
onStartCommand() [每次 startService() 都会触发]
↓
... 服务运行中 ...
↓
调用 stopService() 或 stopSelf()
↓
onDestroy()
bindService 生命周期
调用 bindService()
↓
onCreate() [仅首次]
↓
onBind() [返回 IBinder,仅首次绑定触发]
↓
ServiceConnection.onServiceConnected() [回调给调用者]
↓
... 服务运行中,可双向通信 ...
↓
所有绑定者调用 unbindService()
↓
onUnbind() [最后一个解绑时触发]
↓
onDestroy()
混合模式(同时使用两者)
startService() + bindService()
↓
onCreate() → onStartCommand() → onBind()
↓
... 运行中 ...
↓
所有 bind 解绑 + stopService()/stopSelf()
↓
onUnbind() → onDestroy()
关键规则:混合模式下,Service 必须同时满足两个条件才会销毁:
- 没有
startService()启动(或已调用stopSelf()/stopService())- 没有活跃的绑定连接
三、代码示例
1. startService 示例
java
// Service 端
public class MusicService extends Service {
@Override
public void onCreate() {
super.onCreate();
Log.d("MusicService", "onCreate");
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
String action = intent.getStringExtra("action");
if ("play".equals(action)) {
startMusic();
} else if ("stop".equals(action)) {
stopMusic();
}
// START_STICKY: 被杀死后系统会自动重启
return START_STICKY;
}
@Override
public void onDestroy() {
super.onDestroy();
stopMusic();
Log.d("MusicService", "onDestroy");
}
@Override
public IBinder onBind(Intent intent) {
return null; // startService 不需要 Binder
}
private void startMusic() { /* ... */ }
private void stopMusic() { /* ... */ }
}
java
// Activity 端启动
public class MainActivity extends AppCompatActivity {
public void startMusic() {
Intent intent = new Intent(this, MusicService.class);
intent.putExtra("action", "play");
startService(intent);
}
public void stopMusic() {
Intent intent = new Intent(this, MusicService.class);
stopService(intent);
}
}
2. bindService 示例
java
// Service 端:提供 Binder 接口
public class MusicService extends Service {
private final IBinder binder = new MusicBinder();
private boolean isPlaying = false;
// 定义通信接口
public class MusicBinder extends Binder {
public MusicService getService() {
return MusicService.this;
}
}
// 暴露给外部的方法
public void play() {
isPlaying = true;
// 播放逻辑
}
public void pause() {
isPlaying = false;
// 暂停逻辑
}
public boolean isPlaying() {
return isPlaying;
}
@Override
public IBinder onBind(Intent intent) {
return binder;
}
}
java
// Activity 端绑定
public class MainActivity extends AppCompatActivity {
private MusicService musicService;
private boolean isBound = false;
private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
MusicService.MusicBinder binder = (MusicService.MusicBinder) service;
musicService = binder.getService();
isBound = true;
// 绑定成功,可以调用 Service 方法
updateUI();
}
@Override
public void onServiceDisconnected(ComponentName name) {
isBound = false;
musicService = null;
}
};
@Override
protected void onStart() {
super.onStart();
Intent intent = new Intent(this, MusicService.class);
bindService(intent, connection, Context.BIND_AUTO_CREATE);
}
@Override
protected void onStop() {
super.onStop();
if (isBound) {
unbindService(connection);
isBound = false;
}
}
// 通过 Service 控制音乐
public void onPlayClick(View v) {
if (isBound && musicService != null) {
musicService.play();
updateUI();
}
}
private void updateUI() {
if (musicService != null) {
boolean playing = musicService.isPlaying();
// 更新按钮状态
}
}
}
3. 混合模式示例(最常用)
java
// 先 startService 保证长期存活,再 bindService 实现交互
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 第一步:startService 保证 Service 不被系统轻易杀死
Intent intent = new Intent(this, MusicService.class);
startService(intent);
// 第二步:bindService 实现双向通信
bindService(intent, connection, Context.BIND_AUTO_CREATE);
}
@Override
protected void onDestroy() {
super.onDestroy();
// 只解绑,不 stopService,让音乐继续后台播放
if (isBound) {
unbindService(connection);
}
}
// 真正退出应用时才 stopService
public void exitApp() {
Intent intent = new Intent(this, MusicService.class);
stopService(intent);
}
}
四、日常使用场景
| 场景 | 推荐方式 | 原因 |
|---|---|---|
| 后台音乐播放 | startService + bindService |
需要长期运行 + 需要 UI 控制 |
| 文件下载 | startService |
不需要 UI 交互,完成后自动停止 |
| 定位服务 | bindService |
需要实时获取位置数据更新 UI |
| 计步器 | startService |
长期后台运行,不需要频繁交互 |
| 视频播放器后台播放 | 混合模式 | 切后台继续播放,返回前台可控制 |
| AIDL 跨进程通信 | bindService |
必须通过 Binder 实现 IPC |
五、注意事项
1. 内存泄漏(最常见)
java
// ❌ 错误:匿名内部类持有 Activity 引用
public class MainActivity extends AppCompatActivity {
private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
// 这里隐式持有 MainActivity 引用
// 如果 Service 生命周期比 Activity 长,会造成内存泄漏
}
};
}
java
// ✅ 正确:使用静态内部类 + WeakReference
public class MainActivity extends AppCompatActivity {
private static class MyServiceConnection implements ServiceConnection {
private WeakReference<MainActivity> activityRef;
MyServiceConnection(MainActivity activity) {
this.activityRef = new WeakReference<>(activity);
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
MainActivity activity = activityRef.get();
if (activity != null) {
// 安全使用
}
}
@Override
public void onServiceDisconnected(ComponentName name) {}
}
}
2. 重复解绑崩溃
java
// ❌ 错误:多次调用 unbindService 会抛 IllegalArgumentException
@Override
protected void onDestroy() {
super.onDestroy();
unbindService(connection); // 如果已经解绑过,这里崩溃
}
java
// ✅ 正确:用标志位控制
@Override
protected void onDestroy() {
super.onDestroy();
if (isBound) {
unbindService(connection);
isBound = false;
}
}
3. bindService 后忘记解绑
java
// ❌ 错误:在 onDestroy 中不解绑
@Override
protected void onDestroy() {
super.onDestroy();
// 漏了 unbindService!
}
绑定后不解绑会导致:
- Service 无法销毁,占用内存
- Activity 泄漏(ServiceConnection 持有 Activity 引用)
- 应用退出时可能报
ServiceConnectionLeaked警告
4. 混合模式下的生命周期陷阱
java
// 陷阱:先 bind 后 start,解绑后 Service 不会自动销毁
// 因为 startService 启动了它,需要显式 stopService
// 正确顺序:
// 1. startService() → 启动
// 2. bindService() → 绑定
// 3. unbindService() → 解绑(Service 继续运行)
// 4. stopService() → 真正停止
5. 主线程耗时操作
java
// ❌ 错误:在 Service 主线程做耗时操作
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
downloadLargeFile(); // ANR!
return START_STICKY;
}
java
// ✅ 正确:使用子线程或 IntentService/WorkManager
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
new Thread(() -> downloadLargeFile()).start();
return START_STICKY;
}
6. Android 8.0+ 后台限制
从 Android 8.0(API 26)开始,后台应用调用 startService() 会受到限制:
java
// Android 8.0+ 后台启动 Service 会抛 IllegalStateException
// 解决方案:使用 startForegroundService()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
startForegroundService(intent);
} else {
startService(intent);
}
// 然后在 Service 的 onCreate() 中必须在 5 秒内调用 startForeground()
@Override
public void onCreate() {
super.onCreate();
Notification notification = buildNotification();
startForeground(NOTIFICATION_ID, notification);
}
7. onStartCommand 返回值选择
java
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// 根据场景选择返回值:
// START_STICKY:服务被杀后自动重启,intent 为 null(音乐播放)
// START_NOT_STICKY:服务被杀后不自动重启(一次性任务)
// START_REDELIVER_INTENT:服务被杀后自动重启,且重新传入原 intent(下载任务)
return START_STICKY;
}
六、总结
| 问题 | 答案 |
|---|---|
| 需要后台长期运行且不与 UI 交互? | 用 startService() |
| 需要与 Activity 实时交互? | 用 bindService() |
| 既要后台运行又要 UI 控制? | 混合模式 :先 startService() 再 bindService() |
| 混合模式下如何正确销毁? | 先 unbindService() 再 stopService() |
| Android 8.0+ 后台启动限制? | 用 startForegroundService() + startForeground() |
核心原则 :
startService管"活多久",bindService管"怎么交互"。两者不是互斥的,而是互补的,混合使用是最常见的实战模式。