[Android] Android 操作 Bluetooth(二)——BLE

0x00 低功耗藍牙(BLE)

上一篇簡單介紹了傳統(tǒng)藍牙設備的使用,當你沿著上一篇的思路去連接某些藍牙設備的時候,你會發(fā)現總是不成功。沒錯,我們還有另外一種藍牙沒有講:低功耗藍牙(BLE: Bluetooth Low Energy)。

這并不是一種新的東西,它只是藍牙協(xié)議中的一個新的版本。之前提到的多是藍牙2.0/2.1,這里的低功耗藍牙(BLE)主要是指藍牙4.0/4.1/4.2。在我們的日常生活中這種藍牙越來越常見,比如各種手環(huán)、體脂秤、智能設備、便攜藍牙設備等等。

更多關于藍牙的介紹可以參考這里

0x01 藍牙協(xié)議棧

和學習 TCP/IP 一樣,如果了解BLE的議棧對我們掌握藍牙會有很大的幫助。

藍牙協(xié)議棧
  • 物理 (PHY) 層

    通過藍牙通信信道控制 2.4Ghz 射頻的傳輸/接收。BR/EDR 提供的信道較多但帶寬較窄,而 LE 使用的信道較少但帶寬較寬。

  • 鏈路層

    定義數據包結構/信道、發(fā)現/連接程序以及發(fā)送/接收數據。

  • 直接測試模式

    允許測試人員向 PHY 層發(fā)出指令以傳輸或接收給定數據包序列,通過 HCI 或 2 線 UART 接口提交命令。

  • 主機控制器接口 (HCI)

    藍牙控制器子系統(tǒng)(底部三層)和藍牙主機之間的可選標準接口。

  • 邏輯鏈路控制和適配協(xié)議 (L2CAP) 層

    基于數據包的協(xié)議,可將數據包傳輸至 HCI 或直接傳輸到無主機系統(tǒng)中的鏈路管理器。支持更高級別的協(xié)議多路復用、數據包分割和重組,以及將服務質量信息傳輸到更高層。

  • 屬性協(xié)議 (ATT)

    在建立連接之后定義數據交換客戶端/服務器協(xié)議。使用通用屬性配置文件 (GATT) 將屬性分類為有意義的服務。ATT 主要用于 LE 部署,偶爾也會用于 BR/EDR 部署。

  • 安全管理器

    定義管理藍牙設備之間配對完整性、身份驗證以及加密的協(xié)議和操作,提供安全功能工具箱,其他組件可利用該工具箱支持不同應用所需的各種安全級別。

  • 通用屬性配置文件 (GATT)

    使用屬性協(xié)議,GATT 對封裝設備組件性能的服務進行分組,并描述基于 GATT 功能的用例、角色和一般性能。其服務框架定義服務規(guī)程和格式及其特性,其中包括發(fā)現、讀取、寫入、通知以及指示特性以及配置特性廣播。GATT 僅用于藍牙 LE 部署。 詳細了解 GATT 信息。

  • 通用訪問配置文件(GAP)

可與藍牙 LE 部署中的 GATT 配合使用,以定義與發(fā)現藍牙設備和共享信息相關的規(guī)程和角色,以及連接藍牙設備的鏈路管理內容。

以上信息來自這里

0x02 Android系統(tǒng)中的BLE

Android系統(tǒng)中對于低功耗藍牙我們需要關心以下幾點:

  • Android 4.3(18)開始支持BLE
  • Android 5.0(21)之前手機只可以作為中心設備(Central mode)使用,5.0之后可以作為外設(Peripheral mode)使用

除了以上信息,我們在對協(xié)議中的幾個概念做個介紹,這涉及到之后的開發(fā):

  • Attribute Protocol (ATT)

    屬性協(xié)議,對應 BluetoothGattService

  • Generic Attribute Profile (GATT)

    通用屬性配置文件,對應Android中的 BluetoothGatt

  • Characteristic

    BluetoothGattCharacteristic

  • Descriptor

    BluetoothGattDescriptor

  • Service

    BluetoothGattService

0x03 Android系統(tǒng)中的BLE操作流程

和之前的普通藍牙相同,我們需要先檢測設備的藍牙是否可用,然后掃描周圍的藍牙設備,然后連接。這里可以看到,BLE的操作并不需要配對。如需要在設備必須支持低功耗藍牙,則還需要加上這句:

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

  • 藍牙掃描

    private boolean mScanning;
    
    private void scanLeDevice(final boolean enable) {
        if (enable) {
            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(final BluetoothDevice device, int rssi, byte[] scanRecord) {
            String format = "Name:%s, Mac:%s, Type:%s";
            String msg = String.format(format, device.getName(), device.getAddress(), device.getType());
            print(msg);
        }
    };
    

    掃描完成之后,我們會拿到藍牙的設備信息,然后就可以進行連接了。

  • 連接藍牙

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        if (null != intent) {
            mMAC = intent.getStringExtra(KEY_MAC);
        }
        if (TextUtils.isEmpty(mMAC)) {
            mMAC = MAC_BIKE;
        }
        mBluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
        mBluetoothAdapter = mBluetoothManager.getAdapter();
        if (null == mBluetoothAdapter) {
            stopSelf();
            return START_NOT_STICKY;
        }
    
        BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(mMAC);
        if (null == device) {
            stopSelf();
            return START_NOT_STICKY;
        }
    
        closeConnect();
    
        mBluetoothGatt = device.connectGatt(this, false, mCallBack);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { //5.0設置的傳輸最大空間
            mBluetoothGatt.requestConnectionPriority(BluetoothGatt.CONNECTION_PRIORITY_HIGH);
            mBluetoothGatt.requestMtu(84);
        }
        print("Gatt connect");
        return START_STICKY;
    }
    
  • 連接藍牙

    在連接的CallBack中如果我們檢測到連接成功,才可以請求藍牙提供的服務,這里先檢測連接的狀態(tài):

        private final BluetoothGattCallback mCallBack = new BluetoothGattCallback() {
        @Override
        public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
            super.onConnectionStateChange(gatt, status, newState);
    
            print(String.format("status:%d, newState:%d", status, newState));
    
            if (status != BluetoothGatt.GATT_SUCCESS) {
                closeConnect();
            }
    
            switch (newState) {
                case BluetoothProfile.STATE_CONNECTED:
                    print("連接GATT服務成功,開始發(fā)現服務...");
                    gatt.discoverServices();
                    break;
                case BluetoothProfile.STATE_DISCONNECTED:
                    print("斷開GATT服務,Bye");
                    closeConnect();
                    break;
    
                default:
                    break;
            }
        }
        ...
    };
    
  • 發(fā)現服務

    private final BluetoothGattCallback mCallBack = new BluetoothGattCallback() {
    
        @Override
        public void onServicesDiscovered(BluetoothGatt gatt, int status) {
            super.onServicesDiscovered(gatt, status);
    
            print("發(fā)現服務:" + status);
    
            if (BluetoothGatt.GATT_SUCCESS == status) {
                List<BluetoothGattService> gattServices = gatt.getServices();
                if (null == gattServices || gattServices.size() == 0) {
                    return;
                }
                for (BluetoothGattService gattService : gattServices) {
                    String serviceUUID = gattService.getUuid().toString();
                    print("UUID GATT:" + serviceUUID);
                    List<BluetoothGattCharacteristic> characteristics = gattService.getCharacteristics();
                    for (BluetoothGattCharacteristic characteristic : characteristics) {
                        String uuid = characteristic.getUuid().toString();
                        print("UUID     Cha:" + uuid);
                        print("UUID     Status:" + getProperties(characteristic));
                        if (UUID_RECEIVE.toString().equalsIgnoreCase(uuid)) {
                            mBluetoothGatt.setCharacteristicNotification(characteristic, true);
                            print("開始監(jiān)聽:" + uuid);
                        }
                    }
                }
            }
        }
        ...
    }
    
  • 使用服務

    ...

0x04 還需要了解的一些細節(jié)

  • UUID
  • 判斷服務的屬性

0x05 可能會遇到的坑

手上的兩臺魅族設備,連接一個客戶提供的藍牙模塊,死活連不上,其他手機連接正常。這兩部設備連接淘寶上買的一個藍牙模塊正常。

0xFF 參考

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

推薦閱讀更多精彩內容