第四章 蓝牙适配器初始化:扫描本地蓝牙设备、获取适配器信息、设置设备名称与可见性

好,咱们进入实战环节的第一个硬骨头——蓝牙适配器初始化。

说实话,很多初学者一上来就急着写连接代码、搞通讯录同步,结果卡在最开始的适配器初始化上。我当年第一次做车载蓝牙项目时,就因为这个吃了不少苦头。所以这一章,咱们把基础打扎实。

4.1 蓝牙适配器是什么?

蓝牙适配器(BluetoothAdapter),说白了就是系统里那个负责管理蓝牙硬件的“管家”。

在Android系统里,所有蓝牙操作都得通过它来发起。你想扫描设备?找它。你想获取本机蓝牙地址?还是找它。你想让设备能被别人发现?也得通过它。

我习惯把蓝牙适配器理解成一个“入口”。没有这个入口,后面的一切都是空谈。

4.2 获取蓝牙适配器实例

在Android开发中,获取蓝牙适配器有两种方式。我个人推荐第二种,更稳定。

方式一:通过系统服务获取

BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();

这种方式最简单,一行代码搞定。但有个坑——如果设备没有蓝牙硬件,它会返回null。所以一定要判空。

方式二:通过上下文获取(我推荐)

BluetoothManager bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
BluetoothAdapter bluetoothAdapter = bluetoothManager.getAdapter();

为什么推荐第二种?

我在项目中遇到过一种情况:某些定制化的车载系统,getDefaultAdapter() 返回了非空对象,但实际蓝牙硬件根本没初始化完成。用 BluetoothManager 方式获取,能更准确地反映硬件状态。

注意: 获取适配器前,别忘了在 AndroidManifest.xml 中声明权限:
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
从 Android 6.0 开始,还需要动态申请 ACCESS_FINE_LOCATION 权限。

4.3 检查并启用蓝牙

拿到适配器后,第一件事就是检查蓝牙是否开启。你想想看,如果蓝牙都没开,后面扫描、连接全是白搭。

if (bluetoothAdapter == null) {
    // 设备不支持蓝牙
    return;
}

if (!bluetoothAdapter.isEnabled()) {
    // 蓝牙未开启,请求用户开启
    Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
    startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
}

这里有个细节:isEnabled() 返回 false 时,不要直接调用 enable() 方法强制开启。为什么?因为 enable() 是静默开启,用户完全不知情。这在车载场景下很危险——万一用户在开车,蓝牙突然开启导致音频中断怎么办?

我建议的做法是:弹出一个对话框,让用户确认。这样既合规,又安全。

4.4 扫描本地蓝牙设备

扫描设备,是蓝牙通讯的第一步。车载场景下,我们通常要扫描附近的手机,然后建立连接。

启动扫描:

bluetoothAdapter.startDiscovery();

就这么简单?嗯,表面上看是的。但实际开发中,这里藏着不少坑。

扫描结果回调:

你需要注册一个 BroadcastReceiver 来接收扫描结果:

private final BroadcastReceiver discoveryReceiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        if (BluetoothDevice.ACTION_FOUND.equals(action)) {
            BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
            String deviceName = device.getName();
            String deviceAddress = device.getAddress();
            // 这里可以更新UI,显示发现的设备
        }
    }
};

// 注册接收器
IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
registerReceiver(discoveryReceiver, filter);
经验之谈: 扫描过程中,系统会频繁发送广播。如果你在车载系统上做开发,建议在扫描结束后及时注销接收器,否则会影响系统性能。我曾经在一个项目里忘了注销,结果导致车载中控屏卡顿,被测试同事追着骂了好几天。

停止扫描:

if (bluetoothAdapter.isDiscovering()) {
    bluetoothAdapter.cancelDiscovery();
}

嗯,这里要注意:cancelDiscovery() 并不是立即停止扫描,它只是发送一个停止请求。真正的停止需要等系统处理完当前扫描周期。

4.5 获取适配器信息

获取本机蓝牙适配器的信息,在车载开发中很常用。比如你要在屏幕上显示“本机蓝牙名称:我的车机”,或者调试时需要查看蓝牙地址。

信息项 方法 说明
蓝牙名称 getName() 返回当前蓝牙名称,可修改
蓝牙地址 getAddress() 返回MAC地址,如 00:11:22:AA:BB:CC
扫描状态 isDiscovering() 返回是否正在扫描
蓝牙状态 getState() 返回 STATE_ON、STATE_OFF 等
已绑定设备列表 getBondedDevices() 返回已配对的设备集合

举个例子,获取并显示本机信息:

String name = bluetoothAdapter.getName();
String address = bluetoothAdapter.getAddress();
int state = bluetoothAdapter.getState();
Set<BluetoothDevice> bondedDevices = bluetoothAdapter.getBondedDevices();

Log.d("BluetoothInfo", "名称: " + name);
Log.d("BluetoothInfo", "地址: " + address);
Log.d("BluetoothInfo", "状态: " + (state == BluetoothAdapter.STATE_ON ? "已开启" : "未开启"));
Log.d("BluetoothInfo", "已配对设备数: " + bondedDevices.size());
避坑指南: 我曾经在调试时发现 getAddress() 返回了 02:00:00:00:00:00 这样的地址。后来查资料才知道,从 Android 6.0 开始,出于隐私保护,系统会为未授权的应用返回随机地址。所以,别太依赖蓝牙地址来做设备识别。

4.6 设置设备名称与可见性

车载蓝牙有个典型场景:用户想用手机搜索车机,然后配对。这时候,车机需要有一个友好的名称,并且要处于“可被发现”状态。

设置设备名称:

bluetoothAdapter.setName("我的车载蓝牙");

这个方法会立即生效,并且重启蓝牙后名称仍然保留。不过要注意,名称长度有限制,我记得最长好像是 248 个字节(UTF-8编码)。中文名称一般没问题,但别搞太长的。

设置可见性:

Intent discoverableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300);
startActivity(discoverableIntent);

这里 EXTRA_DISCOVERABLE_DURATION 的单位是秒。最大值是 3600 秒(1小时),设为 0 表示一直可见。但我不建议设 0,原因有两个:

  • 一直可见会持续消耗电量
  • 存在安全隐患,容易被恶意设备扫描到

我一般设 120 到 300 秒,够用户完成配对就行。

重要提醒: 设置可见性会弹出一个系统对话框,需要用户确认。你不能在后台静默设置可见性。这是 Android 的安全机制,别想着绕过它。

4.7 完整初始化流程

好了,把上面的知识点串起来,一个完整的蓝牙适配器初始化流程大概是这样的:

public void initBluetooth() {
    // 1. 获取适配器
    BluetoothManager manager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
    BluetoothAdapter adapter = manager.getAdapter();
    
    if (adapter == null) {
        // 不支持蓝牙,提示用户
        return;
    }
    
    // 2. 检查并启用蓝牙
    if (!adapter.isEnabled()) {
        Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
        startActivityForResult(intent, REQUEST_ENABLE_BT);
        return;
    }
    
    // 3. 设置设备名称
    adapter.setName("CarBT_Device");
    
    // 4. 设置可见性(让手机能搜到)
    Intent discoverable = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
    discoverable.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 180);
    startActivity(discoverable);
    
    // 5. 开始扫描
    adapter.startDiscovery();
    
    // 6. 注册扫描结果接收器
    registerReceiver(discoveryReceiver, new IntentFilter(BluetoothDevice.ACTION_FOUND));
}

这个流程看起来简单,但实际项目中要考虑的东西很多。比如:

  • 如果用户拒绝了蓝牙开启请求怎么办?
  • 扫描过程中用户退出了页面怎么办?
  • 多个页面同时扫描会不会冲突?

这些都是在车载开发中会遇到的真实问题。我建议你在写代码时,把这些边界情况都考虑进去。毕竟,车载系统不像手机,用户可不想在开车时看到一堆莫名其妙的弹窗。

好,这一章就到这里。下一章咱们聊聊蓝牙配对与连接,那才是真正开始“打交道”的时候。