Android蓝牙设备连接实现:代码解析与技术咨询
Android蓝牙设备连接代码解析与技术解答
嘿,咱们来一步步拆解这段Android蓝牙设备连接的代码,顺便解答一些你可能遇到的技术问题~首先先把完整的代码补全(原代码有截断),再逐模块分析:
完整补全后的代码
package com.example.stef.bluetooth_version1000; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothSocket; import android.content.Intent; import android.os.Handler; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.view.View; import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.ListView; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.ArrayList; import java.util.List; import java.util.Set; import java.util.UUID; public class MainActivity extends AppCompatActivity { // 本地蓝牙适配器:操作蓝牙的核心入口 private BluetoothAdapter mBluetoothAdapter; // 展示已配对设备的ListView private ListView mPairedDevicesListView; // 存储已配对设备名称+MAC的列表(MAC是设备唯一标识) private List<String> mPairedDeviceNames = new ArrayList<>(); // 用于跨线程更新UI的Handler private Handler mHandler = new Handler(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 绑定UI组件 mPairedDevicesListView = findViewById(R.id.paired_devices_list); // 获取本地蓝牙适配器实例 mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); // 第一步:检查设备是否支持蓝牙 if (mBluetoothAdapter == null) { // 设备无蓝牙模块,直接退出或提示用户 finish(); return; } // 第二步:检查蓝牙是否开启,未开启则请求用户授权开启 if (!mBluetoothAdapter.isEnabled()) { Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); startActivityForResult(enableBtIntent, 1); } else { // 蓝牙已开启,直接加载已配对设备 loadPairedDevices(); } // 第三步:设置ListView点击事件,选中设备后发起连接 mPairedDevicesListView.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { String selectedDeviceInfo = mPairedDeviceNames.get(position); // 从已配对设备集合中找到对应的BluetoothDevice对象 Set<BluetoothDevice> pairedDevices = mBluetoothAdapter.getBondedDevices(); for (BluetoothDevice device : pairedDevices) { String deviceInfo = device.getName() + " (" + device.getAddress() + ")"; if (deviceInfo.equals(selectedDeviceInfo)) { connectToDevice(device); break; } } } }); } /** * 加载所有已配对的蓝牙设备到ListView */ private void loadPairedDevices() { mPairedDeviceNames.clear(); // 获取系统中已配对的蓝牙设备集合 Set<BluetoothDevice> pairedDevices = mBluetoothAdapter.getBondedDevices(); if (!pairedDevices.isEmpty()) { for (BluetoothDevice device : pairedDevices) { // 同时展示设备名称和MAC地址,避免同名设备混淆 mPairedDeviceNames.add(device.getName() + " (" + device.getAddress() + ")"); } // 给ListView设置适配器,展示设备列表 ArrayAdapter<String> adapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, mPairedDeviceNames); mPairedDevicesListView.setAdapter(adapter); } } /** * 处理用户开启蓝牙的结果回调 */ @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == 1) { if (resultCode == RESULT_OK) { // 用户同意开启蓝牙,加载已配对设备 loadPairedDevices(); } else { // 用户拒绝开启蓝牙,退出页面 finish(); } } } /** * 与选中的蓝牙设备建立连接 * 注意:连接操作必须在子线程执行,避免阻塞主线程导致ANR */ private void connectToDevice(BluetoothDevice device) { mHandler.post(new Runnable() { @Override public void run() { try { // 蓝牙串口服务的标准UUID,大部分蓝牙串口设备(如HC-05)都支持 UUID serialUUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB"); // 创建与设备通信的Socket BluetoothSocket socket = device.createRfcommSocketToServiceRecord(serialUUID); // 连接前取消扫描,扫描会占用蓝牙资源,降低连接成功率 mBluetoothAdapter.cancelDiscovery(); // 发起阻塞式连接 socket.connect(); // 连接成功!获取输入输出流用于数据传输 InputStream inputStream = socket.getInputStream(); OutputStream outputStream = socket.getOutputStream(); // 这里可以启动独立线程处理数据读写,避免阻塞当前线程 // startDataTransferThread(inputStream, outputStream); } catch (IOException e) { e.printStackTrace(); // 连接失败,回到主线程提示用户 runOnUiThread(() -> { // Toast.makeText(MainActivity.this, "设备连接失败", Toast.LENGTH_SHORT).show(); }); } } }); } }
代码核心模块解析
1. 蓝牙基础准备
- 蓝牙适配器初始化:
BluetoothAdapter.getDefaultAdapter()是获取本地蓝牙模块的唯一入口,返回null表示设备不支持蓝牙。 - 蓝牙开启请求:使用系统Intent
ACTION_REQUEST_ENABLE请求用户开启蓝牙,这是合规的权限申请方式,不需要自己实现开关逻辑。
2. 已配对设备加载
getBondedDevices():获取系统中所有已完成配对的蓝牙设备集合,这些设备是之前用户手动配对过的。- 展示设备时同时带上MAC地址:因为很多蓝牙设备名称可能重复,MAC地址是设备的唯一标识,能避免选错设备。
3. 设备连接逻辑
- 必须在子线程执行:
socket.connect()是阻塞操作,如果在主线程执行会触发ANR(应用无响应)错误,这里用Handler将任务切换到后台线程。 - 标准UUID:
00001101-0000-1000-8000-00805F9B34FB是蓝牙串口服务的通用UUID,大部分消费级蓝牙设备(如蓝牙模块、蓝牙打印机)都支持这个UUID。 - 取消扫描:连接前调用
cancelDiscovery()关闭蓝牙扫描,扫描会占用大量蓝牙带宽,影响连接成功率。
常见技术问题解答
Q1: 为什么getBondedDevices()获取不到已配对设备?
- 权限问题:Android 12及以上版本需要动态申请
BLUETOOTH_CONNECT权限,静态声明权限已经不够了。 - 蓝牙未开启:必须在蓝牙开启后才能获取已配对设备集合,所以要确保蓝牙状态是开启的。
- 设备绑定失效:部分设备配对后可能会丢失绑定关系,需要重新在系统设置中配对。
Q2: 连接设备时抛出IOException怎么办?
- UUID不匹配:如果是自定义蓝牙服务,需要和设备端使用的UUID完全一致;如果是通用串口设备,检查是否用了上面的标准UUID。
- 设备未进入连接模式:有些蓝牙设备(如HC-05)需要手动触发进入连接模式,才能被其他设备连接。
- 设备已被占用:如果目标设备已经和其他设备建立连接,当前设备无法再连接,需要先断开原有连接。
Q3: 如何扫描未配对的蓝牙设备?
- 注册广播接收器监听
BluetoothDevice.ACTION_FOUND动作,调用startDiscovery()开启扫描:
// 定义广播接收器 private BroadcastReceiver mDiscoveryReceiver = 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); // 将扫描到的设备添加到未配对列表 // unPairedDeviceList.add(device.getName() + " (" + device.getAddress() + ")"); } } }; // 在onCreate中注册接收器 IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND); registerReceiver(mDiscoveryReceiver, filter); // 开启扫描 mBluetoothAdapter.startDiscovery();
注意:Android 12+需要动态申请BLUETOOTH_SCAN权限。
Q4: 为什么不能在主线程处理蓝牙连接?
- Android主线程是UI线程,所有阻塞操作(如网络请求、蓝牙连接)都会导致主线程卡顿,超过5秒就会触发ANR错误,所以必须将这类操作放到后台线程执行。
内容的提问来源于stack exchange,提问作者Stéphane Guillemot




