Android ble藍(lán)牙開發(fā)介紹以及遇到的坑2

Android ble藍(lán)牙開發(fā)

BLE介紹

安卓4.3(API 18)為BLE的核心功能提供平臺(tái)支持和API蛀蜜,App可以利用它來發(fā)現(xiàn)設(shè)備、查詢服務(wù)和讀寫特性增蹭。相比傳統(tǒng)的藍(lán)牙滴某,BLE更顯著的特點(diǎn)是低功耗。這一優(yōu)點(diǎn)使Android App可以與具有低功耗要求的BLE設(shè)備通信滋迈,如近距離傳感器霎奢、心臟速率監(jiān)視器、健身設(shè)備等饼灿。

BLE開發(fā)

BLE權(quán)限添加

為了在app中使用藍(lán)牙功能幕侠,必須聲明藍(lán)牙權(quán)限BLUETOOTH。利用這個(gè)權(quán)限去執(zhí)行藍(lán)牙通信碍彭,例如請(qǐng)求連接晤硕、接受連接、和傳輸數(shù)據(jù)硕旗。如果想讓你的app啟動(dòng)設(shè)備發(fā)現(xiàn)或操縱藍(lán)牙設(shè)置窗骑,必須聲明BLUETOOTH_ADMIN權(quán)限女责。注意:如果你使用BLUETOOTH_ADMIN權(quán)限漆枚,你也必須聲明BLUETOOTH權(quán)限。在你的app manifest文件中聲明藍(lán)牙權(quán)限抵知。

設(shè)置BLE

你的app能與BLE通信之前墙基,你需要確認(rèn)設(shè)備是否支持BLE软族,如果支持,確認(rèn)已經(jīng)啟用残制。雖然現(xiàn)在的手機(jī)基本都支持BLE立砸,但是考慮到程序的健碩性,如果設(shè)置為false,這個(gè)檢查是必需的初茶。

BluetoothAdapter類介紹

獲取:所有的藍(lán)牙活動(dòng)都需要藍(lán)牙適配器颗祝。BluetoothAdapter代表設(shè)備本身的藍(lán)牙適配器(藍(lán)牙無線)。整個(gè)系統(tǒng)只有一個(gè)藍(lán)牙適配器恼布,而且你的app使用它與系統(tǒng)交互螺戳。下面的代碼片段顯示了如何得到適配器。注意該方法使用getSystemService()返回BluetoothManager折汞,然后將其用于獲取適配器的一個(gè)實(shí)例倔幼。Android 4.3(API 18)引入BluetoothManager

// 初始化藍(lán)牙適配器
final BluetoothManager bluetoothManager =

(BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);

mBluetoothAdapter = bluetoothManager.getAdapter();

有了mBluetoothAdapter之后就可以判斷當(dāng)前藍(lán)牙開關(guān)狀態(tài)爽待、藍(lán)牙未開啟情況下代碼里面自動(dòng)開啟藍(lán)牙损同、以及掃描周邊的ble設(shè)備

開啟藍(lán)牙

接下來,你需要確認(rèn)藍(lán)牙是否開啟鸟款。調(diào)用isEnabled()去檢測(cè)藍(lán)牙當(dāng)前是否開啟膏燃。如果該方法返回false,藍(lán)牙被禁用。下面的代碼檢查藍(lán)牙是否開啟欠雌,如果沒有開啟蹄梢,可以提示用戶去設(shè)置開啟藍(lán)牙。

// 確保藍(lán)牙在設(shè)備上可以開啟

if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) {
    //藍(lán)牙未開啟
}

發(fā)現(xiàn)BLE設(shè)備

為了發(fā)現(xiàn)BLE設(shè)備富俄,使用startLeScan()方法禁炒。這個(gè)方法需要一個(gè)參數(shù)BluetoothAdapter.LeScanCallback。你必須實(shí)現(xiàn)它的回調(diào)函數(shù)霍比,那就是返回的掃描結(jié)果幕袱。因?yàn)閽呙璺浅O碾娏浚銘?yīng)當(dāng)遵守以下準(zhǔn)則:

1·只要找到所需的設(shè)備悠瞬,停止掃描们豌。

2·不要在循環(huán)里掃描,并且對(duì)掃描設(shè)置時(shí)間限制浅妆。以前可用的設(shè)備可能已經(jīng)移出范圍望迎,繼續(xù)掃描消耗電池電量。

以下代碼顯示如何掃描設(shè)備和停止掃描設(shè)備

// 10秒后停止尋找.

private static final long SCAN_PERIOD = 10000;

private void scanLeDevice(final boolean enable) {
    if (enable) {
        // 經(jīng)過預(yù)定掃描期后停止掃描
        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);
    }
}

如果你只想掃描指定類型的外圍設(shè)備凌外,可以改為調(diào)用startLeScan(UUID[], BluetoothAdapter.LeScanCallback),需要提供你的app支持的GATT servicesUUID對(duì)象數(shù)組辩尊。

掃描的信息在LeScallCallback里面返回

private BluetoothAdapter.LeScanCallback mLeScanCallback =

    new BluetoothAdapter.LeScanCallback() {
    
        //device 里面包含設(shè)備的mac地址和設(shè)備的名稱
        //scanRecord里面就是ble設(shè)備發(fā)出的廣播包數(shù)據(jù)
        //rssi表示ble設(shè)備的信號(hào)值,該值為負(fù)數(shù)康辑,值越大表示信號(hào)值越好
    
        @Override
        public void onLeScan(final BluetoothDevice device, int rssi,
        byte[] scanRecord) {
            //如果操作UI摄欲,切換主線程
            runOnUiThread(new Runnable() {
            @Override
            public void run() {
                
                ...
            
            }
        
        });
    
    }

};

連接到GATT服務(wù)端

與一個(gè)BLE設(shè)備交互的第一步就是連接它——更具體的轿亮,連接到BLE設(shè)備上的GATT服務(wù)端。為了連接到BLE設(shè)備上的GATT服務(wù)端胸墙,需要使用connectGatt()方法我注。這個(gè)方法需要三個(gè)參數(shù):一個(gè)Context對(duì)象,自動(dòng)連接(boolean值,表示只要BLE設(shè)備可用是否自動(dòng)連接到它)迟隅,和BluetoothGattCallback調(diào)用但骨。

mBluetoothGatt = device.connectGatt(this, false, mGattCallback);

連接到GATT服務(wù)端時(shí),由BLE設(shè)備做主機(jī)智袭,并返回一個(gè)BluetoothGatt實(shí)例嗽冒,然后你可以使用這個(gè)實(shí)例來進(jìn)行GATT客戶端操作。請(qǐng)求方(Android app)是GATT客戶端补履。BluetoothGattCallback用于傳遞結(jié)果給用戶添坊,例如連接狀態(tài),以及任何進(jìn)一步GATT客戶端操作箫锤。

private final BluetoothGattCallback mGattCallback =
new BluetoothGattCallback() {
    @Override
    public void onConnectionStateChange(BluetoothGatt gatt, int status,
    int newState) {//當(dāng)連接狀態(tài)發(fā)生改變
    
        String intentAction;
        if (newState == BluetoothProfile.STATE_CONNECTED) {//當(dāng)藍(lán)牙設(shè)備已經(jīng)連接
        
            //獲取ble設(shè)備上面的服務(wù)
            Log.i(TAG, "Attempting to start service discovery:" +
            mBluetoothGatt.discoverServices());
        
        } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {//當(dāng)設(shè)備  無法連接
        
        }
    
    }
    
    //調(diào)用discoverServices后的回調(diào)    
    @Override
    public void onServicesDiscovered(BluetoothGatt gatt, int status) {
    
        if (status == BluetoothGatt.GATT_SUCCESS) {
        
        //獲取服務(wù)成功
        
        } else {
        
        Log.w(TAG, "onServicesDiscovered received: " + status);
        
        }
    
    }
    // 讀寫特性
    @Override   
    public void onCharacteristicRead(BluetoothGatt gatt,
        BluetoothGattCharacteristic characteristic,int status) {
    
            if (status == BluetoothGatt.GATT_SUCCESS) {
            
            }
        
        }
    
    ...

};

發(fā)送數(shù)據(jù)

首先通過UUID拿到對(duì)應(yīng)的服務(wù)贬蛙,再通過UUID拿到服務(wù)的特征,設(shè)置特征的屬性是BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE。設(shè)置成功后可以在該特征值上發(fā)送數(shù)據(jù)到ble設(shè)備和接收ble設(shè)備的數(shù)據(jù)谚攒⊙糇迹看到這里也許各位不熟ble開發(fā)和剛ble開發(fā)的看官也許就一臉懵逼,我只是想發(fā)送數(shù)據(jù)到ble設(shè)備馏臭,怎么一下子搞出個(gè)UUID 服務(wù)和特征值了,難道就不能和B/S開發(fā)一樣野蝇,連接之后我把數(shù)據(jù)發(fā)送到一個(gè)接口,服務(wù)器端就返回我需要的數(shù)據(jù)那么簡(jiǎn)單。這還得從ble藍(lán)牙的架構(gòu)說起括儒。

BLE分為三部分Service绕沈、Characteristic、Descriptor帮寻,這三部分都由UUID作為唯一標(biāo)示符乍狐。一個(gè)藍(lán)牙4.0的終端可以包含多個(gè)Service,一個(gè)Service可以包含多個(gè)Characteristic固逗,一個(gè)Characteristic包含一個(gè)Value和多個(gè)Descriptor浅蚪,一個(gè)Descriptor包含一個(gè)Value。service是characteristic的集合.一個(gè)characteristic包括一個(gè)單一變量和0-n個(gè)用來描述characteristic變量的descriptor.Descriptor用來描述characteristic變量的屬性烫罩。例如惜傲,一個(gè)descriptor可以規(guī)定一個(gè)可讀的描述,或者一個(gè)characteristic變量可接受的范圍贝攒,或者一個(gè)characteristic變量特定的測(cè)量單位盗誊。一般來說,Characteristic是手機(jī)與BLE終端交換數(shù)據(jù)的關(guān)鍵.。

舉個(gè)栗子:當(dāng)我們想要用手機(jī)與BLE設(shè)備進(jìn)行通信時(shí)浊伙,實(shí)際上也就相當(dāng)于我們要去找一個(gè)學(xué)生交流,首先我們需要搭建一個(gè)管道长捧,也就是我們需要先獲取得到一個(gè)BluetoothGatt嚣鄙,其次我們需要知道這個(gè)學(xué)生在哪一個(gè)班級(jí),學(xué)號(hào)是什么串结,這也就是我們所說的serviceUUID哑子,和charUUID。這里我們還需要注意一下肌割,找到這個(gè)學(xué)生后并不是直接和他交流卧蜓,他就好像一個(gè)中介一樣,在手機(jī)和BLE終端設(shè)備之間幫助這兩者傳遞著信息把敞,我們手機(jī)所發(fā)數(shù)據(jù)要先經(jīng)過他弥奸,在由他傳遞到BLE設(shè)備上,而BLE設(shè)備上的返回信息奋早,也是先傳遞到他那邊盛霎,然后手機(jī)再從他那邊進(jìn)行讀取。

在發(fā)送數(shù)據(jù)之前需先設(shè)置特征的具有notificaion功能

private BluetoothGatt mBluetoothGatt;

BluetoothGattCharacteristic characteristic;

boolean enabled;

mBluetoothGatt.setCharacteristicNotification(characteristic, enabled);

BluetoothGattDescriptor descriptor = characteristic.getDescriptor(
UUID.fromString(SampleGattAttributes.CLIENT_CHARACTERISTIC_CONFIG));

descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE)

mBluetoothGatt.writeDescriptor(descriptor);

設(shè)置完成后回調(diào)

@Override
public final void onDescriptorWrite(final BluetoothGatt gatt, final BluetoothGattDescriptor descriptor, final int status) {

    //設(shè)置成功
    if (status == BluetoothGatt.GATT_SUCCESS) {
    
    }

}

設(shè)置成功后就開始發(fā)送數(shù)據(jù)了耽装。

//將指令放置進(jìn)特征中

characteristic.setValue(data);

//設(shè)置回復(fù)形式characteristic.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE);

//開始寫數(shù)據(jù)

mBluetoothGatt.writeCharacteristic(chharacteristic);

寫入數(shù)據(jù)成功后回調(diào)愤炸,在BluetoothGattCallback里面

protected void onCharacteristicWrite(final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic) {

    //發(fā)送數(shù)據(jù)成功啦啦啦

}

如何設(shè)備回復(fù)數(shù)據(jù)則會(huì)回調(diào)BluetoothGattCallback

@Override
public final void onCharacteristicChanged(final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic) {

}

關(guān)閉客戶端App

當(dāng)你的app完成BLE設(shè)備的使用后,應(yīng)該調(diào)用close()掉奄,系統(tǒng)可以合理釋放占用資源规个。

public void close() {
    if (mBluetoothGatt == null) {
        return;
    }
    mBluetoothGatt.close();
    mBluetoothGatt = null;

}

最后分享我在BLE 開發(fā)中遇到的坑和一些經(jīng)驗(yàn)

1 在所有藍(lán)牙的回調(diào)中不要操作UI。我是不會(huì)告訴你我是怎么發(fā)現(xiàn)這個(gè)坑的姓建。

2 在所有的藍(lán)牙回調(diào)中不要執(zhí)行耗時(shí)操作诞仓。

3 發(fā)送數(shù)據(jù)要等到上一條數(shù)據(jù)發(fā)送成功后再發(fā)下一條數(shù)據(jù),畢竟BLE設(shè)備運(yùn)算沒有手機(jī)快速兔,這里可以推薦一個(gè)nodic的開源藍(lán)牙連接nRf工具,里面非常好的對(duì)發(fā)送的數(shù)據(jù)做了一個(gè)數(shù)據(jù)隊(duì)列狂芋。

4 合理的控制掃描過程,一般出現(xiàn)133錯(cuò)誤的時(shí)候重連就可以先去掃描再去連接憨栽。若掃描不到時(shí)不要馬上又去掃描帜矾,不然你把手機(jī)放那一夜,把設(shè)備遠(yuǎn)離它屑柔,第二天回來看手機(jī)時(shí)會(huì)驚喜的發(fā)現(xiàn)手機(jī)沒電自動(dòng)關(guān)機(jī)了

遇到的坑

1 斷線重連的時(shí)候總是報(bào)133錯(cuò)誤,

斷線后不要馬上去連接.先掃描設(shè)備屡萤,掃描到設(shè)備后再去連接。

2 掃描不到設(shè)備

手動(dòng)關(guān)閉藍(lán)牙再打開藍(lán)牙開關(guān)掸宛。這個(gè)可能是重連里面的掃描引起的死陆,如果設(shè)備未在周邊,一直去掃描的話,后來設(shè)備在身邊也可能掃描不到設(shè)備。如果未能連接設(shè)備措译,也不能一直去掃描别凤。掃描不到設(shè)備時(shí)說明設(shè)備并不到周邊,可以延遲多少時(shí)間后再去掃描

3 連接設(shè)備后發(fā)送數(shù)據(jù)领虹,發(fā)送數(shù)據(jù)的回調(diào)函數(shù)也已經(jīng)走了规哪。沒有接收到數(shù)據(jù)

查看設(shè)置特征值的描述值

descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE)
mBluetoothGatt.writeDescriptor(descriptor)的回調(diào)里面是不是回調(diào)成功了

4反復(fù)斷開藍(lán)牙后再重連導(dǎo)致連接失敗

斷開藍(lán)牙后應(yīng)該調(diào)用close()方法釋放資源.連接時(shí)應(yīng)該設(shè)置超時(shí),在超時(shí)時(shí)間內(nèi)繼續(xù)去連接,基本低塌衰、中诉稍、高端機(jī)都能重新連接上。

5 連接上之后自動(dòng)斷開連接最疆,重連上之后又自動(dòng)斷開連接杯巨,如此反復(fù)。

我們的BLE設(shè)備在某些低端機(jī)會(huì)遇到這種問題努酸。聽固件工程師說是BLE設(shè)備藍(lán)牙芯片頻率和手機(jī)藍(lán)牙頻率問題服爷,需調(diào)BLE設(shè)備頻率。遇到這種問題APP就束手無策了获诈。

6 反復(fù)操作斷開和連接導(dǎo)致系統(tǒng)藍(lán)牙掛掉(無響應(yīng))

基本也是沒有合理釋放資源導(dǎo)致

7 調(diào)用掃描操作導(dǎo)致APP無響應(yīng)

查看系統(tǒng)藍(lán)牙是否掛掉了层扶。基本和問題6類似

參考文章
Android BLE開發(fā)——Android手機(jī)與BLE終端通信初識(shí)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末烙荷,一起剝皮案震驚了整個(gè)濱河市镜会,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌终抽,老刑警劉巖戳表,帶你破解...
    沈念sama閱讀 218,755評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異昼伴,居然都是意外死亡匾旭,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門圃郊,熙熙樓的掌柜王于貴愁眉苦臉地迎上來价涝,“玉大人,你說我怎么就攤上這事持舆∩瘢” “怎么了?”我有些...
    開封第一講書人閱讀 165,138評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵逸寓,是天一觀的道長(zhǎng)居兆。 經(jīng)常有香客問我,道長(zhǎng)竹伸,這世上最難降的妖魔是什么泥栖? 我笑而不...
    開封第一講書人閱讀 58,791評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上吧享,老公的妹妹穿的比我還像新娘魏割。我一直安慰自己,他們只是感情好钢颂,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評(píng)論 6 392
  • 文/花漫 我一把揭開白布钞它。 她就那樣靜靜地躺著,像睡著了一般甸陌。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上盐股,一...
    開封第一講書人閱讀 51,631評(píng)論 1 305
  • 那天钱豁,我揣著相機(jī)與錄音,去河邊找鬼疯汁。 笑死牲尺,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的幌蚊。 我是一名探鬼主播谤碳,決...
    沈念sama閱讀 40,362評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼溢豆!你這毒婦竟也來了蜒简?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,264評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤漩仙,失蹤者是張志新(化名)和其女友劉穎搓茬,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體队他,經(jīng)...
    沈念sama閱讀 45,724評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡卷仑,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了麸折。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片锡凝。...
    茶點(diǎn)故事閱讀 40,040評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖垢啼,靈堂內(nèi)的尸體忽然破棺而出窜锯,到底是詐尸還是另有隱情,我是刑警寧澤芭析,帶...
    沈念sama閱讀 35,742評(píng)論 5 346
  • 正文 年R本政府宣布衬浑,位于F島的核電站,受9級(jí)特大地震影響放刨,放射性物質(zhì)發(fā)生泄漏工秩。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望助币。 院中可真熱鬧浪听,春花似錦、人聲如沸眉菱。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽俭缓。三九已至克伊,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間华坦,已是汗流浹背愿吹。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評(píng)論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留惜姐,地道東北人犁跪。 一個(gè)月前我還...
    沈念sama閱讀 48,247評(píng)論 3 371
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像歹袁,于是被迫代替她去往敵國和親坷衍。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評(píng)論 2 355