藍牙開發(二)掃描設備

一、申請位置權限

在Android6.0以後要掃描藍牙設備,還需要請求位置權限:

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

位置權限屬於危險權限,因此需要動態獲取:

//判斷是否有權限
if (ContextCompat.checkSelfPermission(this,
        Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
//請求權限
      ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.ACCESS_COARSE_LOCATION},MY_PERMISSIONS_REQUEST_ACCESS_COARSE_LOCATION);
    //判斷是否需要 向用戶解釋,爲什麼要申請該權限
    if(ActivityCompat.shouldShowRequestPermissionRationale(this,
            Manifest.permission.READ_CONTACTS)) {
                Toast.makeText(this,"shouldShowRequestPermissionRationale",
Toast.LENGTH_SHORT).show();
    }
}
//權限申請結果
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[]
      grantResults) {
  super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}

在實際開發的過程中你也可以設置只有支持藍牙的設備才能運行該APP:

<uses-feature
        android:name="android.hardware.bluetooth_le"
        android:required="true" />

這裏設置required爲true就是只有在支持藍牙的設備上才能運行,但是如果想讓你的app提供給那些不支持BLE的設備,需要在manifest中包括上面代碼並設置required="false",然後在運行時可以通過使用PackageManager.hasSystemFeature()確定BLE的可用性。

// 使用此檢查確定BLE是否支持在設備上,然後你可以有選擇性禁用BLE相關的功能
if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
    Toast.makeText(this, R.string.ble_not_supported, Toast.LENGTH_SHORT).show();
    finish();
}

二、掃描方式

Android官方提供的藍牙掃描方式有三種,分別是:

  1. BluetoothAdapter.startDiscovery()//可以掃描經典藍牙和ble藍牙兩種
  2. BluetoothAdapter.startLeScan()//掃描低功耗藍牙,在api21已經棄用,不過還是可以使用
  3. BluetoothLeScanner.startScan()//新的ble掃描方法

startDiscovery()方法在大多數手機上是可以同時發現經典藍牙和低功耗藍牙(BLE)的,但是startDiscovery()的回調無法返回BLE的廣播,所以無法通過廣播識別設備,而且startDiscovery()掃描BLE效率比startLeScan()低很多。因此需要根據具體的需求去做適配,才能更高效的搜尋藍牙。PS: startLeScan()和startScan()有重載方法可以指定規則,參數去搜索。

市面上的藍牙設備,現在一般都是低功耗的藍牙設備,因此只需要用下面兩種掃描方式就OK了

2.1.startLeScan方式

官方提供的事例代碼正是使用了這種方式,startLeScan方法有兩個:

public boolean startLeScan(LeScanCallback callback)
public boolean startLeScan(final UUID[] serviceUuids, final LeScanCallback callback)

查看源碼會發現一個參數的方法是調用了兩個參數的方法,只是serviceuuid[]傳入爲null,也就是過濾條件爲空。這裏我使用的是沒有過濾條件的方法:

// Stops scanning after 10 seconds.
    private static final long SCAN_PERIOD = 10000;
    ...
    private void scanLeDevice(final boolean enable) {
        if (enable) {
            // Stops scanning after a pre-defined scan period.
            mHandler.postDelayed(new Runnable() {
                @Override
                public void run() {
                    mScanning = false;
                    mBluetoothAdapter.stopLeScan(mLeScanCallback);
                }
            }, SCAN_PERIOD);

            mScanning = true;
            mBluetoothAdapter.startLeScan(mLeScanCallback);
        } else {
            mScanning = false;
            mBluetoothAdapter.stopLeScan(mLeScanCallback);
        }
        ...
    }

// Device scan callback.
    private BluetoothAdapter.LeScanCallback mLeScanCallback = new BluetoothAdapter.LeScanCallback() {
        @Override
        public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) {
            //掃描到的設備信息,將字符數組轉成16進制字符串
            String scanRecordStr = SysConvert.bytesToHex(scanRecord);
            //在這裏可以做一下簡單的操作,避免多操作造成性能問題
        }
    };

因爲在掃描藍牙是一個非常非常耗能的操作,因此一定要掃描一段時間就關閉,如果需要循環掃描,可以通過定時器實現掃描的開關操作

注意:打開和關閉所使用的回調必須是同一個,雖然關閉一個沒有打開的回調沒有錯誤返回。

2.2.startScan方式

我在項目中是使用startScan()掃描的,因爲可以通過設置一些參數來靈活控制掃描的頻率等。

同樣的有三個重載方法:

public void startScan(final ScanCallback callback)

public void startScan(List<ScanFilter> filters, ScanSettings settings,final ScanCallback callback) 

public int startScan(@Nullable List<ScanFilter> filters, @Nullable ScanSettings settings,
        @NonNull PendingIntent callbackIntent) 

這裏我以第二種爲例:

/**
     * 掃描設備
     */
    public void scanDevices() {
        mHandler.postDelayed(new Runnable() {
            @Override
            public void run() {
                mProgressDialog.dismiss();
                if (mBluetoothLeScanner != null) {
                    mBluetoothLeScanner.stopScan(scanCallback);
                }
            }
        },  1000);
        mBluetoothLeScanner.startScan(null, createScanSetting(), scanCallback);
    } 
/**
     * 掃描廣播數據設置
     *
     * @return
     */
    public ScanSettings createScanSetting() {
        ScanSettings.Builder builder = new ScanSettings.Builder();
        builder.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY);
        //builder.setReportDelay(100);//設置延遲返回時間
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            builder.setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES);
        }
        return builder.build();
    }
/**
* 回調
*/
private ScanCallback scanCallback=new ScanCallback() {
        @Override
        public void onScanResult(int callbackType, ScanResult result) {
            byte[] scanData=result.getScanRecord().getBytes();
            //把byte數組轉成16進制字符串,方便查看

        }
        @Override
        public void onBatchScanResults(List<ScanResult> results) {
            super.onBatchScanResults(results);
        }

        @Override
        public void onScanFailed(int errorCode) {
            super.onScanFailed(errorCode);
        }
    };

ScanSettings :

setScanMode:有三種模式分別是

SCAN_MODE_LOW_POWER--------耗電最少,掃描時間間隔最短

SCAN_MODE_BALANCED---------平衡模式,耗電適中,掃描時間間隔一般,我使用這種模式來更新設備狀態

SCAN_MODE_LOW_LATENCY---------最耗電,掃描延遲時間短,打開掃描需要立馬返回結果可以使用

setReportDelay:設置掃描返回延遲時間,一般是大於零的毫秒值

如果設置了該屬性,掃描結果會以list形式在onBatchScanResults方法返回,在onScanResult中沒有返回,如果不設置則在onScanResult中返回,返回的信息包含在result中,可以通過getBytes返回每一個設備信息的字符數組,getDevice獲取BluetoothDevice設備,在通過字符數組和藍牙設備對象獲取你想要的信息

 

本人也是一個藍牙開發新手,如果上面有什麼地方有問題,請指出,共同探討,共同進步,不勝感激!

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