蓝牙LE扫描在后台无法在Android M上运行

时间:2015-09-22 04:26:18

标签: android bluetooth-lowenergy android-bluetooth android-6.0-marshmallow

以下代码适用于运行Android 5.1.1(Build LMY48M)的Nexus 9,但无法在运行Android 6.0的Nexus 9上运行(Build MPA44l)

List<ScanFilter> filters = new ArrayList<ScanFilter>();
ScanSettings settings = (new ScanSettings.Builder().setScanMode(ScanSettings.SCAN_MODE_LOW_POWER)).build();
ScanFilter.Builder builder = new ScanFilter.Builder();
builder.setManufacturerData((int) 0x0118, new byte[]{(byte) 0xbe, (byte) 0xac}, new byte[]{(byte) 0xff, (byte)0xff});
ScanFilter scanFilter = builder.build();
filters.add(scanFilter);
mBluetoothLeScanner.startScan(filters, settings, new ScanCallback() {
  ...
});

在Android 5.x上,当看到与扫描过滤器匹配的制造商广告时,上述代码会产生回调。 (参见下面的示例Logcat输出。)在带有MPA44l的Nexus 9上,没有收到回调。如果您注释掉扫描过滤器,则会在Nexus 9上成功接收回调。

09-22 00:07:28.050    1748-1796/org.altbeacon.beaconreference D/BluetoothLeScanner﹕ onScanResult() - ScanResult{mDevice=00:07:80:03:89:8C, mScanRecord=ScanRecord [mAdvertiseFlags=6, mServiceUuids=null, mManufacturerSpecificData={280=[-66, -84, 47, 35, 68, 84, -49, 109, 74, 15, -83, -14, -12, -111, 27, -87, -1, -90, 0, 1, 0, 1, -66, 0]}, mServiceData={}, mTxPowerLevel=-2147483648, mDeviceName=null], mRssi=-64, mTimestampNanos=61272522487278}

有人看到ScanFilters在Android M上运行吗?

4 个答案:

答案 0 :(得分:30)

我的应用程序连接到蓝牙有类似的问题。不是LE ScanFilter,但它是一个权限问题,就像OP一样。

根本原因是从SDK 23开始,您需要使用Activity的{​​{1}}方法在运行时提示用户获取权限。

这对我有用:

  1. 在根节点内添加以下两行之一到requestPermissions()

    AndroidManifest.xml
  2. 在您的活动中,在尝试连接蓝牙之前,请调用<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> 的{​​{1}}方法,该方法会打开系统对话框以提示用户获取权限。权限对话框在另一个线程中打开,因此请务必在尝试连接蓝牙之前等待结果。

  3. 覆盖Activity的{​​{1}}来处理结果。如果用户拒绝授予权限,该方法实际上只需要做一些事情,告诉用户应用程序无法进行蓝牙活动。

  4. This blog post有一些示例代码,它使用AlertDialogs告诉用户发生了什么。这是一个很好的起点,但有一些缺点:

    • 它不处理等待requestPermissions()线程完成
    • Activity的呼叫包裹起来的AlertDialog对我来说似乎无关紧要。只需拨打onRequestPermissionsResult()即可。

答案 1 :(得分:19)

问题不在于扫描过滤器,而是在应用程序在后台时才使用扫描过滤器。从Android M开始,除非应用具有以下两种权限之一,否则后台蓝牙LE扫描将被阻止:

Sep 21, 2015 4:50:29 AM org.apache.catalina.core.ApplicationDispatcher invoke
SEVERE: Servlet.service() for servlet jsp threw exception
java.lang.NoClassDefFoundError: com/liferay/marketplace/util/PortletPropsValues
        at com.liferay.marketplace.store.portlet.StoreControlPanelEntry.hasAccessPermission(StoreControlPanelEntry.java:33)
        at sun.reflect.GeneratedMethodAccessor857.invoke(Unknown Source)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:606)
        at com.liferay.portal.kernel.bean.ClassLoaderBeanHandler.invoke(ClassLoaderBeanHandler.java:67)
        at com.sun.proxy.$Proxy414.hasAccessPermission(Unknown Source)
---snip---
        at java.lang.Thread.run(Thread.java:745)

我正在测试的应用程序没有请求这些权限中的任何一个,因此它在Android M的后台(在扫描过滤器处于活动状态的唯一时间)不起作用。添加第一个解决了问题。

我意识到这是问题,因为我在Logcat中看到了以下行:

android.permission.ACCESS_COARSE_LOCATION
android.permission.ACCESS_FINE_LOCATION

详情请见此处:https://code.google.com/p/android-developer-preview/issues/detail?id=2964

答案 2 :(得分:10)

添加位置权限以及BLE

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

复制粘贴此方法以请求和授予位置权限

  @Override
    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
        switch (requestCode) {
            case REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS: {
                Map<String, Integer> perms = new HashMap<String, Integer>();
                // Initial
                perms.put(Manifest.permission.ACCESS_FINE_LOCATION, PackageManager.PERMISSION_GRANTED);


                // Fill with results
                for (int i = 0; i < permissions.length; i++)
                    perms.put(permissions[i], grantResults[i]);

                // Check for ACCESS_FINE_LOCATION
                if (perms.get(Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED

                        ) {
                    // All Permissions Granted

                    // Permission Denied
                    Toast.makeText(ScanningActivity.this, "All Permission GRANTED !! Thank You :)", Toast.LENGTH_SHORT)
                            .show();


                } else {
                    // Permission Denied
                    Toast.makeText(ScanningActivity.this, "One or More Permissions are DENIED Exiting App :(", Toast.LENGTH_SHORT)
                            .show();

                    finish();
                }
            }
            break;
            default:
                super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        }
    }


    @TargetApi(Build.VERSION_CODES.M)
    private void fuckMarshMallow() {
        List<String> permissionsNeeded = new ArrayList<String>();

        final List<String> permissionsList = new ArrayList<String>();
        if (!addPermission(permissionsList, Manifest.permission.ACCESS_FINE_LOCATION))
            permissionsNeeded.add("Show Location");

        if (permissionsList.size() > 0) {
            if (permissionsNeeded.size() > 0) {

                // Need Rationale
                String message = "App need access to " + permissionsNeeded.get(0);

                for (int i = 1; i < permissionsNeeded.size(); i++)
                    message = message + ", " + permissionsNeeded.get(i);

                showMessageOKCancel(message,
                        new DialogInterface.OnClickListener() {

                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                requestPermissions(permissionsList.toArray(new String[permissionsList.size()]),
                                        REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS);
                            }
                        });
                return;
            }
            requestPermissions(permissionsList.toArray(new String[permissionsList.size()]),
                    REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS);
            return;
        }

        Toast.makeText(ScanningActivity.this, "No new Permission Required- Launching App .You are Awesome!!", Toast.LENGTH_SHORT)
                .show();
    }

    private void showMessageOKCancel(String message, DialogInterface.OnClickListener okListener) {
        new AlertDialog.Builder(ScanningActivity.this)
                .setMessage(message)
                .setPositiveButton("OK", okListener)
                .setNegativeButton("Cancel", null)
                .create()
                .show();
    }

    @TargetApi(Build.VERSION_CODES.M)
    private boolean addPermission(List<String> permissionsList, String permission) {

        if (checkSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) {
            permissionsList.add(permission);
            // Check for Rationale Option
            if (!shouldShowRequestPermissionRationale(permission))
                return false;
        }
        return true;
    }

然后在onCreate上检查权限

 if (Build.VERSION.SDK_INT >= 23) {
            // Marshmallow+ Permission APIs
            fuckMarshMallow();
        }

希望能节省您的时间。

答案 3 :(得分:0)

如果您的应用定位到Android Q,仅使用粗略位置是不够的,则需要使用精细位置,否则会出现此错误:

E/BluetoothUtils: Permission denial: Need ACCESS_FINE_LOCATION permission to get scan results

有关官方消息,请参见https://developer.android.com/preview/privacy/camera-connectivity#fine-location-telephony-wifi-bt