安卓 - 蓝牙搜索找不到任何设备

49

我目前正在开发一个小应用程序,以开始使用蓝牙Android API提供的服务。

编辑->答案:

看起来问题是由于特定的Nexus 5设备引起的。似乎它们的蓝牙接收器不工作良好。下面的解决方案应该适用于其他设备

备注:

  1. 我已经阅读了这里的文档:http://developer.android.com/guide/topics/connectivity/bluetooth.html,以及此教程http://www.londatiga.net/it/programming/android/how-to-programmatically-scan-or-discover-android-bluetooth-device/的源代码,位于/lorensiuswlt/AndroBluetooth下。

  2. 我几乎完成了我感兴趣的所有功能(例如检查适配器是否存在、启用/禁用蓝牙、查询配对设备、将适配器设置为可发现)。

问题:

实际上,在我启动.onDiscovery()方法时没有找到任何设备,即使在我的Nexus 5上从“设置/蓝牙”中找到设备。

以下是我的处理方式:

public class MainActivity extends AppCompatActivity {
private BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();

...

protected void onCreate(Bundle savedInstanceState) {
IntentFilter filter = new IntentFilter();
filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
filter.addAction(BluetoothDevice.ACTION_FOUND);
filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_STARTED);
filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
registerReceiver(mReceiver, filter); 
}

就我所尝试的,过滤器运行良好,即 ACTION_STATE_CHANGED(在蓝牙启用时)和两个 ACTION_DISCOVERY_***。

接下来成功调用了以下方法:

public void onDiscovery(View view)
{
    mBluetoothAdapter.startDiscovery();
}

然后我有我的蓝牙接收器:

private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();

        if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(action)) {
            final int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR);

            if (state == BluetoothAdapter.STATE_ON) {
                showToast("ACTION_STATE_CHANGED: STATE_ON");
            }
        }

        else if (BluetoothAdapter.ACTION_DISCOVERY_STARTED.equals(action)) {
            mDeviceList = new ArrayList<>();
            showToast("ACTION_DISCOVERY_STARTED");
            mProgressDlg.show();
        }

        else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action) && !bluetoothSwitchedOFF) {
            mProgressDlg.dismiss();
            showToast("ACTION_DISCOVERY_FINISHED");

            Intent newIntent = new Intent(MainActivity.this, DeviceListActivity.class);

            newIntent.putParcelableArrayListExtra("device.list", mDeviceList);

            startActivity(newIntent);
        }

        else if (BluetoothDevice.ACTION_FOUND.equals(action)) {// When discovery finds a device
            // Get the BluetoothDevice object from the Intent
            BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
            mDeviceList.add(device);
            showToast("Device found = " + device.getName());
        }
    }
};

我没有任何问题从logcat中出来,并且在我进行的测试期间没有注意到任何问题。唯一的问题是,在扫描结束时,即使附近有很多可发现设备,没有发现任何设备

为了避免淹没主题,我尝试不放太多代码。如果需要,请问我。

感谢您阅读我的内容,并提前感谢您的回答。


我在我的清单中使用 Nexus 5 获得了位置权限,但是我没有找到任何设备。对我来说缺失的东西是在 MainActivity(或托管搜索的任何活动)中在搜索设备之前没有请求位置权限。这为我解决了问题。 - Khash
3个回答

88

你正在运行哪个版本的Android? 如果是Android 6.x,我相信您需要将ACCESS_FINE_LOCATION权限添加到您的清单中。 例如:

<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/> 

我有过类似的问题,这解决了我的问题。

更新:谷歌直接提供文档来说明:

要通过蓝牙和Wi-Fi扫描访问附近外部设备的硬件标识符,您的应用现在必须具有ACCESS_FINE_LOCATIONACCESS_COARSE_LOCATION权限。

更新2020年:我们最近将应用程序更新为目标SDK版本29。在此过程中,我们的应用程序再次无法发现蓝牙设备。自从最初编写答案以来,我们一直在使用ACCESS_COARSE_LOCATION。切换到ACCESS_FINE_LOCATION似乎可以解决这个问题。我现在建议开发者在面对29+时尝试使用ACCESS_FINE_LOCATION。答案已更新以反映此更改。


1
那个方法没有起作用,而且我真的不明白为什么会起作用,因为这个权限是关于使用Android的网络位置提供程序的。你确定这就是解决你问题的方法吗? - Amesys
是的。我们在Android 5上完全实现了蓝牙功能,升级到6的唯一问题是在发现过程中找不到蓝牙设备。这对我们来说也没有意义。我们正在使用startDiscovery()方法,与您相同。 - nverbeek
你是否已经指定了BLUETOOTHBLUETOOTH_ADMIN权限?我想你可能已经做了,不过还是问一下。 - nverbeek
3
谢谢您的解决方案!也许您还应该提到,仅在清单中设置权限是不够的,还需要在运行时请求权限,因为这两个权限都需要这样做。 - Viacheslav
1
谢谢,奇怪的是https://developer.android.com/guide/topics/connectivity/bluetooth.html上的蓝牙文档没有提到这一点。 - Joe Maher
显示剩余2条评论

76

虽然来晚了,但这对其他人可能有用。

你需要做两件事情:

  1. 在AndroidManifest.xml中添加 <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/> 或者 <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>

保留原文的html标签。

  1. 同样,在Android 6.0设备上运行时确保请求权限,可以使用像这样的代码:

    int MY_PERMISSIONS_REQUEST_ACCESS_COARSE_LOCATION = 1; 
    ActivityCompat.requestPermissions(this,
            new String[]{Manifest.permission.ACCESS_COARSE_LOCATION},
            MY_PERMISSIONS_REQUEST_ACCESS_COARSE_LOCATION);
    
    只是在mBluetoothAdapter.startDiscovery();之前。
    如果您不执行步骤2,则无法获取Android版本大于等于6.0的设备上的任何硬件标识符信息。
    更新/编辑: 带有Android版本检查和警告对话框的示例。
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {  // Only ask for these permissions on runtime when running Android 6.0 or higher
        switch (ContextCompat.checkSelfPermission(getBaseContext(), Manifest.permission.ACCESS_COARSE_LOCATION)) {
            case PackageManager.PERMISSION_DENIED:
                ((TextView) new AlertDialog.Builder(this)
                        .setTitle("Runtime Permissions up ahead")
                        .setMessage(Html.fromHtml("<p>To find nearby bluetooth devices please click \"Allow\" on the runtime permissions popup.</p>" +
                                "<p>For more info see <a href=\"http://developer.android.com/about/versions/marshmallow/android-6.0-changes.html#behavior-hardware-id\">here</a>.</p>"))
                        .setNeutralButton("Okay", new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                if (ContextCompat.checkSelfPermission(getBaseContext(), Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
                                    ActivityCompat.requestPermissions(DeviceListActivity.this,
                                            new String[]{Manifest.permission.ACCESS_COARSE_LOCATION},
                                            REQUEST_ACCESS_COARSE_LOCATION);
                                }
                            }
                        })
                        .show()
                        .findViewById(android.R.id.message))
                        .setMovementMethod(LinkMovementMethod.getInstance());       // Make the link clickable. Needs to be called after show(), in order to generate hyperlinks
                break;
            case PackageManager.PERMISSION_GRANTED:
                break;
        }
    }
    

5
对我来说起了作用,但是如果 startDiscovery() 无法获得访问权限,则应返回 false。它仍然返回 true,这有点具有误导性… - mallaudin
3
我已经连续三天撞墙想让它工作,然后找到了这篇帖子,按照第二步做,哎呀!它工作了。谢谢! - NoLiver92
2
当我将这段代码添加到我的项目中时,有很多未解决的引用问题,但它是缺失的关键 - 谢谢! - ChronoFish
1
我没有给予位置权限。在授权位置权限之后,它可以正常工作。 - Nishanth S Babu
2
我在我的个人Android 8.0.0设备上没有被强制使用“步骤2”。相反,在其他设备上,我被迫以那种方式请求权限。因此,我认为对于所有Android >= 6.0的设备,“步骤2”并非必需品。这需要进一步调查... - bitfox
显示剩余2条评论

0

随着新的安卓版本,一些事情发生了变化。旧的安卓版本(如安卓10)需要FINE_LOCATION权限,而新的安卓版本(如安卓12)也可以使用COARSE_LOCATION权限。因此,我的建议是请求FINE_LOCATION权限,这样在两个版本中都可以使用。不要同时请求COARSE_LOCATION和FINE_LOCATION权限,否则如果用户在应用启动时选择粗略位置,则无法在安卓12及更高版本上工作。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接