You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android蓝牙聊天中BroadcastReceiver未被调用的问题排查

解决蓝牙发现后获取设备元数据并启动文本传输的问题

我看你已经在使用广播接收器捕获已发现的蓝牙设备了,接下来我们可以一步步完善这个流程,实现获取设备元数据(比如名称、MAC地址、服务UUID等)并启动文本数据传输的功能。

一、完善广播接收器,提取设备元数据

首先要确保你的广播接收器能正确从发现广播中提取蓝牙设备的关键信息,在onReceive方法中可以直接获取BluetoothDevice对象,进而拿到所需元数据:

private final BroadcastReceiver bluetoothDiscoveryReceiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        // 监听设备发现的广播
        if (BluetoothDevice.ACTION_FOUND.equals(action)) {
            // 从Intent中取出发现的蓝牙设备
            BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
            if (device != null) {
                // 获取核心元数据
                String deviceName = device.getName();
                String deviceMac = device.getAddress();
                // 可选:获取设备支持的服务UUID(需配对后才能拿到完整列表)
                ParcelUuid[] uuids = device.getUuids();
                
                Log.d(TAG, "发现设备: " + (deviceName != null ? deviceName : "未知设备") + " | MAC: " + deviceMac);
                
                // 根据元数据判断是否是你要传输的目标设备,符合条件则启动传输
                if (isTargetDevice(deviceName, deviceMac)) {
                    initiateTextTransfer(device);
                }
            }
        }
    }
};

// 自定义判断逻辑,筛选目标设备
private boolean isTargetDevice(String deviceName, String deviceMac) {
    // 示例:匹配特定设备名称或MAC地址
    return (deviceName != null && deviceName.contains("MyTargetDevice")) 
            || "AA:BB:CC:DD:EE:FF".equals(deviceMac);
}

二、权限配置与广播接收器注册

蓝牙操作需要特定权限,同时要确保广播接收器正确注册和注销:

1. 声明权限(AndroidManifest.xml)

<!-- 基础蓝牙权限 -->
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<!-- Android 12+ 新增的蓝牙权限 -->
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<!-- Android 6+ 蓝牙发现需要位置权限 -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

2. 注册/注销广播接收器

在MainActivity的生命周期方法中处理:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    
    // 初始化你原代码中的控件
    btnListen = findViewById(R.id.btnListen);
    btnSearch = findViewById(R.id.btnSearch);
    btnStop = findViewById(R.id.btnStop);
    incomingTET = findViewById(R.id.incomingTET);
    // ... 其他控件初始化
    
    // 设置点击事件
    btnListen.setOnClickListener(this);
    btnSearch.setOnClickListener(this);
    btnStop.setOnClickListener(this);
    
    // 注册广播接收器
    IntentFilter discoveryFilter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
    registerReceiver(bluetoothDiscoveryReceiver, discoveryFilter);
}

@Override
protected void onDestroy() {
    super.onDestroy();
    // 注销接收器,避免内存泄漏
    unregisterReceiver(bluetoothDiscoveryReceiver);
}

三、实现文本传输逻辑

接下来封装文本传输的方法,通过蓝牙SPP(串口协议)建立连接并发送数据:

private void initiateTextTransfer(BluetoothDevice targetDevice) {
    // 蓝牙SPP服务的标准UUID,大部分蓝牙设备都支持
    UUID sppUuid = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");
    
    // 蓝牙操作不能在主线程执行,开启子线程处理
    new Thread(() -> {
        BluetoothSocket socket = null;
        OutputStream outputStream = null;
        try {
            // 创建Socket并连接目标设备
            socket = targetDevice.createRfcommSocketToServiceRecord(sppUuid);
            socket.connect();
            
            // 获取输出流,发送文本
            outputStream = socket.getOutputStream();
            String textToSend = outgo... // 从你的输入框获取要发送的文本
            outputStream.write(textToSend.getBytes(StandardCharsets.UTF_8));
            outputStream.flush();
            
            // 回到主线程提示用户
            runOnUiThread(() -> 
                Toast.makeText(MainActivity.this, "文本发送成功", Toast.LENGTH_SHORT).show()
            );
        } catch (IOException e) {
            Log.e(TAG, "传输失败: " + e.getMessage());
            runOnUiThread(() -> 
                Toast.makeText(MainActivity.this, "传输失败:" + e.getMessage(), Toast.LENGTH_SHORT).show()
            );
        } finally {
            // 及时关闭资源
            try {
                if (outputStream != null) outputStream.close();
                if (socket != null) socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }).start();
}

四、完善按钮点击逻辑

补充onClick方法,处理搜索、监听、停止操作:

@Override
public void onClick(View v) {
    BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
    if (bluetoothAdapter == null) {
        Toast.makeText(this, "当前设备不支持蓝牙", Toast.LENGTH_SHORT).show();
        return;
    }
    
    if (v.getId() == R.id.btnSearch) {
        // 开启蓝牙设备发现
        if (!bluetoothAdapter.isDiscovering()) {
            bluetoothAdapter.startDiscovery();
            Toast.makeText(this, "开始搜索蓝牙设备", Toast.LENGTH_SHORT).show();
        }
    } else if (v.getId() == R.id.btnListen) {
        // 启动蓝牙服务端,监听文本接收
        startBluetoothServer();
    } else if (v.getId() == R.id.btnStop) {
        // 停止发现或服务
        if (bluetoothAdapter.isDiscovering()) {
            bluetoothAdapter.cancelDiscovery();
        }
        // 如果有服务端线程,也需要在这里停止(可自行维护线程标识)
        Toast.makeText(this, "已停止操作", Toast.LENGTH_SHORT).show();
    }
}

// 可选:实现蓝牙服务端,用于接收文本
private void startBluetoothServer() {
    UUID sppUuid = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");
    BluetoothServerSocket serverSocket = null;
    
    new Thread(() -> {
        try {
            serverSocket = bluetoothAdapter.listenUsingRfcommWithServiceRecord("MyTextServer", sppUuid);
            while (true) {
                // 等待客户端连接
                BluetoothSocket socket = serverSocket.accept();
                InputStream inputStream = socket.getInputStream();
                
                // 读取接收的文本
                byte[] buffer = new byte[1024];
                int bytesRead = inputStream.read(buffer);
                String receivedText = new String(buffer, 0, bytesRead, StandardCharsets.UTF_8);
                
                // 主线程更新UI
                runOnUiThread(() -> {
                    incomingTET.setText(receivedText);
                    Toast.makeText(MainActivity.this, "收到文本", Toast.LENGTH_SHORT).show();
                });
                
                // 关闭资源
                inputStream.close();
                socket.close();
            }
        } catch (IOException e) {
            Log.e(TAG, "服务端异常: " + e.getMessage());
        } finally {
            try {
                if (serverSocket != null) serverSocket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }).start();
}

关键注意事项

  • 动态权限申请:Android 6+ 及更高版本需要动态申请位置权限和蓝牙权限,记得用ActivityCompat.requestPermissions处理权限请求。
  • 配对问题:如果目标设备未配对,连接时会弹出系统配对弹窗,需引导用户完成配对。
  • 线程安全:所有蓝牙IO操作必须在子线程执行,避免触发ANR(应用无响应)。
  • 资源释放:使用完Socket、流等资源后一定要关闭,防止内存泄漏。

内容的提问来源于stack exchange,提问作者Andromeda

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.25 06:18:38