android藍牙BLE(一) —— 掃描

nRF Connect 官方有開源BLE基礎框架,具體查看 這里

前序

? Googleandroid 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位置相關權限-->
<!-- 如果設配Android9及更低版本,可以申請 ACCESS_COARSE_LOCATION -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<!--ble模塊 設置為true表示只有支持ble的手機才能安裝-->
<uses-feature
    android:name="android.hardware.bluetooth_le"
    android:required="true" />

? 由于藍牙掃描需要用到模糊定位權限( Android10 后需要精準定位權限 ),所以android6.0之后,除了在 AndroidManifest.xml中 申明權限之外,還需要動態申請定位權限,才可進行藍牙掃描,否則不會掃描到任何Ble設備。

可依據 PackageManager.hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE) 獲知該手機是否支持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.3android 4.4進行藍牙掃描,可使用BluetoothAdapter.startLeScan(BluetoothAdapter.LeScanCallback)進行藍牙掃描。

//開始掃描
mBluetoothAdapter.startLeScan(mLeScanCallback);
//停止掃描
mBluetoothAdapter.stopLeScan(mLeScanCallback);

android 5.0以上 掃描

android 5.0之后的版本(包括 5.0)建議使用新的Api進行藍牙掃描:

  • BluetoothLeScanner.startScan(ScanCallback)
  • BluetoothLeScanner.startScan(List<ScanFilter>, 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 以上需要獲取到定位權限。否則會報如下運行時異常:
image
  • android 7.0 后不能在30秒內掃描+停止超過5次。(官網沒特意說明,可自行測試,設置掃描時長為3秒,連續掃描10次,穩定復現5次后不能掃描到任何設備。android 藍牙模塊會打印當前應用掃描太頻繁的log日志,并在android 5.0ScanCallback回調中觸發onScanFailed(int),返回錯誤碼:ScanCallback.SCAN_FAILED_APPLICATION_REGISTRATION_FAILED,表示app無法注冊,無法開始掃描)。
image

五、掃描回調

android 4.3 掃描回調:LeScanCallback

android 4.3 的掃描回調接口BluetoothAdapter.LeScanCallback

image

回調接口中只有一個回調函數onLeScan,掃描到的設備會通過該方法返回。
參數:

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

獲取BluetoothDevice中的信息

image

可以從中獲取到設備的mac地址,設備名稱,綁定狀態和設備類型等信息,并作相應的保存。

  • mac可用于再創建BluetoothDevice對象進行gatt連接。

  • 綁定狀態:

    • BOND_NONE:數值 10
      表示遠程設備未綁定,沒有共享鏈接密鑰,因此通信(如果允許的話)將是未經身份驗證和未加密的。(掃描到未綁定的小米手環)
    • BOND_BONDING:數值 11 表示正在與遠程設備進行綁定;
    • BOND_BONDED:數值 12 表示遠程設備已綁定,遠程設備本地存儲共享連接的密鑰,因此可以對通信進行身份驗證和加密。(掃描到已綁定的小米手環)
  • 設備類型:一般是2,表示LE設備

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_APPLICATION_REGISTRATION_FAILED,表示app無法注冊,無法開始掃描。
    
    }
};

ScanCallback掃描回調存在三個回調函數:

  • onScanResult(int,ScanResult):類似于BluetoothAdapter.LeScanCallback中的onLeScan(),可在ScanResult實例中獲取到BluetoothDevice藍牙設備對象,rssi信號值等信息。一般都是在該函數中回調獲取掃描到藍牙設備和信號值,在本函數中執行和onLeScan()中相同的邏輯處理即可。
  • onBatchScanResults(List) 批量返回掃描結果。
  • onScanFailed(int) 掃描失敗返回錯誤碼。

注意事項

  • 回調函數中盡量不要做耗時操作!
  • 一般藍牙設備對象都是通過onScanResult(int,ScanResult)返回,而不會在onBatchScanResults(List)方法中返回,除非手機支持批量掃描模式并且開啟了批量掃描模式。批處理的開啟請查看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 粘性模式,在通過硬件報告之前,需要更高的信號強度和目擊閾值
    • MATCH_MODE_AGGRESSIVE 激進模式,即使信號強度微弱且持續時間內瞄準/匹配的次數很少,hw也會更快地確定匹配。
  • Bluetoothadapter.isOffloadedScanBatchingSupported() 判斷當前手機藍牙芯片是否支持批處理掃描。
    • 如果支持掃描則使用批處理掃描,可通過ScanSettings.Builder對象調用setReportDelay(Long)方法來設置藍牙LE掃描的報告延遲的時間(以毫秒為單位)來啟動批處理掃描模式。
  • ScanSettings.Builder.setReportDelay(Long);
    • 當設備藍牙芯片支持批處理掃描時,用來設置藍牙LE掃描的報告延遲的時間(以毫秒為單位)。
    • 該參數默認為 0,如果不修改它的值,則默認只會在onScanResult(int,ScanResult)中返回掃描到的藍牙設備,不會觸發不會觸發onBatchScanResults(List)方法。(onScanResult(int,ScanResult)onBatchScanResults(List) 是互斥的。 )
    • 設置為0以立即通知結果,不開啟批處理掃描模式。即ScanCallback藍牙回調中,不會觸發onBatchScanResults(List)方法,但會觸發onScanResult(int,ScanResult)方法,返回掃描到的藍牙設備。
    • 當設置的時間大于0L時,則會開啟批處理掃描模式。即觸發onBatchScanResults(List)方法,返回掃描到的藍牙設備列表。但不會觸發onScanResult(int,ScanResult)方法。

提示

  • Android 10 進行BLE掃描時需要打開GPS。

  • 具體源碼可參考Ble實戰BleScanHelper.kt

  • Android 8更新了一個掃描API,系統層為你提供后臺持續掃描的能力。(即便APP已被殺死,掃描仍會繼續。但如果用戶重啟或關閉藍牙后,該掃描停止)。 具體可查看官網: BluetoothLeScanner#startScan

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

android BLE系列:

android藍牙BLE(一) —— 掃描

android藍牙BLE(二) —— 通信

android藍牙BLE(三) —— 廣播

android藍牙BLE(四) —— 實戰

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,967評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,273評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,870評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,742評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,527評論 6 407
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,010評論 1 322
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,108評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,250評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,769評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,656評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,853評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,371評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,103評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,472評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,717評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,487評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,815評論 2 372

推薦閱讀更多精彩內容

  • 用兩張圖告訴你,為什么你的 App 會卡頓? - Android - 掘金 Cover 有什么料? 從這篇文章中你...
    hw1212閱讀 12,772評論 2 59
  • 背景 藍牙歷史說到藍牙,就不得不說下藍牙技術聯盟(Bluetooth SIG),它負責藍牙規范制定和推廣的國際組織...
    徐正峰閱讀 12,445評論 6 33
  • 因為自己的項目中有用到了藍牙相關的功能,所以之前也斷斷續續地針對藍牙通信尤其是BLE通信進行了一番探索,整理出了一...
    陳利健閱讀 115,758評論 172 294
  • 前言: 本文主要描述Android BLE的一些基礎知識及相關操作流程,不牽扯具體的業務實現,其中提供了針對廣播包...
    幻影宇寰閱讀 5,378評論 6 19
  • 【木心說】(分行體) 木心說 對那些跟你地位差不多的朋輩 做論文的時候 你可以用 這樣的題目 《論某某某》 對那些...
    長衣倍倍閱讀 556評論 7 8