android藍芽BLE(一) —— 掃描

大棋發表於2018-12-05

android BLE系列:

android藍芽BLE(一) —— 掃描
android藍芽BLE(二) —— 通訊

    Google在android 4.3(API Level 18)的android版本中引入了低功耗藍芽BLE核心API。低功耗藍芽BLE也就是我們經常說的藍芽4.0, 該技術擁有極低的執行和待機功耗,使用一粒鈕釦電池甚至可連續工作數年之久。先不講藍芽協議與藍芽模組一些類的作用與之間的關係,本章僅僅記錄android Ble開發中的掃描模組及其一些細節。

    一、宣告藍芽許可權和定位許可權

<!--藍芽許可權-->
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
<!-- LE Beacons位置相關許可權-->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<!--藍芽模組 設定為true表示只有支援藍芽的手機才能安裝-->
<uses-feature
    android:name="android.hardware.bluetooth_le"
    android:required="true" />
複製程式碼

    由於藍芽掃描需要用到模糊定位許可權,所以android6.0之後,除了在 AndroidManifest.xml中 申明許可權之外,還需要動態申請定位許可權,才可進行藍芽掃描,否則不會掃描到任何Ble裝置。
    二、中心裝置與外圍裝置     Ble開發中,存在著兩個角色:中心裝置角色和外圍裝置角色。粗略瞭解下:

  • 外圍裝置:一般指非常小或者低功耗裝置,更強大的中心裝置可以連線外圍裝置為中心裝置提供資料。外設會不停的向外廣播,讓中心裝置知道它的存在。 例如小米手環。
  • 中心裝置:可以掃描並連線多個外圍裝置,從外設中獲取資訊。


    外圍裝置會設定一個廣播間隔,每個廣播間隔中,都會傳送自己的廣播資料。廣播間隔越長,越省電。一個沒有被連線的Ble外設會不斷髮送廣播資料,這時可以被多箇中心裝置發現。一旦外設被連線,則會馬上停止廣播。
    android 4.3 時引入的Ble核心Api只支援android手機作為中心裝置角色,當android 5.0 更新Api後,android手機支援充當作為外設角色和中心角色。即 android 5.0 引入了外設角色的Api,同時也更新了部分中心角色的Api。比如:中心角色中,更新了藍芽掃描的Api。


    三、開啟藍芽

//初始化ble設配器
BluetoothManager manager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
BluetoothAdapter mBluetoothAdapter = manager.getAdapter();
//判斷藍芽是否開啟,如果關閉則請求開啟藍芽
if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) {
    //方式一:請求開啟藍芽
    Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
    startActivityForResult(intent, 1);
    //方式二:半靜默開啟藍芽
    //低版本android會靜默開啟藍芽,高版本android會請求開啟藍芽
    //mBluetoothAdapter.enable();
}
複製程式碼

    mBluetoothAdapter.isEnabled()判斷當前藍芽是否開啟,如果藍芽處於開啟狀態返回true。
    同時可以在activity層通過廣播監聽藍芽的關閉與開啟,進行自己的邏輯處理:

new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        //獲取藍芽廣播  本地藍芽介面卡的狀態改變時觸發
        String action = intent.getAction();
        if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
            //獲取藍芽廣播中的藍芽新狀態
            int blueNewState = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, 0);
            //獲取藍芽廣播中的藍芽舊狀態
            int blueOldState = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, 0);
            switch (blueNewState) {
                //正在開啟藍芽
                case BluetoothAdapter.STATE_TURNING_ON:
                    break;
                    //藍芽已開啟
                case BluetoothAdapter.STATE_ON:
                    break;
                    //正在關閉藍芽
                case BluetoothAdapter.STATE_TURNING_OFF:
                    break;
                    //藍芽已關閉
                case BluetoothAdapter.STATE_OFF:
                    break;
            }
        }
    }
};
複製程式碼

    四、掃描
    在android 4.3 和 android 4.4進行藍芽掃描中,可使用BluetoothAdapter.startLeScan(BluetoothAdapter.LeScanCallback)進行藍芽掃描。

//開始掃描
mBluetoothAdapter.startLeScan(mLeScanCallback);
//停止掃描
mBluetoothAdapter.stopLeScan(mLeScanCallback);
複製程式碼

    在 android 5.0之後的版本(包括 5.0)建議使用新的Api進行藍芽掃描:
BluetoothLeScanner.startScan(ScanCallback)或
BluetoothLeScanner.startScan(List, ScanSettings, ScanCallback)。

//獲取 5.0 的掃描類例項
mBLEScanner = mBluetoothAdapter.getBluetoothLeScanner();
//開始掃描
//可設定過濾條件,在第一個引數傳入,但一般不設定過濾。
mBLEScanner.startScan(null,mScanSettings,mScanCallback);
//停止掃描
mBLEScanner.stopScan(mScanCallback);
複製程式碼

    藍芽掃描示例:

//如果沒開啟藍芽,不進行掃描操作,或請求開啟藍芽。
if(!mBluetoothAdapter.isEnabled()) {
    return;
}
 //處於未掃描的狀態  
if (!mScanning){
    //android 5.0後
    if(android.os.Build.VERSION.SDK_INT >= 21) {
        //標記當前的為掃描狀態
        mScanning = true;
        //獲取5.0新添的掃描類
        if (mBLEScanner == null){
            //mBLEScanner是5.0新新增的掃描類,通過BluetoothAdapter例項獲取。
            mBLEScanner = mBluetoothAdapter.getBluetoothLeScanner();
        }
        //開始掃描 
        //mScanSettings是ScanSettings例項,mScanCallback是ScanCallback例項,後面進行講解。
        mBLEScanner.startScan(null,mScanSettings,mScanCallback);
    } else {
        //標記當前的為掃描狀態
        mScanning = true;
        //5.0以下  開始掃描
        //mLeScanCallback是BluetoothAdapter.LeScanCallback例項
        mBluetoothAdapter.startLeScan(mLeScanCallback);
    }
    //設定結束掃描
    mHandler.postDelayed(new Runnable() {
        @Override
        public void run() {
            //停止掃描裝置
            if(android.os.Build.VERSION.SDK_INT >= 21) {
                //標記當前的為未掃描狀態
                mScanning = false;
                mBLEScanner.stopScan(mScanCallback);
            } else {
                //標記當前的為未掃描狀態
                mScanning = false;
                //5.0以下  停止掃描
                mBluetoothAdapter.stopLeScan(mLeScanCallback);
            }
        }
    },SCAN_TIME);
}
複製程式碼

    掃描程式碼如上述所示,當掃描到所需要的裝置的時候,就要手動馬上停止藍芽掃描,因為藍芽掃描是耗電操作。但還需要注意兩點:

  • android 6.0 以上需要獲取到定位許可權。否則會爆如下執行時異常:

android藍芽BLE(一) —— 掃描

  • android 7.0 後不能在30秒內掃描和停止超過5次。(官網沒特意說明,可自行測試,設定掃描時長為3秒,連續掃描10次,穩定復現5次後不能掃描到任何裝置。android 藍芽模組會列印當前應用掃描太頻繁的log日誌,並在android 5.0 的ScanCallback回撥中觸發onScanFailed(int),返回錯誤碼:ScanCallback.SCAN_FAILED_APPLICATION_REGISTRATION_FAILED,表示app無法註冊,無法開始掃描)。

android藍芽BLE(一) —— 掃描

    五、掃描回撥
    1、android 4.3 的掃描回撥介面BluetoothAdapter.LeScanCallback:

android藍芽BLE(一) —— 掃描
    回撥介面中只有一個回撥函式onLeScan,掃描到的裝置會通過該方法返回。
引數:

  • BluetoothDevice 掃描到的裝置例項,可從例項中獲取到相應的資訊。如:名稱,mac地址
  • rssi 可理解成裝置的訊號值。該數值是一個負數,越大則訊號越強。
  • scanRecord 遠端裝置提供的廣播記錄的內容。
//5.0以下
mLeScanCallback = new BluetoothAdapter.LeScanCallback() {
    @Override
    public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) {
        //對掃描到的裝置進行操作。如:獲取裝置資訊。
        
    }
};
複製程式碼

    1.1、獲取BluetoothDevice中的資訊:

android藍芽BLE(一) —— 掃描

    可以從中獲取到裝置的mac地址,裝置名稱,繫結狀態和裝置型別等資訊,並作相應的儲存。

    mac可用於再建立BluetoothDevice物件進行gatt連線。

    繫結狀態:
        BOND_NONE:數值 10
    表示遠端裝置未繫結,沒有共享連結金鑰,因此通訊(如果允許的話)將是未經身份驗證和未加密的。(掃描到未繫結的小米手環)

        BOND_BONDING:數值 11
    表示正在與遠端裝置進行繫結;

        BOND_BONDED:數值 12
    表示遠端裝置已繫結,遠端裝置本地儲存共享連線的金鑰,因此可以對通訊進行身份驗證和加密。(掃描到已繫結的小米手環)

    裝置型別:一般是2,表示LE裝置

注:回撥函式中儘量不要做耗時操作!

    2、android 5.0 的掃描回撥介面ScanCallback:

mScanCallback = new ScanCallback() {
    //當一個藍芽ble廣播被發現時回撥
    @Override
    public void onScanResult(int callbackType, ScanResult result) {
        super.onScanResult(callbackType, result);
        //掃描型別有開始掃描時傳入的ScanSettings相關
        //對掃描到的裝置進行操作。如:獲取裝置資訊。
        
    }

    //批量返回掃描結果
    //@param results 以前掃描到的掃描結果列表。
    @Override
    public void onBatchScanResults(List<ScanResult> results) {
        super.onBatchScanResults(results);
        
    }

    //當掃描不能開啟時回撥
    @Override
    public void onScanFailed(int errorCode) {
        super.onScanFailed(errorCode);
        //掃描太頻繁會返回ScanCallback.SCAN_FAILED_ALREADY_STARTED,表示app無法註冊,無法開始掃描。
    
    }
};
複製程式碼

    ScanCallback掃描回撥存在三個回撥函式:

  • onScanResult(int,ScanResult):
    類似於BluetoothAdapter.LeScanCallback中的onLeScan(),可在ScanResult例項中獲取到BluetoothDevice藍芽裝置物件,rssi訊號值等資訊,一般都是在該函式中回撥獲取掃描到藍芽裝置和訊號值,在本函式中執行onLeScan()中相同的邏輯處理即可。
  • onBatchScanResults(List) 批量返回掃描結果
  • onScanFailed(int) 掃描失敗返回錯誤碼

    一般藍芽裝置物件都是通過onScanResult(int,ScanResult)返回,而不會在onBatchScanResults(List)方法中返回,除非手機支援批量掃描模式並且開啟了批量掃描模式。批處理的開啟請檢視ScanSettings。

    3、ScanSettings:
        ScanSettings例項物件是通過ScanSettings.Builder構建的。通過Builder物件為ScanSettings例項設定掃描模式、回撥型別、匹配模式等引數,用於配置android 5.0 的掃描引數。

//建立ScanSettings的build物件用於設定引數
ScanSettings.Builder builder = new ScanSettings.Builder()
    //設定高功耗模式
    .setScanMode(SCAN_MODE_LOW_LATENCY);
    //android 6.0新增設定回撥型別、匹配模式等
    if(android.os.Build.VERSION.SDK_INT >= 23) {
        //定義回撥型別
        builder.setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES)
        //設定藍芽LE掃描濾波器硬體匹配的匹配模式
        builder.setMatchMode(ScanSettings.MATCH_MODE_STICKY);
    }
//晶片組支援批處理晶片上的掃描
if (bluetoothadapter.isOffloadedScanBatchingSupported()) {
    //設定藍芽LE掃描的報告延遲的時間(以毫秒為單位)
    //設定為0以立即通知結果
    builder.setReportDelay(0L);
}
builder.build();
複製程式碼

配置描述:

  • setScanMode() 設定掃描模式
    可選擇模式主要三種:
         ScanSettings.SCAN_MODE_LOW_POWER 低功耗模式
         ScanSettings.SCAN_MODE_BALANCED 平衡模式
         ScanSettings.SCAN_MODE_LOW_LATENCY 高功耗模式
        從上到下,會越來越耗電,但掃描間隔越來越短,即掃描速度會越來越快。

  • setCallbackType() 設定回撥型別
    可選擇模式主要三種:
        ScanSettings.CALLBACK_TYPE_ALL_MATCHES 數值: 1
            尋找符合過濾條件的藍芽廣播,如果沒有設定過濾條件,則返回全部廣播包     ScanSettings.CALLBACK_TYPE_FIRST_MATCH 數值: 2
            僅針對與篩選條件匹配的第一個廣播包觸發結果回撥。     ScanSettings.CALLBACK_TYPE_MATCH_LOST 數值: 4
            

    回撥型別一般設定ScanSettings.CALLBACK_TYPE_ALL_MATCHES,有過濾條件時過濾,返回符合過濾條件的藍芽廣播。無過濾條件時,返回全部藍芽廣播。

  • setMatchMode() 設定藍芽LE掃描濾波器硬體匹配的匹配模式
        一般設定ScanSettings.MATCH_MODE_STICKY

  • Bluetoothadapter.isOffloadedScanBatchingSupported()
        判斷當前手機藍芽晶片是否支援批處理掃描。如果支援掃描則使用批處理掃描,可通過ScanSettings.Builder物件呼叫setReportDelay(Long)方法來設定藍芽LE掃描的報告延遲的時間(以毫秒為單位)來啟動批處理掃描模式。

  • ScanSettings.Builder.setReportDelay(Long);
        當裝置藍芽晶片支援批處理掃描時,用來設定藍芽LE掃描的報告延遲的時間(以毫秒為單位)。
        該引數預設為 0,如果不修改它的值,則預設只會在onScanResult(int,ScanResult)中返回掃描到的藍芽裝置,不會觸發不會觸發onBatchScanResults(List)方法。
        設定為0以立即通知結果,不開啟批處理掃描模式。即ScanCallback藍芽回撥中,不會觸發onBatchScanResults(List)方法,但會觸發onScanResult(int,ScanResult)方法,返回掃描到的藍芽裝置。
        當設定的時間大於0L時,則會開啟批處理掃描模式。即觸發onBatchScanResults(List)方法,返回掃描到的藍芽裝置列表。但不會觸發onScanResult(int,ScanResult)方法。

相關文章