一、蓝牙服务的架构困局——为什么需要解耦?
做蓝牙开发这些年,我见过太多项目栽在同一个坑里:Service 和 Activity 强耦合。
说白了,就是把蓝牙连接、数据收发、状态管理全塞在 Activity 里。一开始觉得方便,Activity 一启动就 bindService,一关闭就 unbindService。但项目一复杂,问题就来了——
- Activity 重建(比如屏幕旋转)时,蓝牙连接断了
- 后台需要持续扫描,但 Activity 被杀了
- 多个页面都要用蓝牙,每个页面都得写一遍绑定逻辑
我在项目中遇到过最夸张的一次:一个同事把 BluetoothGatt 对象直接存在 Activity 的成员变量里,结果用户切到后台再回来,连接全丢了,还报了一堆空指针。嗯,从那以后,我强制团队必须用独立 Service 管理蓝牙生命周期。
核心原则:蓝牙 Service 的生命周期必须独立于 UI。Activity 可以死,Service 不能死。
二、Service 与 Activity 解耦——实战方案
2.1 解耦的本质
解耦不是不通信,而是通信方式要松耦合。我个人习惯用三种方式:
- Binder 直连——适合单页面、短生命周期场景
- 广播 + LocalBroadcastManager——适合多页面监听状态
- LiveData / Flow——适合 MVVM 架构
2.2 代码示例:Binder 方式
// BluetoothService.java
public class BluetoothService extends Service {
private final IBinder binder = new LocalBinder();
public class LocalBinder extends Binder {
BluetoothService getService() {
return BluetoothService.this;
}
}
@Override
public IBinder onBind(Intent intent) {
return binder;
}
public void startScan() {
// 扫描逻辑
}
public void connect(String address) {
// 连接逻辑
}
}
// MainActivity.java
private BluetoothService bluetoothService;
private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
bluetoothService = ((BluetoothService.LocalBinder) service).getService();
bluetoothService.startScan();
}
@Override
public void onServiceDisconnected(ComponentName name) {
bluetoothService = null;
}
};
避坑指南:我曾经在 onServiceDisconnected 里直接调用了 bluetoothService 的方法,结果空指针了。记住:断开后要立即置 null,所有调用前都要判空。
2.3 广播方式——多页面监听
如果多个 Activity 或 Fragment 都要监听蓝牙状态,Binder 就不太够用了。你想想看,总不能每个页面都 bindService 吧?
// 发送广播
Intent intent = new Intent("com.example.BLUETOOTH_STATE_CHANGED");
intent.putExtra("state", BluetoothAdapter.STATE_CONNECTED);
LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
// 接收广播
BroadcastReceiver receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
int state = intent.getIntExtra("state", -1);
// 更新 UI
}
};
LocalBroadcastManager.getInstance(this).registerReceiver(receiver,
new IntentFilter("com.example.BLUETOOTH_STATE_CHANGED"));
三、Binder 通信——跨进程的桥梁
蓝牙服务经常需要跨进程。比如系统设置里的蓝牙开关,和你的 App 不在同一个进程。这时候就得靠 Binder。
3.1 AIDL 定义接口
// IBluetoothService.aidl
interface IBluetoothService {
boolean enableBluetooth();
boolean disableBluetooth();
int getState();
List<BluetoothDevice> getBondedDevices();
}
3.2 服务端实现
public class BluetoothService extends Service {
private final IBluetoothService.Stub binder = new IBluetoothService.Stub() {
@Override
public boolean enableBluetooth() throws RemoteException {
return BluetoothAdapter.getDefaultAdapter().enable();
}
@Override
public boolean disableBluetooth() throws RemoteException {
return BluetoothAdapter.getDefaultAdapter().disable();
}
@Override
public int getState() throws RemoteException {
return BluetoothAdapter.getDefaultAdapter().getState();
}
@Override
public List<BluetoothDevice> getBondedDevices() throws RemoteException {
return new ArrayList<>(BluetoothAdapter.getDefaultAdapter().getBondedDevices());
}
};
@Override
public IBinder onBind(Intent intent) {
return binder;
}
}
注意:跨进程传 Parcelable 对象时,要确保类实现了 Parcelable 接口,并且两端都有相同的类定义。我踩过这个坑——客户端和服务端用的 BluetoothDevice 版本不一致,直接崩溃。
四、多进程蓝牙服务——架构设计
为什么要多进程?说白了,蓝牙服务不能因为 App 崩溃就挂掉。尤其是系统级蓝牙服务,必须跑在独立进程里。
4.1 进程划分
| 进程 | 职责 | 生命周期 |
|---|---|---|
| com.android.bluetooth | 蓝牙核心服务(GATT、A2DP、HFP) | 系统启动时拉起,常驻 |
| App 进程 | UI 交互、业务逻辑 | 随 App 启动/销毁 |
| 系统设置进程 | 蓝牙开关、设备管理 | 随设置 App 启动/销毁 |
4.2 多进程通信架构
// 在 AndroidManifest 中声明多进程
<service
android:name=".BluetoothService"
android:process=":bluetooth"
android:exported="true">
</service>
进程间通信用 AIDL,数据同步用 ContentProvider 或 SharedPreferences(跨进程模式)。我个人更推荐用 ContentProvider,因为 SharedPreferences 跨进程有坑——并发写会丢数据。
五、系统级蓝牙服务——SystemServer 设计思路
Android 系统里的蓝牙服务,最终是跑在 SystemServer 进程里的。你想想看,如果每个 App 都自己管蓝牙,那不乱套了?
5.1 SystemServer 启动流程
- SystemServer 启动时,调用 BluetoothService 的构造函数
- BluetoothService 初始化 BluetoothAdapter、BluetoothGatt 等
- 注册到 ServiceManager,供其他进程通过 Binder 调用
5.2 核心设计要点
- 单例模式:整个系统只有一个 BluetoothService 实例
- 权限管控:所有跨进程调用都要检查 BLUETOOTH、BLUETOOTH_ADMIN 权限
- 状态机:蓝牙状态切换用状态机管理,避免并发问题
- 回调机制:用 Binder 回调通知客户端状态变化
系统级服务 vs App 级服务:系统级服务有更高的优先级,系统不会轻易杀它。但也要注意内存泄漏——我见过系统蓝牙服务因为没解注册回调,导致内存暴涨的案例。
六、架构全景图
下面这张图,是我整理的系统级蓝牙服务架构。你看一眼就能明白各层的关系:
七、总结与避坑
蓝牙服务架构设计,说白了就三件事:
- 解耦——Service 和 Activity 各管各的,别混在一起
- 跨进程——用 Binder/AIDL 做桥梁,注意权限和生命周期
- 系统级设计——状态机、单例、回调管理,一个都不能少
我的个人建议:如果你在做一个需要长期连接蓝牙设备的 App,一定要把蓝牙 Service 放在独立进程里。这样即使 App 崩溃,蓝牙连接也不会断。我曾经在一个医疗项目中这么做过,用户反馈稳定性提升了一大截。
最后提醒:多进程通信时,注意序列化和反序列化的性能开销。频繁传输大数据(比如蓝牙音频流)不要走 Binder,用 Socket 或共享内存更合适。
公众号:蓝海资料掘金营,微信deep3321