Android7.0以上BLE掃描返回空的問題

解決安卓7.0BLE(低功耗藍牙)掃描返回空的問題

Android7.0後之前的採用BluetoothAdapterstartLeScan方法已經無法獲取到ibeacon的信息了。
會報權限安全的錯誤,要求申請下面的權限之一

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

經過查找資料發現,7.0從安全和功耗的角度出發,把BLE掃描跟定位權限綁在一起了(室內定位需要BLE採集數據),因此我們出了上面的靜態權限還需要在代碼裏通過運行時權限獲得GPS定位權限再操作。

查看了Android的藍牙源碼發現,Android7.0修改了藍牙BLE掃描的API,把之前的BlueToothAdapter方法標記爲了Deprecated方法,如圖1所示
圖1 BluetoothAdapter源碼
同時在源碼的註釋裏面推薦我們使用BluetoothLeScanner類的startScan方法。搞清楚了原因就動手修改代碼了。

先貼上7.0以上版本的核心代碼:

BluetoothManager bluetoothManager = (BluetoothManager) getActivity().getSystemService(Context.BLUETOOTH_SERVICE);
BluetoothLeScanner bleScanner = btAdapter.getBluetoothLeScanner();//用過單例的方式獲取實例

開啓BLE掃描

 bleScanner.startScan(scanCallback);
 ScanCallback scanCallback = new ScanCallback() {
        @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
        @Override
        public void onScanResult(int callbackType, ScanResult result) {
            super.onScanResult(callbackType, result);
            BluetoothDevice device = result.getDevice();
            int rssi = result.getRssi();//獲取rssi
            //這裏寫你自己的邏輯
        }
    };

停止掃描的代碼

 bleScanner.stopScan(scanCallback);

此外,不要忘記申請定位權限

if (ContextCompat.checkSelfPermission(Objects.requireNonNull(getActivity()), Manifest.permission.ACCESS_COARSE_LOCATION)
                    != PackageManager.PERMISSION_GRANTED) {
                // 申請定位授權
                ActivityCompat.requestPermissions(Objects.requireNonNull(getActivity()),
                        new String[]{Manifest.permission.ACCESS_COARSE_LOCATION}, LOCATION_CODE);
            } else {
                isScanning = true;
                bleScanner.startScan(scanCallback);
            }
        }

@Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        switch (requestCode) {
            case 110:
                if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    bleImage.setImageDrawable(getResources().getDrawable(R.drawable.ic_bluetooth_searching_black_24dp));

                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                        bleScanner.startScan(scanCallback);//開啓BLE掃描
                    }
                } else
                    Toast.makeText(getContext(), "沒有獲取到定位權限", Toast.LENGTH_LONG).show();
                break;
        }
    }

完整的全版本BLE掃描代碼如下

    //通過HashMap保存掃描到的藍牙信息。可以有效防止重複
    private HashMap<String, String> ibeaconAddrHM = new HashMap<>();
    private StringBuilder TextViewMsg = new StringBuilder();
    private int bleScanTimes = 0;

    //Handler.Callback()可以解決編譯器Handler.leak的提示
    private Handler handler = new Handler(new Handler.Callback() {
        @Override
        public boolean handleMessage(Message msg) {
            switch (msg.what) {
                case 1010:
                    bleText.setText(TextViewMsg.toString() +
                            "\r\n" + "進行了 " + bleScanTimes + " 次掃描"
                    );
                    break;
            }
            return false;
        }
    });
    //region Button Task
    @Override
    public void onClick(View v) {
        int id = v.getId();
        switch (id) {
            case R.id.image_ble://一個藍牙的圖片按鈕
                if (!isScanning) {
                    if (isBluetoothValid()) { //判斷設備是否支持Ble藍牙
                        if (isBluetoothOpen()) { //藍牙已經打開,則開始進行藍牙搜索
                            ourBleScan();//開啓BLE掃描
                        } else {
                            enableBluetooth();//開啓藍牙
                            ourBleScan();//開啓BLE掃描
                        }
                    }
                } else {
                    if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.M) {
                    //安卓6.0及以下版本BLE操作的代碼
                        btAdapter.stopLeScan(mLeScanCallback);
                    } else
                     //安卓7.0及以上版本BLE操作的代碼
                        bleScanner.stopScan(scanCallback);
                    bleScanTimes = 0;
                    Toast.makeText(getContext(), "BLE掃描已經關閉", Toast.LENGTH_SHORT).show();
                    isScanning = false;
                }
                break;
        }
    }
    //endregion

    //region 判斷是否支持藍牙設備
    public boolean isBluetoothValid() {
        if (!getActivity().getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
            return false;
        }
        BluetoothManager bluetoothManager = (BluetoothManager) getActivity().getSystemService(Context.BLUETOOTH_SERVICE);
        btAdapter = bluetoothManager.getAdapter();

        if (btAdapter == null) {
            return false;
        }
        //
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && bleScanner == null) {
            bleScanner = btAdapter.getBluetoothLeScanner();
        }
        return true;
    }
    //endregion

    //region 藍牙是否打開
    private boolean isBluetoothOpen() {
        return btAdapter.isEnabled();
    }
    //endregion

    //region 打開藍牙
    private void enableBluetooth() {
        if (!btAdapter.isEnabled()) {
            btAdapter.enable();
        }
    }
    //endregion

    //region Ble Scan
    private void ourBleScan() {
        if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.M) {//安卓6.0以下的方案
            btAdapter.startLeScan(mLeScanCallback);
            isScanning = true;
        } else {//安卓7.0及以上的方案
            if (ContextCompat.checkSelfPermission(Objects.requireNonNull(getActivity()), Manifest.permission.ACCESS_COARSE_LOCATION)
                    != PackageManager.PERMISSION_GRANTED) {
                // 申請定位授權
                ActivityCompat.requestPermissions(Objects.requireNonNull(getActivity()),
                        new String[]{Manifest.permission.ACCESS_COARSE_LOCATION}, LOCATION_CODE);
            } else {
                isScanning = true;
                bleScanner.startScan(scanCallback);
            }
        }
    }
    //endregion


    //region Android M 以下的回調
    BluetoothAdapter.LeScanCallback mLeScanCallback = new BluetoothAdapter.LeScanCallback() {
        @Override
        public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) {
            // TODO Auto-generated method stub

            bleScanTimes++;//Scan times plus one

            String macAddr = device.getAddress();
            String ibeconInfo = "\r\n" + macAddr + " -- " + rssi + "DB";

            if (macAddr != null) {
                TextViewMsg.delete(0, TextViewMsg.length());//initialize StringBuilder
                ibeaconAddrHM.put(macAddr, ibeconInfo);

                Iterator it = ibeaconAddrHM.keySet().iterator();
                while (it.hasNext()) {
                    TextViewMsg.append(ibeaconAddrHM.get(it.next()));/這裏的TextViewMsg是一個TextView
                }
               //寫入文件,這個方法可以參考我的另一篇博文 Android/安卓開發兩句代碼寫文件到外部存儲
               // WriteToFile.writeToFile(TextViewMsg.toString());
                Message message = new Message();
                message.what = 1010;
                handler.sendMessage(message);
            }
            Log.i(TAG, macAddr);
        }
    };
    //endregion

    //region Android M 以上的回調
    ScanCallback scanCallback = new ScanCallback() {
        @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
        @Override
        public void onScanResult(int callbackType, ScanResult result) {
            super.onScanResult(callbackType, result);
            BluetoothDevice device = result.getDevice();
            int rssi = result.getRssi();

            bleScanTimes++;//Scan times plus one

            String macAddr = device.getAddress();
            String ibeconInfo = "\r\n" + macAddr + " -- " + rssi + "DB";

          if (macAddr != null) {
                TextViewMsg.delete(0, TextViewMsg.length());//initialize StringBuilder
                ibeaconAddrHM.put(macAddr, ibeconInfo);

                Iterator it = ibeaconAddrHM.keySet().iterator();
                while (it.hasNext()) {
                    TextViewMsg.append(ibeaconAddrHM.get(it.next()));/這裏的TextViewMsg是一個TextView
                }
               //寫入文件,這個方法可以參考我的另一篇博文 Android/安卓開發兩句代碼寫文件到外部存儲
               // WriteToFile.writeToFile(TextViewMsg.toString());
                Message message = new Message();
                message.what = 1010;
                handler.sendMessage(message);
            }
            Log.i(TAG, macAddr);
        }
    };
    //endregion

    //region Request Permissions
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {

        switch (requestCode) {
            case 110:
                if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {

                    isScanning = true;
                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                        bleScanner.startScan(scanCallback);
                    }
                } else
                    Toast.makeText(getContext(), "沒有獲取到定位權限", Toast.LENGTH_LONG).show();
                break;

        }
    }
    //endregion
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章