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

相关推荐
hunterandroid5 小时前
WorkManager 可靠性实战:唯一任务、重试与幂等设计
android·前端
程序员老刘5 小时前
Android 17的10个AI功能,国内用户真正能用的有几个?
android·flutter·ai编程
花开彼岸天~6 小时前
鸿蒙应用开发实战【94】— 实战多卡号场景管理最佳实践
android·华为·harmonyos·鸿蒙系统
用户41659673693556 小时前
视频源文件音频轨异常分析记录
android
雨白9 小时前
嵌套 ScrollView 滑动冲突实战:实现内部优先滚动
android
阿pin9 小时前
Android随笔-IntentService详解(了解)
android·intentservice
海天鹰9 小时前
content://com.android.externalstorage.documents/document/primary%3A
android
阿pin10 小时前
Android随笔-WorkManager 与 AlarmManager
android·alarmmanager·workmanager
帅帅的记忆11 小时前
uniapp项目调用原生android studio的sdk文件(即aar文件)
android·uni-app·android studio
天才少年曾牛11 小时前
Android16在状态栏顶部新增图标(SystemUI)
android·frameworks