Android BLE低功耗藍牙開發(fā)

前言

啦啦啦在上一個項目中有用到BLE低功耗藍牙開發(fā),當時baidu google了很多資料鳖枕,但大多數(shù)都是千篇一律魄梯,英文文檔我這種渣渣又看不懂。宾符。酿秸。總之剛開始查的很痛苦吸奴。所以要把自己的踩坑之路寫下來記錄下允扇,缠局,,或許能幫到后來人呢考润?


概念

這是低功耗藍牙的官方文檔,英文好的同學可以直接看看這個:https://developer.android.google.cn/guide/topics/connectivity/bluetooth-le.html

這里寫圖片描述

低功耗藍牙是android4.3(API level18)之后才支持的.
一個低功耗藍牙終端包含多個Service,而Service中又包含多個Characteristic,一個Characteristic又包含一個Value和多個Descriptor.Characteristic是手機和BLE終端交換數(shù)據(jù)的關鍵.
這三部分都是通過UUID來獲取,UUID是他們的唯一標識.

鏈接

掃描的部分我們就不多說了,參考官方Demo寫的很詳細.主要說一說鏈接及發(fā)送消息等.
參考官方Demo,所有的鏈接等操作都是放在一個service中進行的,通過在service中發(fā)送廣播來通知activity來處理相關的數(shù)據(jù).很簡單呦!我們先來看看service中都做了什么~

這是初始化藍牙適配器的方法,在開啟服務時調(diào)用.

/**
     * Initializes a reference to the local Bluetooth adapter.
     * 初始化一個藍牙適配器   里面會判斷系統(tǒng)版本狭园,如果低于18則返回false(低于18不支持低功耗藍牙)
     *
     * @return Return true if the initialization is successful.
     * 返回true表示初始化成功
     */
    public boolean initialize() {
        // For API level 18 and above, get a reference to BluetoothAdapter through
        // BluetoothManager.
        if (mBluetoothManager == null) {
            mBluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
            if (mBluetoothManager == null) {
                //無法初始化藍牙管理器
                Log.e(TAG, "Unable to initialize BluetoothManager.");
                return false;
            }
        }

        mBluetoothAdapter = mBluetoothManager.getAdapter();
        if (mBluetoothAdapter == null) {
            //無法獲取藍牙適配器
            Log.e(TAG, "Unable to obtain a BluetoothAdapter.");
            return false;
        }

        return true;
    }

這是連接藍牙設備的方法,address就是藍牙設備的MAC地址,如果是掃描的則可以通過device.getAddress()來獲取到這個address.

/**
     * Connects to the GATT server hosted on the Bluetooth LE device.
     *
     * 連接成功返回true 異步返回
     *
     * @param address The device address of the destination device.
     *
     * @return Return true if the connection is initiated successfully. The connection result
     *         is reported asynchronously through the
     *         {@code BluetoothGattCallback#onConnectionStateChange(android.bluetooth.BluetoothGatt, int, int)}
     *         callback.
     */
    public boolean connect(final String address) {
        if (mBluetoothAdapter == null || address == null) {
            //還沒有初始化或者指定地址
            Log.w(TAG, "BluetoothAdapter not initialized or unspecified address.");
            return false;
        }

        // Previously connected device.  Try to reconnect.  以前連接的設備   嘗試重新連接
        if (mBluetoothDeviceAddress != null && address.equals(mBluetoothDeviceAddress)
                && mBluetoothGatt != null) {
            Log.d(TAG, "Trying to use an existing mBluetoothGatt for connection.");
            if (mBluetoothGatt.connect()) {
                mConnectionState = STATE_CONNECTING;
                return true;
            } else {
                return false;
            }
        }

        //根據(jù)mac地址獲取設備
        final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address);
        //如果設備為null則表示未獲取到設備
        if (device == null) {
            Log.w(TAG, "Device not found.  Unable to connect.");
            return false;
        }
        // We want to directly connect to the device, so we are setting the autoConnect
        //我們要直接連接到設備,所以我們設置自動連接
        // parameter to false.
        mBluetoothGatt = device.connectGatt(this, false, mGattCallback);
        //試圖創(chuàng)建一個新的連接
        Log.d(TAG, "Trying to create a new connection.");
        mBluetoothDeviceAddress = address;
        mConnectionState = STATE_CONNECTING;
        return true;
    }

在調(diào)用connectGatt(context,autoConnect,callback)方法進行連接時,我們需要傳入一個context,一個boolean值,該參數(shù)表示以后是否需要自動連接到該設備,最后一個是藍牙連接的監(jiān)聽回調(diào)對象BluetoothGattCallback,我們需要new 出來這個對象,重寫他的幾個方法,如下:

藍牙連接狀態(tài)改變的回調(diào):在連接成功之后需要調(diào)用discoverServices方法來找服務,只有找到了服務才算是真正的連接成功.

 @Override
        public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
            super.onConnectionStateChange(gatt, status, newState);
            if (newState == BluetoothProfile.STATE_CONNECTED) {
                mConnectionState = STATE_CONNECTING;
                //開始找服務,只有找到服務才算是真正的連接上了
                mBluetoothGatt.discoverServices();
            } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
                //發(fā)送廣播通知activity連接已斷開
                broadcastUpdate(ACTION_GATT_DISCONNECTED);
                mConnectionState = STATE_DISCONNECTED;
            } else {
                //發(fā)送廣播通知activity連接已斷開
                broadcastUpdate(ACTION_GATT_DISCONNECTED);
                mConnectionState = STATE_DISCONNECTED;
            }
        }

發(fā)現(xiàn)服務的回調(diào):
在找到服務之后就是真正的連接成功了,我在這里設置了對返回數(shù)據(jù)的監(jiān)聽,后面具體說

@Override
        public void onServicesDiscovered(BluetoothGatt gatt, int status) {
            super.onServicesDiscovered(gatt, status);
            if (status == BluetoothGatt.GATT_SUCCESS) {
                //找到了服務,此時才是真正的連接上了設備
                mConnectionState = STATE_CONNECTED;
                //設置監(jiān)聽返回數(shù)據(jù)
                enableTXNotification();
                //發(fā)送廣播通知activity連接成功了
                broadcastUpdate(ACTION_GATT_CONNECTED);
            } else {
                broadcastUpdate(ACTION_GATT_DISCONNECTED);
                mConnectionState = STATE_DISCONNECTED;
            }
        }

Characteristic寫操作的結果回調(diào):
這個監(jiān)聽在分包發(fā)送的時候需要用到,在里面判斷的發(fā)送結果是否成功,成功的話則發(fā)送下一條數(shù)據(jù),這樣可以保證分包發(fā)送的順序不會錯誤.

        @Override
        public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
            super.onCharacteristicWrite(gatt, characteristic, status);
            if (status == BluetoothGatt.GATT_SUCCESS) {
               //發(fā)送下一條數(shù)據(jù)
            }
        }

Characteristic狀態(tài)改變的回調(diào):
這個方法挺重要的,藍牙廣播(是指藍牙設備本身發(fā)出的數(shù)據(jù))出來的數(shù)據(jù)在這里獲取,發(fā)送指令后藍牙的回應也是在這里獲取.當然要走這個回調(diào)必須先設置相關的characteristic的監(jiān)聽.

        @Override
        public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
            super.onCharacteristicChanged(gatt, characteristic);
            //藍牙狀態(tài)   發(fā)送廣播
            byte[] value = characteristic.getValue();
            broadcastUpdate(ACTION_GATT_UPDATE, value);
        }

以上就是BluetoothGattCallback對象中要重寫的幾個方法,下面我們說一說characteristic的監(jiān)聽設置

     /**
     * 設置特征監(jiān)聽
     */
    public void enableTXNotification() {

        BluetoothGattService RxService = mBluetoothGatt.getService(SERVICE_UUID);
        if (RxService == null) {
            L.e("未找到藍牙中的對應服務");
            return;
        }
        BluetoothGattCharacteristic RxChar = RxService.getCharacteristic(RX_UUID);
        if (RxChar == null) {
            L.e("未找到藍牙中的對應特征");
            return;
        }
        //設置true為啟用通知,false反之
        mBluetoothGatt.setCharacteristicNotification(RxChar, true);

        //下面為開啟藍牙notify功能,向CCCD中寫入值1
        BluetoothGattDescriptor descriptor = RxChar.getDescriptor(CCCD);
        descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
        mBluetoothGatt.writeDescriptor(descriptor);
        
        List<BluetoothGattDescriptor> descriptors = RxChar.getDescriptors();
        for (BluetoothGattDescriptor dp : descriptors) {
            dp.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
            mBluetoothGatt.writeDescriptor(dp);
        }
    }
先通過Service的UUID找到對應的Service對象,再通過我們想要設置監(jiān)聽的Characteristic的UUID找到相應的Characteristic對象,UUID都是藍牙設備廠商在文檔中提供的.找到相應的特征后調(diào)用setCharacteristicNotification方法開啟監(jiān)聽,就可以在該特征狀態(tài)發(fā)生改變后走到onCharacteristicChanged方法中.
而藍牙的notify功能需要向CCCD中寫入值1才能開啟,CCCD值為:`public static final UUID CCCD = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb");`

往Characteristic中寫入數(shù)據(jù):
我們知道低功耗藍牙的特點就是快速連接,超低功耗保持連接和傳輸數(shù)據(jù),其缺點是傳輸速率很低,每次最多向Characteristic中寫入20字節(jié)的內(nèi)容.因此當我們需要寫入的數(shù)據(jù)過大時就要使用分包發(fā)送.
先來看看如何往Characteristic中寫入數(shù)據(jù)吧

    /**
     * 往Characteristic中寫入數(shù)據(jù)
     * @param value 寫入的數(shù)據(jù)
     */
    public void writeRXCharacteristic(byte[] value) {

        if (mBluetoothGatt == null) {
            return;
        }

        BluetoothGattService RxService = mBluetoothGatt
                .getService(SERVICE_UUID);
        if (RxService == null) {
//            broadcastUpdate(DEVICE_DOES_NOT_SUPPORT_UART);
            return;
        }
        BluetoothGattCharacteristic RxChar = RxService
                .getCharacteristic(TX_UUID);
        if (RxChar == null) {
//            broadcastUpdate(DEVICE_DOES_NOT_SUPPORT_UART);
            return;
        }
        RxChar.setValue(value);
        mBluetoothGatt.writeCharacteristic(RxChar);
    }

還是要先獲取到Characteristic對象,然后調(diào)用setValue方法設置我們需要寫入的數(shù)據(jù),該方法接受byte[]和String,我們一般都是寫入byte[].基本都要求是十六進制byte[],很多同學會在這里糾結十六進制的轉換、數(shù)組中有些數(shù)據(jù)超出byte取值范圍了等問題来吩,這些你其實完全不用考慮...不用非要寫成0x00這樣的形式,因為對于計算機來說都是一樣的,超出byte取值范圍也不用擔心,沒有什么影響的.不過如果可以的話最好還是能和你們的硬件工程師溝通一下.
分包發(fā)送的話還是需要與硬件工程師溝通具體的分包方法,寫入數(shù)據(jù)是延時多少毫秒,然后在上面onCharacteristicWrite方法中判斷寫入狀態(tài),成功的話則寫入下一條數(shù)據(jù),這樣來保證分包數(shù)據(jù)的順序不會錯亂.

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末宴抚,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子愈捅,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,635評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件窃肠,死亡現(xiàn)場離奇詭異,居然都是意外死亡刷允,警方通過查閱死者的電腦和手機冤留,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,543評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來树灶,“玉大人纤怒,你說我怎么就攤上這事√焱ǎ” “怎么了泊窘?”我有些...
    開封第一講書人閱讀 168,083評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長像寒。 經(jīng)常有香客問我烘豹,道長,這世上最難降的妖魔是什么萝映? 我笑而不...
    開封第一講書人閱讀 59,640評論 1 296
  • 正文 為了忘掉前任吴叶,我火速辦了婚禮,結果婚禮上序臂,老公的妹妹穿的比我還像新娘蚌卤。我一直安慰自己,他們只是感情好奥秆,可當我...
    茶點故事閱讀 68,640評論 6 397
  • 文/花漫 我一把揭開白布逊彭。 她就那樣靜靜地躺著,像睡著了一般构订。 火紅的嫁衣襯著肌膚如雪侮叮。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,262評論 1 308
  • 那天悼瘾,我揣著相機與錄音囊榜,去河邊找鬼审胸。 笑死,一個胖子當著我的面吹牛卸勺,可吹牛的內(nèi)容都是我干的砂沛。 我是一名探鬼主播,決...
    沈念sama閱讀 40,833評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼曙求,長吁一口氣:“原來是場噩夢啊……” “哼碍庵!你這毒婦竟也來了?” 一聲冷哼從身側響起悟狱,我...
    開封第一講書人閱讀 39,736評論 0 276
  • 序言:老撾萬榮一對情侶失蹤静浴,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后挤渐,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體苹享,經(jīng)...
    沈念sama閱讀 46,280評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,369評論 3 340
  • 正文 我和宋清朗相戀三年挣菲,在試婚紗的時候發(fā)現(xiàn)自己被綠了富稻。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,503評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡白胀,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出抚岗,到底是詐尸還是另有隱情或杠,我是刑警寧澤,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布宣蔚,位于F島的核電站向抢,受9級特大地震影響,放射性物質發(fā)生泄漏胚委。R本人自食惡果不足惜挟鸠,卻給世界環(huán)境...
    茶點故事閱讀 41,870評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望亩冬。 院中可真熱鬧艘希,春花似錦、人聲如沸硅急。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,340評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽营袜。三九已至撒顿,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間荚板,已是汗流浹背凤壁。 一陣腳步聲響...
    開封第一講書人閱讀 33,460評論 1 272
  • 我被黑心中介騙來泰國打工吩屹, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人拧抖。 一個月前我還...
    沈念sama閱讀 48,909評論 3 376
  • 正文 我出身青樓煤搜,卻偏偏與公主長得像,于是被迫代替她去往敵國和親徙鱼。 傳聞我的和親對象是個殘疾皇子宅楞,可洞房花燭夜當晚...
    茶點故事閱讀 45,512評論 2 359

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