一、蓝牙服务的架构困局——为什么需要解耦?

做蓝牙开发这些年,我见过太多项目栽在同一个坑里:Service 和 Activity 强耦合。

说白了,就是把蓝牙连接、数据收发、状态管理全塞在 Activity 里。一开始觉得方便,Activity 一启动就 bindService,一关闭就 unbindService。但项目一复杂,问题就来了——

  • Activity 重建(比如屏幕旋转)时,蓝牙连接断了
  • 后台需要持续扫描,但 Activity 被杀了
  • 多个页面都要用蓝牙,每个页面都得写一遍绑定逻辑

我在项目中遇到过最夸张的一次:一个同事把 BluetoothGatt 对象直接存在 Activity 的成员变量里,结果用户切到后台再回来,连接全丢了,还报了一堆空指针。嗯,从那以后,我强制团队必须用独立 Service 管理蓝牙生命周期。

核心原则:蓝牙 Service 的生命周期必须独立于 UI。Activity 可以死,Service 不能死。

二、Service 与 Activity 解耦——实战方案

2.1 解耦的本质

解耦不是不通信,而是通信方式要松耦合。我个人习惯用三种方式:

  1. Binder 直连——适合单页面、短生命周期场景
  2. 广播 + LocalBroadcastManager——适合多页面监听状态
  3. 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 启动流程

  1. SystemServer 启动时,调用 BluetoothService 的构造函数
  2. BluetoothService 初始化 BluetoothAdapter、BluetoothGatt 等
  3. 注册到 ServiceManager,供其他进程通过 Binder 调用

5.2 核心设计要点

  • 单例模式:整个系统只有一个 BluetoothService 实例
  • 权限管控:所有跨进程调用都要检查 BLUETOOTH、BLUETOOTH_ADMIN 权限
  • 状态机:蓝牙状态切换用状态机管理,避免并发问题
  • 回调机制:用 Binder 回调通知客户端状态变化
系统级服务 vs App 级服务:系统级服务有更高的优先级,系统不会轻易杀它。但也要注意内存泄漏——我见过系统蓝牙服务因为没解注册回调,导致内存暴涨的案例。

六、架构全景图

下面这张图,是我整理的系统级蓝牙服务架构。你看一眼就能明白各层的关系:

系统级蓝牙服务架构图 应用层(App 进程) Activity / Fragment / ViewModel Binder 通信层(AIDL 接口) IBluetoothService.aidl / 回调接口 蓝牙服务层(SystemServer 进程) BluetoothService / BluetoothGatt / A2DP / HFP 状态机管理 / 权限管控 / 回调分发 蓝牙协议栈层(Native 层) BlueZ / Fluoride / HCI / L2CAP / RFCOMM 图:系统级蓝牙服务四层架构,从上到下依次为应用层、Binder 层、服务层、协议栈层

七、总结与避坑

蓝牙服务架构设计,说白了就三件事:

  1. 解耦——Service 和 Activity 各管各的,别混在一起
  2. 跨进程——用 Binder/AIDL 做桥梁,注意权限和生命周期
  3. 系统级设计——状态机、单例、回调管理,一个都不能少
我的个人建议:如果你在做一个需要长期连接蓝牙设备的 App,一定要把蓝牙 Service 放在独立进程里。这样即使 App 崩溃,蓝牙连接也不会断。我曾经在一个医疗项目中这么做过,用户反馈稳定性提升了一大截。
最后提醒:多进程通信时,注意序列化和反序列化的性能开销。频繁传输大数据(比如蓝牙音频流)不要走 Binder,用 Socket 或共享内存更合适。

公众号:蓝海资料掘金营,微信deep3321