You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

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将任务切换到后台线程。
  • 标准UUID00001101-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

火山引擎 最新活动