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

安卓蓝牙扫描应用在虚拟设备运行正常,真机点击扫描后崩溃如何解决

安卓蓝牙扫描物理机崩溃修复方案

崩溃根因

从报错日志可以定位到空指针触发在ArrayAdapter创建列表条目阶段,核心问题是:

  • 部分无名称的蓝牙设备(比如未命名的BLE设备、广播Beacon设备)调用BluetoothDevice.getName()会返回null,你直接将null存入字符串类型的设备列表后,系统默认列表布局渲染时会调用条目的toString()方法,触发空指针异常。模拟器通常返回模拟的有名称设备,不会触发该问题,物理机扫描真实环境设备时才会出现。
  • 额外潜在问题:安卓6.0以上位置权限、安卓12以上蓝牙扫描权限未正确配置、未判断权限授予状态就启动扫描,也可能引发异常。

修复方案

第一步:补全AndroidManifest.xml权限

在AndroidManifest.xml的manifest标签下添加以下权限,适配不同安卓版本的蓝牙扫描要求:

<!-- 基础蓝牙权限,安卓12及以上无需这两个 -->
<uses-permission android:name="android.permission.BLUETOOTH" android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" android:maxSdkVersion="30" />
<!-- 安卓12+专属蓝牙权限 -->
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" android:usesPermissionFlags="neverForLocation" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<!-- 安卓6.0-11扫描需要位置权限 -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" android:maxSdkVersion="30" />

<!-- 声明应用需要蓝牙硬件,非强制要求 -->
<uses-feature android:name="android.hardware.bluetooth_le" android:required="false" />

第二步:修改MainActivity.java代码

修改后的代码如下,核心调整了空值处理、权限判断、列表清空逻辑:

package com.example.drop_java_test2;

import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;

import android.Manifest;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.ListView;
import android.widget.Toast;

import java.util.ArrayList;

public class MainActivity extends AppCompatActivity {

    Button scanBt;
    ListView scanList;
    ArrayList<String> devicesList = new ArrayList<>();
    ArrayAdapter<String> arrayAdapter;

    BluetoothAdapter btAdapter = BluetoothAdapter.getDefaultAdapter();
    private static final int PERMISSION_REQUEST_CODE = 1001;
    private static final int REQUEST_ENABLE_BT = 1002;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        scanBt = findViewById(R.id.bt);
        scanList = findViewById(R.id.list);

        arrayAdapter = new ArrayAdapter<>(getApplicationContext(), android.R.layout.simple_list_item_1, devicesList);
        scanList.setAdapter(arrayAdapter);

        // 注册蓝牙发现广播
        IntentFilter intentFilter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
        registerReceiver(myReciver, intentFilter);

        scanBt.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // 先判断蓝牙是否开启
                if (btAdapter == null || !btAdapter.isEnabled()) {
                    Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
                    if (ActivityCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED) {
                        requestNecessaryPermissions();
                        return;
                    }
                    startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
                    return;
                }

                // 检查权限是否授予
                if (!checkHasNecessaryPermissions()) {
                    requestNecessaryPermissions();
                    return;
                }

                // 启动扫描前清空列表,避免重复添加
                devicesList.clear();
                arrayAdapter.notifyDataSetChanged();

                // 停止之前的扫描再启动新扫描,避免冲突
                if (btAdapter.isDiscovering()) {
                    btAdapter.cancelDiscovery();
                }
                boolean startSuccess = btAdapter.startDiscovery();
                Toast.makeText(MainActivity.this, startSuccess ? "扫描启动成功" : "扫描启动失败", Toast.LENGTH_SHORT).show();
            }
        });
    }

    // 检查是否已经获取所有必要权限
    private boolean checkHasNecessaryPermissions() {
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S) {
            return ContextCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH_SCAN) == PackageManager.PERMISSION_GRANTED
                    && ContextCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH_CONNECT) == PackageManager.PERMISSION_GRANTED;
        } else {
            return ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED;
        }
    }

    // 动态请求必要权限
    private void requestNecessaryPermissions() {
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S) {
            ActivityCompat.requestPermissions(this, new String[]{
                    Manifest.permission.BLUETOOTH_SCAN,
                    Manifest.permission.BLUETOOTH_CONNECT
            }, PERMISSION_REQUEST_CODE);
        } else {
            ActivityCompat.requestPermissions(this, new String[]{
                    Manifest.permission.ACCESS_FINE_LOCATION
            }, PERMISSION_REQUEST_CODE);
        }
    }

    BroadcastReceiver myReciver = 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);
                if (device == null) return;
                // 设备名称为空时兜底显示MAC地址,避免空值
                String deviceName = device.getName();
                String showName = (deviceName == null || deviceName.trim().isEmpty()) ? "未知设备:" + device.getAddress() : deviceName + "\n" + device.getAddress();
                devicesList.add(showName);
                arrayAdapter.notifyDataSetChanged();
            }
        }
    };

    @Override
    protected void onDestroy() {
        super.onDestroy();
        // 销毁时注销广播、停止扫描,避免内存泄漏
        unregisterReceiver(myReciver);
        if (btAdapter != null && ActivityCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH_SCAN) == PackageManager.PERMISSION_GRANTED) {
            btAdapter.cancelDiscovery();
        }
    }
}

第三步:XML布局无需调整

你提供的布局文件逻辑正常,不需要修改。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.27 05:45:06