如何拆分蓝牙连接与消息发送方法?解决空指针异常
拆分蓝牙连接与消息发送方法的解决方案
完全可以将蓝牙连接和消息发送拆分为两个独立方法,你遇到的空指针错误(Attempt to invoke virtual method 'void java.io.OutputStream.write(byte[])' on a null object reference),本质是拆分后没有正确保存连接后的BluetoothSocket、OutputStream等核心对象,导致发送消息时这些对象为null。
下面给出具体的实现方案:
核心思路
- 用类的实例变量保存连接后的
BluetoothSocket、InputStream、OutputStream,确保跨方法访问时对象有效 - 连接方法仅负责建立连接并初始化上述实例变量
- 发送方法复用已保存的实例变量完成消息发送,发送前先校验连接状态
- 蓝牙操作必须在后台线程执行(避免阻塞主线程),React Native的
@ReactMethod默认在主线程,所以要手动切换线程 - 提供连接关闭方法,及时释放资源并重置实例变量
具体代码实现
1. 定义实例变量
在你的蓝牙模块类中添加以下实例变量:
private BluetoothSocket bluetoothSocket; private InputStream inputStream; private OutputStream outputStream; // 通用SPP服务UUID,大部分蓝牙设备支持 private final UUID BLUETOOTH_SPP_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");
2. 连接设备方法
@ReactMethod public void connectToDevice(String deviceAddress, Promise promise) { // 切换到后台线程执行蓝牙连接操作 new Thread(() -> { try { // 先关闭已有连接,避免重复连接 if (bluetoothSocket != null && bluetoothSocket.isConnected()) { closeConnectionInternal(); } BluetoothDevice device = bluetoothAdapter.getRemoteDevice(deviceAddress); bluetoothSocket = device.createRfcommSocketToServiceRecord(BLUETOOTH_SPP_UUID); bluetoothSocket.connect(); // 初始化输入输出流 inputStream = bluetoothSocket.getInputStream(); outputStream = bluetoothSocket.getOutputStream(); // 回到主线程resolve Promise(React Native要求Promise操作在主线程) PromiseUtils.resolvePromiseOnMainThread(promise, "设备连接成功"); } catch (IOException e) { e.printStackTrace(); PromiseUtils.rejectPromiseOnMainThread(promise, new Throwable("连接失败: " + e.getMessage())); } }).start(); }
3. 发送消息方法
@ReactMethod public void sendMessage(String message, Promise promise) { new Thread(() -> { try { // 校验连接状态,避免空指针 if (bluetoothSocket == null || !bluetoothSocket.isConnected() || outputStream == null) { throw new IOException("未建立有效蓝牙连接,请先调用connectToDevice"); } // 发送消息 outputStream.write(message.getBytes()); outputStream.flush(); // 读取设备响应 byte[] buffer = new byte[1024]; int bytesRead = inputStream.read(buffer); String response = new String(buffer, 0, bytesRead); PromiseUtils.resolvePromiseOnMainThread(promise, response); } catch (IOException e) { e.printStackTrace(); PromiseUtils.rejectPromiseOnMainThread(promise, new Throwable("发送失败: " + e.getMessage())); } }).start(); }
4. 关闭连接方法
@ReactMethod public void closeConnection(Promise promise) { new Thread(() -> { try { closeConnectionInternal(); PromiseUtils.resolvePromiseOnMainThread(promise, "连接已关闭"); } catch (IOException e) { e.printStackTrace(); PromiseUtils.rejectPromiseOnMainThread(promise, new Throwable("关闭连接失败: " + e.getMessage())); } }).start(); } // 内部工具方法,统一处理资源释放 private void closeConnectionInternal() throws IOException { if (outputStream != null) { outputStream.close(); outputStream = null; } if (inputStream != null) { inputStream.close(); inputStream = null; } if (bluetoothSocket != null) { bluetoothSocket.close(); bluetoothSocket = null; } }
5. Promise主线程处理工具
需要一个工具类来确保Promise在主线程完成(React Native的Promise必须在调用它的线程(主线程)resolve/reject):
import com.facebook.react.bridge.Promise; import android.os.Handler; import android.os.Looper; public class PromiseUtils { public static void resolvePromiseOnMainThread(Promise promise, Object result) { new Handler(Looper.getMainLooper()).post(() -> promise.resolve(result)); } public static void rejectPromiseOnMainThread(Promise promise, Throwable error) { new Handler(Looper.getMainLooper()).post(() -> promise.reject(error)); } }
关键注意事项
- 线程切换:所有蓝牙相关操作(连接、发送、关闭)都必须在后台线程执行,否则会触发ANR(应用无响应)
- 状态校验:发送消息前一定要检查连接状态,避免因连接断开、未初始化导致的空指针
- 资源释放:不再使用连接时务必调用
closeConnection释放资源,避免内存泄漏 - UUID兼容性:示例中使用的是通用SPP服务UUID,若你的设备使用自定义UUID,需要替换为对应值
内容的提问来源于stack exchange,提问作者new learner
相关产品推荐
相关产品推荐

