
本文详解如何避免因重复创建 recyclerview adapter 导致的空指针异常,通过事件回调机制将扫描到的蓝牙设备安全、高效地传递至其他组件,并完成后续连接逻辑。重点涵盖适配器设计优化、线程安全更新、设备去重与点击事件安全分发。
本文详解如何避免因重复创建 recyclerview adapter 导致的空指针异常,通过事件回调机制将扫描到的蓝牙设备安全、高效地传递至其他组件,并完成后续连接逻辑。重点涵盖适配器设计优化、线程安全更新、设备去重与点击事件安全分发。
在 Android 蓝牙开发中,一个常见却易被忽视的问题是:在 BroadcastReceiver 中频繁重建 RecyclerView.Adapter 并调用 setAdapter(),不仅造成性能浪费,更会导致 BluetoothDevice 对象在跨组件传递时为 null——这正是你遇到 NullPointerException 的根本原因。问题不在于设备本身不可用,而在于数据流设计不合理:ScanActivity 每次发现新设备都新建 Bluetooth_adapter,使旧实例持有的 data 列表与 UI 状态脱节,且未提供安全的设备选择出口。
✅ 正确的数据流设计:单例 Adapter + 回调驱动
核心原则是 “Adapter 只初始化一次,数据由其内部统一管理,选择行为通过接口回调通知宿主”。我们需重构两部分:
1. ScanActivity:精简生命周期,专注事件响应
- 移除全局
ArrayList<bluetoothdevicemodel> data</bluetoothdevicemodel>和重复new Bluetooth_adapter(...); - 在
onCreate()中一次性创建适配器并设置给RecyclerView; - 实现
DeviceSelectListener接口,在onDeviceSelected()中获取选中设备,并启动后续流程(如跳转连接页)。
// ScanActivity.java(关键片段)
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_scan);
RecyclerView recyclerViewSD = findViewById(R.id.Scan_devices_RV);
recyclerViewSD.setLayoutManager(new LinearLayoutManager(this));
// ✅ 仅在此处创建一次适配器
DeviceSelectListener listener = new DeviceSelectListener() {
@Override
public void onDeviceSelected(BluetoothDeviceModel deviceModel) {
// ✅ 安全获取设备信息(非 null!)
String address = deviceModel.getAddress();
String name = deviceModel.getName();
// ? 启动连接 Activity 或执行连接逻辑
Intent intent = new Intent(ScanActivity.this, ConnectActivity.class);
intent.putExtra("device_address", address);
intent.putExtra("device_name", name);
startActivity(intent);
}
};
BluetoothScanListAdapter adapter = new BluetoothScanListAdapter(listener);
recyclerViewSD.setAdapter(adapter); // ✅ 仅设一次
// 其他初始化...
}
2. BluetoothScanListAdapter:职责单一,线程安全更新
- 将设备列表
devices私有化并由 Adapter 自行维护; - 提供
addDevice()方法,内部校验地址去重 +notifyItemInserted(); -
关键:
onBindViewHolder中使用holder.getAbsoluteAdapterPosition()替代参数position,避免因列表异步更新导致位置错乱; - 添加健壮性检查:索引越界、
deviceModel == null。
// BluetoothScanListAdapter.java
public class BluetoothScanListAdapter extends RecyclerView.Adapter<bluetoothviewholder> {
public interface DeviceSelectListener {
void onDeviceSelected(BluetoothDeviceModel deviceModel);
}
private final List<bluetoothdevicemodel> devices;
private final DeviceSelectListener selectListener;
public BluetoothScanListAdapter(DeviceSelectListener selectListener) {
this.devices = new ArrayList();
this.selectListener = selectListener;
}
@Override
public void onBindViewHolder(@NonNull BluetoothViewHolder holder, int position) {
BluetoothDeviceModel device = devices.get(position);
holder.deviceNamePD.setText(device.getName() != null ? device.getName() : device.getAddress());
holder.dropdownImgPD.setImageResource(device.getImageresouce());
holder.cardViewPD.setOnClickListener(v -> {
int index = holder.getAbsoluteAdapterPosition(); // ✅ 安全获取实时位置
if (index = devices.size()) return;
BluetoothDeviceModel selected = devices.get(index);
if (selected == null) return; // 防御性检查
if (selectListener != null) {
selectListener.onDeviceSelected(selected); // ✅ 安全回调
}
});
}
@Override
public int getItemCount() {
return devices.size();
}
// ✅ 线程安全添加设备(需在主线程调用)
public void addDevice(BluetoothDeviceModel device) {
// 地址去重:避免重复显示同一设备
for (BluetoothDeviceModel d : devices) {
if (d.getAddress().equals(device.getAddress())) {
return;
}
}
devices.add(device);
notifyItemInserted(devices.size() - 1);
}
}</bluetoothdevicemodel></bluetoothviewholder>
3. 在 BroadcastReceiver 中安全更新 UI
发现设备后,必须在主线程操作 UI(如调用 addDevice())。原代码中直接在 onReceive() 里操作 RecyclerView 是危险的:
// ScanActivity.java 中 BroadcastReceiver 片段
private final BroadcastReceiver receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (BluetoothDevice.ACTION_FOUND.equals(intent.getAction())) {
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
if (device == null) return;
String name = device.getName();
String address = device.getAddress();
int logoRes = getLogoResource(device.getBluetoothClass()); // 封装逻辑
// ✅ 使用 Handler.post 切换到主线程更新 Adapter
handler.post(() -> {
// 假设 scanListAdapter 已初始化
scanListAdapter.addDevice(new BluetoothDeviceModel(name, address, logoRes));
});
}
}
};
⚠️ 关键注意事项
-
权限适配:Android 12+(API 31+)需动态申请
BLUETOOTH_SCAN、BLUETOOTH_CONNECT等权限,且targetSdkVersion >= 31时registerReceiver()必须指定Context.RECEIVER_EXPORTED或RECEIVER_NOT_EXPORTED。 -
设备去重:蓝牙扫描可能多次返回同一设备,
addDevice()中的地址校验可避免列表重复。 -
空值防御:
BluetoothDevice.getName()可能返回null,UI 展示时务必判空。 -
资源释放:
onPause()中unregisterReceiver()和cancelDiscovery()不可省略,防止内存泄漏与后台扫描耗电。
✅ 总结
解决 NullPointerException 的本质,是摒弃“临时对象传递”的思维,转向状态托管 + 事件驱动架构:
? ScanActivity 不持有设备数据,只响应选择事件;
? BluetoothScanListAdapter 独立管理设备列表,提供安全的增删查接口;
? 所有 UI 更新严格限定在主线程,通过 Handler.post() 或 runOnUiThread() 保证线程安全。
如此设计,既消除了空指针隐患,又为后续连接、配对、数据传输等复杂逻辑提供了清晰、可扩展的入口。











