Android随笔-startService&bindService

一、核心区别对比

特性 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 必须同时满足两个条件才会销毁:

  1. 没有 startService() 启动(或已调用 stopSelf() / stopService())
  2. 没有活跃的绑定连接

三、代码示例

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 管"怎么交互"。两者不是互斥的,而是互补的,混合使用是最常见的实战模式。

相关推荐
晚风叙码11 小时前
MySQL 数据类型详解:从数值到字符串,一篇讲透
android·mysql·adb
传奇开心果编程11 小时前
【Compose Multiplatform 跨端开发学与练】第3课 布局与组件
android·windows·学习·ui·ios·kotlin·composer
传奇开心果编程14 小时前
【Compose Multiplatform 跨端开发学与练】第8课 资源管理与主题
android·windows·学习·ios·kotlin·web·composer
传奇开心果编程16 小时前
【Compose Multiplatform 跨端开发学与练】第9课 测试与调试
android·学习·macos·ios·kotlin·web·composer
传奇开心果编程16 小时前
【Compose Multiplatform 跨端开发学与练】第4课 导航与路由
android·windows·学习·ui·ios·kotlin·composer
传奇开心果编程16 小时前
【Compose Multiplatform 跨端开发学与练】第6课 状态管理与架构
android·学习·ui·ios·架构·kotlin·composer
事圆则缓16 小时前
Android AOSP 定制常见概念:源码目录、系统镜像与刷机流程
android
传奇开心果编程16 小时前
【Compose Multiplatform 跨端开发学与练】第2课 Compose 基础语法
android·windows·学习·ui·ios·kotlin·composer
supabc12317 小时前
Celium:连接 Windows、Mac、Linux 与 Android,让远程访问和设备管理更简单
android·linux·windows·macos·远程访问·网络管理·celium
Dovis(誓平步青云)17 小时前
导览音频切换太快,旧讲解不能覆盖新展品
android·前端·javascript·ecmascript·音视频·宠物