用于蓝牙设备发现的 BroadcastReceiver 适用于一台设备,但不适用于另一台设备

BroadcastReceiver for Bluetooth device discovery works on one device but not on another

代码:

我使用取自 here 的以下代码,目标 API 级别 23(最低 API 级别 18)。

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

        if (BluetoothDevice.ACTION_FOUND.equals(action))
        {
            bluetoothDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
            deviceNameTextView.setText(bluetoothDevice.getName());
        }
    }
};

在按钮按下事件中我调用:

IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
registerReceiver(mReceiver, filter);
mBluetoothAdapter.startDiscovery(); // was initialized successsfully

我的 AndroidManifest.xml 包含:

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

设备:

  1. 三星 Galaxy S III(API 18 级)
  2. 索尼 Xperia Z3(API 23 级)

事实:

怎么了?

更新 1: 由于 API 级别 23 权限可能必须在 运行 时请求。 Yvette 向我指出了这一点,谢谢!不幸的是,它没有解决我的问题。

反对她理论的内容如下:

mBluetoothAdapter.startDiscovery() returns true,表示成功(参见here)。

// Assume thisActivity is the current activity
int permissionCheck = ContextCompat.checkSelfPermission(thisActivity , Manifest.permission.BLUETOOTH_ADMIN);

if(permissionCheck == PackageManager.PERMISSION_GRANTED)
    Log.i("info", "Permission granted!");
else
    Log.i("info", "Permission not granted!");

运行 此代码与 BLUETOOTH_ADMINBLUETOOTH returns 两次:

Permission granted!

听起来您没有管理 runtime permissions SDK 23 及更高版本。 sdk 为 23 的 phone 要么静默忽略任何需要运行时权限的请求,要么崩溃。

另见文档 System Permissions

If your app lists normal permissions in its manifest (that is, permissions that don't pose much risk to the user's privacy or the device's operation), the system automatically grants those permissions. If your app lists dangerous permissions in its manifest (that is, permissions that could potentially affect the user's privacy or the device's normal operation), the system asks the user to explicitly grant those permissions. The way Android makes the requests depends on the system version, and the system version targeted by your app:

If the device is running Android 6.0 (API level 23) or higher, and the app's targetSdkVersion is 23 or higher, the app requests permissions from the user at run-time. The user can revoke the permissions at any time, so the app needs to check whether it has the permissions every time it runs. For more information about requesting permissions in your app, see the Working with System Permissions training guide. If the device is running Android 5.1 (API level 22) or lower, or the app's targetSdkVersion is 22 or lower, the system asks the user to grant the permissions when the user installs the app. If you add a new permission to an updated version of the app, the system asks the user to grant that permission when the user updates the app. Once the user installs the app, the only way they can revoke the permission is by uninstalling the app. Often times a permission failure will result in a SecurityException being thrown back to the application. However, this is not guaranteed to occur everywhere. For example, the sendBroadcast(Intent) method checks permissions as data is being delivered to each receiver, after the method call has returned, so you will not receive an exception if there are permission failures. In almost all cases, however, a permission failure will be printed to the system log.

The permissions provided by the Android system can be found at Manifest.permission. Any application may also define and enforce its own permissions, so this is not a comprehensive list of all possible permissions.

A particular permission may be enforced at a number of places during your program's operation:

  • At the time of a call into the system, to prevent an application from executing certain functions.

  • When starting an activity, to prevent applications from launching activities of other applications.

  • Both sending and receiving broadcasts, to control who can receive your broadcast or who can send a broadcast to you.

  • When accessing and operating on a content provider.

  • Binding to or starting a service.

至于应用程序崩溃:

Everything every Android Developer must know about new Android's Runtime Permission

Next question in your head right now. So will my application crash?

Such a kindness sent from god delivered through the Android team. When we call a function that requires a permission user revoked on application with targetSdkVersion less than 23, no any Exception will be thrown. Instead it will just simply do nothing. For the function that return value, it will return either null or 0 depends on the case.

But don't be too happy. Although application would not be crashed from calling a function. It may still can crash from what that application does next with those returned value.

这些答案中有更多详细信息Require dangerous permissions during installation When asking for runtime permission for location

// Here, thisActivity is the current activity 
if (ContextCompat.checkSelfPermission(thisActivity,
            Manifest.permission.BLUETOOTH) 
    != PackageManager.PERMISSION_GRANTED) {

// Should we show an explanation? 
if (ActivityCompat.shouldShowRequestPermissionRationale(thisActivity,
        Manifest.permission.BLUETOOTH)) { 

    // Show an expanation to the user *asynchronously* -- don't block 
    // this thread waiting for the user's response! After the user 
    // sees the explanation, try again to request the permission. 

} else { 

    // No explanation needed, we can request the permission. 

    ActivityCompat.requestPermissions(thisActivity,
            new String[]{Manifest.permission.BLUETOOTH},
            MY_PERMISSIONS_REQUEST_BLUETOOTH // this variable should be a unique int identifier of your chosing); 

    // MY_PERMISSIONS_REQUEST_BLUETOOTH is an 
    // app-defined int constant. The callback method gets the 
    // result of the request. 
} 
} 

你的 activity 应该实现 onRequestPermissionsResult 方法

@Override 
public void onRequestPermissionsResult(int requestCode,
    String permissions[], int[] grantResults) {
       switch (requestCode) {
       case MY_PERMISSIONS_REQUEST_READ_CONTACTS: { 
          // If request is cancelled, the result arrays are empty. 
          if (grantResults.length > 0
               && grantResults[0] == PackageManager.PERMISSION_GRANTED) {

             // permission was granted, yay! Do the 
             // contacts-related task you need to do. 

           } else { 

            // permission denied, boo! Disable the 
            // functionality that depends on this permission. 
           } 
           return; 
       } 

       // other 'case' lines to check for other 
       // permissions this app might request 
   } 
 } 

您可以对 BLUETOOTH_ADMIN 权限执行相同的操作

在做一些研究时,我从官方文档中找到了 following article 关于 Android 6.0(API 级别 23)的更改。

To access the hardware identifiers of nearby external devices via Bluetooth and Wi-Fi scans, your app must now have the ACCESS_FINE_LOCATION or ACCESS_COARSE_LOCATION permissions:
- WifiManager.getScanResults()
- BluetoothDevice.ACTION_FOUND
- BluetoothLeScanner.startScan()

所以,我一直缺少权限 ACCESS_FINE_LOCATIONACCESS_COARSE_LOCATION。但是仅仅将它们添加到 AndroidManifest.xml 文件中是不够的。您必须像 Yvette 建议的那样在 运行 时间请求这些权限。

您可以找到 here 如何做到这一点,或者只使用我编写的这段代码来获取蓝牙发现所需的权限。

final int CODE = 5; // app defined constant used for onRequestPermissionsResult

String[] permissionsToRequest =
{
    Manifest.permission.BLUETOOTH_ADMIN,
    Manifest.permission.BLUETOOTH,
    Manifest.permission.ACCESS_FINE_LOCATION,
    Manifest.permission.ACCESS_COARSE_LOCATION
};

boolean allPermissionsGranted = true;

for(String permission : permissionsToRequest)
{
    allPermissionsGranted = allPermissionsGranted && (ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED);
}

if(!allPermissionsGranted)
{
    ActivityCompat.requestPermissions(this, permissionsToRequest, CODE);
}

mBluetoothAdapter.startDiscovery();

此代码假设用户授予权限(为简单起见)。如果您希望您的应用在未授予权限时表现不同,请参阅此 article 中的 "Handle the permissions request response"。