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

0x00 低功耗藍(lán)牙(BLE)

上一篇簡(jiǎn)單介紹了傳統(tǒng)藍(lán)牙設(shè)備的使用,當(dāng)你沿著上一篇的思路去連接某些藍(lán)牙設(shè)備的時(shí)候判族,你會(huì)發(fā)現(xiàn)總是不成功臀稚。沒(méi)錯(cuò)疫粥,我們還有另外一種藍(lán)牙沒(méi)有講:低功耗藍(lán)牙(BLE: Bluetooth Low Energy)蟆湖。

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

更多關(guān)于藍(lán)牙的介紹可以參考這里

0x01 藍(lán)牙協(xié)議棧

和學(xué)習(xí) TCP/IP 一樣,如果了解BLE的議棧對(duì)我們掌握藍(lán)牙會(huì)有很大的幫助。

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

    通過(guò)藍(lán)牙通信信道控制 2.4Ghz 射頻的傳輸/接收喉磁。BR/EDR 提供的信道較多但帶寬較窄,而 LE 使用的信道較少但帶寬較寬悠垛。

  • 鏈路層

    定義數(shù)據(jù)包結(jié)構(gòu)/信道、發(fā)現(xiàn)/連接程序以及發(fā)送/接收數(shù)據(jù)娜谊。

  • 直接測(cè)試模式

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

  • 主機(jī)控制器接口 (HCI)

    藍(lán)牙控制器子系統(tǒng)(底部三層)和藍(lán)牙主機(jī)之間的可選標(biāo)準(zhǔn)接口纱皆。

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

    基于數(shù)據(jù)包的協(xié)議湾趾,可將數(shù)據(jù)包傳輸至 HCI 或直接傳輸?shù)綗o(wú)主機(jī)系統(tǒng)中的鏈路管理器。支持更高級(jí)別的協(xié)議多路復(fù)用派草、數(shù)據(jù)包分割和重組搀缠,以及將服務(wù)質(zhì)量信息傳輸?shù)礁邔印?/p>

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

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

  • 安全管理器

    定義管理藍(lán)牙設(shè)備之間配對(duì)完整性鉴竭、身份驗(yàn)證以及加密的協(xié)議和操作歧譬,提供安全功能工具箱,其他組件可利用該工具箱支持不同應(yīng)用所需的各種安全級(jí)別搏存。

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

    使用屬性協(xié)議瑰步,GATT 對(duì)封裝設(shè)備組件性能的服務(wù)進(jìn)行分組,并描述基于 GATT 功能的用例璧眠、角色和一般性能缩焦。其服務(wù)框架定義服務(wù)規(guī)程和格式及其特性,其中包括發(fā)現(xiàn)责静、讀取袁滥、寫(xiě)入、通知以及指示特性以及配置特性廣播灾螃。GATT 僅用于藍(lán)牙 LE 部署呻拌。 詳細(xì)了解 GATT 信息。

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

可與藍(lán)牙 LE 部署中的 GATT 配合使用睦焕,以定義與發(fā)現(xiàn)藍(lán)牙設(shè)備和共享信息相關(guān)的規(guī)程和角色藐握,以及連接藍(lán)牙設(shè)備的鏈路管理內(nèi)容靴拱。

以上信息來(lái)自這里

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

Android系統(tǒng)中對(duì)于低功耗藍(lán)牙我們需要關(guān)心以下幾點(diǎn):

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

除了以上信息猾普,我們?cè)趯?duì)協(xié)議中的幾個(gè)概念做個(gè)介紹袜炕,這涉及到之后的開(kāi)發(fā):

  • Attribute Protocol (ATT)

    屬性協(xié)議,對(duì)應(yīng) BluetoothGattService

  • Generic Attribute Profile (GATT)

    通用屬性配置文件初家,對(duì)應(yīng)Android中的 BluetoothGatt

  • Characteristic

    BluetoothGattCharacteristic

  • Descriptor

    BluetoothGattDescriptor

  • Service

    BluetoothGattService

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

和之前的普通藍(lán)牙相同偎窘,我們需要先檢測(cè)設(shè)備的藍(lán)牙是否可用,然后掃描周?chē)乃{(lán)牙設(shè)備溜在,然后連接陌知。這里可以看到,BLE的操作并不需要配對(duì)掖肋。如需要在設(shè)備必須支持低功耗藍(lán)牙仆葡,則還需要加上這句:

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

  • 藍(lán)牙掃描

    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);
        }
    };
    

    掃描完成之后,我們會(huì)拿到藍(lán)牙的設(shè)備信息志笼,然后就可以進(jìn)行連接了沿盅。

  • 連接藍(lán)牙

    @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設(shè)置的傳輸最大空間
            mBluetoothGatt.requestConnectionPriority(BluetoothGatt.CONNECTION_PRIORITY_HIGH);
            mBluetoothGatt.requestMtu(84);
        }
        print("Gatt connect");
        return START_STICKY;
    }
    
  • 連接藍(lán)牙

    在連接的CallBack中如果我們檢測(cè)到連接成功,才可以請(qǐng)求藍(lán)牙提供的服務(wù)纫溃,這里先檢測(cè)連接的狀態(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服務(wù)成功腰涧,開(kāi)始發(fā)現(xiàn)服務(wù)...");
                    gatt.discoverServices();
                    break;
                case BluetoothProfile.STATE_DISCONNECTED:
                    print("斷開(kāi)GATT服務(wù),Bye");
                    closeConnect();
                    break;
    
                default:
                    break;
            }
        }
        ...
    };
    
  • 發(fā)現(xiàn)服務(wù)

    private final BluetoothGattCallback mCallBack = new BluetoothGattCallback() {
    
        @Override
        public void onServicesDiscovered(BluetoothGatt gatt, int status) {
            super.onServicesDiscovered(gatt, status);
    
            print("發(fā)現(xiàn)服務(wù):" + 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("開(kāi)始監(jiān)聽(tīng):" + uuid);
                        }
                    }
                }
            }
        }
        ...
    }
    
  • 使用服務(wù)

    ...

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

  • UUID
  • 判斷服務(wù)的屬性

0x05 可能會(huì)遇到的坑

手上的兩臺(tái)魅族設(shè)備紊浩,連接一個(gè)客戶(hù)提供的藍(lán)牙模塊窖铡,死活連不上,其他手機(jī)連接正常坊谁。這兩部設(shè)備連接淘寶上買(mǎi)的一個(gè)藍(lán)牙模塊正常万伤。

0xFF 參考

Create by ttdevs
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市呜袁,隨后出現(xiàn)的幾起案子敌买,更是在濱河造成了極大的恐慌,老刑警劉巖阶界,帶你破解...
    沈念sama閱讀 211,817評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件虹钮,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡膘融,警方通過(guò)查閱死者的電腦和手機(jī)芙粱,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,329評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)氧映,“玉大人春畔,你說(shuō)我怎么就攤上這事。” “怎么了律姨?”我有些...
    開(kāi)封第一講書(shū)人閱讀 157,354評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵振峻,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我择份,道長(zhǎng)扣孟,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,498評(píng)論 1 284
  • 正文 為了忘掉前任荣赶,我火速辦了婚禮凤价,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘拔创。我一直安慰自己利诺,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,600評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布剩燥。 她就那樣靜靜地躺著慢逾,像睡著了一般。 火紅的嫁衣襯著肌膚如雪躏吊。 梳的紋絲不亂的頭發(fā)上氛改,一...
    開(kāi)封第一講書(shū)人閱讀 49,829評(píng)論 1 290
  • 那天帐萎,我揣著相機(jī)與錄音比伏,去河邊找鬼。 笑死疆导,一個(gè)胖子當(dāng)著我的面吹牛赁项,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播澈段,決...
    沈念sama閱讀 38,979評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼悠菜,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了败富?” 一聲冷哼從身側(cè)響起悔醋,我...
    開(kāi)封第一講書(shū)人閱讀 37,722評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎兽叮,沒(méi)想到半個(gè)月后芬骄,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,189評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡鹦聪,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,519評(píng)論 2 327
  • 正文 我和宋清朗相戀三年账阻,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片泽本。...
    茶點(diǎn)故事閱讀 38,654評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡淘太,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情蒲牧,我是刑警寧澤撇贺,帶...
    沈念sama閱讀 34,329評(píng)論 4 330
  • 正文 年R本政府宣布,位于F島的核電站造成,受9級(jí)特大地震影響显熏,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜晒屎,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,940評(píng)論 3 313
  • 文/蒙蒙 一喘蟆、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧鼓鲁,春花似錦蕴轨、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,762評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至燥狰,卻和暖如春棘脐,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背龙致。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,993評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工蛀缝, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人目代。 一個(gè)月前我還...
    沈念sama閱讀 46,382評(píng)論 2 360
  • 正文 我出身青樓屈梁,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親榛了。 傳聞我的和親對(duì)象是個(gè)殘疾皇子在讶,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,543評(píng)論 2 349

推薦閱讀更多精彩內(nèi)容