以面向對象的思維,搭建Android與多ble藍牙設備併發通訊小框架

  此框架支持多種不同類型的ble設備,同時連接、收發數據,互不干擾。比如APP同時連兩個LED藍牙燈、兩個手環、一個藍牙加熱器,當然連接單個ble設備,或者只連接一種ble設備同樣適用本框架。

前言

  小白請繞道百度,本文適合有一定Android、ble藍牙、面向對象基礎的同學進階探討,只講關鍵技術點,細節自行腦補

  看過很多藍牙demo、開源庫,沒發現真正以面向對象的思維寫的,把自己的一套框架開源出來,希望對看到的有緣人有用,特別是面向對象思維方面。不是說定義了類,就叫面向對象,希望你能領悟

  Android連接多藍牙設備、藍牙與多設備連接、藍牙ble多設備併發操作、Android連接不了、Android ble開發框架、Android 連接藍牙總結

  (連接不可超過7個,極少數手機不可超過5個)

  github源碼:https://github.com/ruigeyun/Android-DualBle

  轉載引用請註明出處,尊重勞動者,讓開源發揚光大!

  以面向對象之名

一、理解業務需求:ble與Android APP通訊的基本內容

  (一)藍牙連接處理基本流程

  如下圖,來自  https://www.jianshu.com/p/1c42074b1430?from=groupmessage ,感謝作者

 

 

    對上圖補充:

    0、app連接ble成功後,才能讀到ble的service uuid,而這個service uuid代表不同類型的設備。

    1、APP與ble可以通訊後,APP發送認證密碼給ble,認證通過後,ble同步自身信息給APP,最終才進入正常業務交互

    2、APP與ble,斷連後,自動重連

    3、APP可主動斷開ble,之後可主動連接ble回來

    4、APP可刪除ble,之後可再掃描連接回來

    5、接收到的藍牙數據包,需要把數據緩存後拼接成完整數據包,極有可能一次收到的數據包不是完整的

    6、藍牙數據分發到對應的業務接口

  (二)Android APP與藍牙多設備連接注意的點:

    1、設備一個一個連,連接成功一個再一個,如果同時連多個,可能一個都連不上。具體原因沒有深究

    2、如果一個設備被你連過,然後一系列操作後,無法再掃描到,用其他工具APP也掃描不到,說明這個設備被你連着,沒有徹底的釋放掉!如何完全釋放ble,具體看源碼,其中部分我也是參考了網上著名的藍牙框架 fastble:https://www.jianshu.com/p/795bb0a08beb ,感謝作者

    3、對APP對ble的每一步操作間,必須加延時,否則會有意想不到的問題。具體看源碼

    4、ble被斷開後,必須延時1-2秒,再去連接他(不通過掃描直接連的情況),否則會有意想不到的問題

二、分析整個系統:

    架構,是模塊及模塊之間的交互

    (一)整個藍牙業務系統分成的模塊:APP與ble連接交互模塊、APP與ble數據交互模塊、APP對所有ble整合管理模塊、其他能動輔助模塊

    1、APP與ble連接的交互:(1)APP掃描ble,必定有一個負責掃描的類;(2)掃描連接所有的ble,需要一個類專門負責連接的類;(3)ble自身的各種狀態以及數據交互,必定就有個ble類來描述這些自身屬性;(3)ble連接成功後,密碼驗證、數據同步、掉線重連,這些ble必須自發的行爲,需要一個類來描述這些藍牙設備自發業務;

    2、APP與ble數據交互:(1)一個格式完整的數據包,以及這個數據包屬於哪個ble,必須由一個數據包類描述;(2)接收到數據,對數據拼包、過濾得到一個有效包的過程,需要一個緩存類描述;(3)完整的數據包最終對外分發,需要一個數據分發類描述;

    3、APP與ble整合管理:(1)統一調配各個ble間的關係(連接、斷開、刪除,發數據等),需要一個調配服務中心類描述;(2)對整個藍牙框架的管理,掃描、連接、數據處理等整合起來,需要一個框架管理類描述。並且這個類作爲此框架對外的門面,所有對外的操作,都得通過它,達到隱藏這個框架的其他複雜細節的目的

    4、其他能動輔助:各種工具、日誌調試

    (二)最終提取到的對象:

    1、APP與ble建立連接:掃描器,連接器,ble藍牙屬性設備,藍牙設備自發業務(重連、認證、同步)

    2、APP與ble數據交互:接收數據拼包緩存,數據包,數據分發器

    3、APP與ble整合管理:設備間調配服務中心,框架管理類

    4、其他能動輔助:工具、日誌

    這裏最關鍵的一個對象設計:ble藍牙屬性設備(BLELogicDevice)每個藍牙設備提煉成一個對象,APP每連接一個設備,就開闢一個此對象。每個對象分配一個mDeviceId。每個對象都有 BluetoothGattCallback 數據交互接口,這樣每個對象跟自己對應的ble設備單獨交互,互不相干。從一大堆掃描、回調、管理中解耦出來。每個設備對象從回調方法onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic)拿到數據,把數據緩存到一個自身的數據緩存區(每個設備對象都有一個緩存區對象),在緩存中拼接成數據包,數據都攜帶mDeviceId作爲標誌,對外分發。

    另一個關鍵對象:設備間調配服務中心(BLEServerCentral),所有設備掛在其鏈表中,其負責維護各個設備對象的狀態(連接、斷開、刪除等),控制APP與各設備數據交

三、設計小結

  1、面向對象的思維:需求分析、系統構思、細化流程、提煉對象、對象整合,最終把整個系統完整描述清楚。根據自己的設計粒度,每一個類都去描繪一個事物,負有單一的職責,這是建立一個類的最基本原則。不是隨意定義了類,然後一大堆if else邏輯,面向過程的思維解決問題。如果你的代碼中,if else if 超過三層,說明你的代碼耦合度過高了,需要拆分整合了

  2、以上只是寫了關鍵的設計思路,源碼有很多拓展的地方,有緣人可以自己閱讀,代碼其實沒多少行,慢慢仔細看一下就明白了,不懂的可以在博客留言,我儘可能答覆

  3、我把這個module做成了庫,自己運行下makeClockJar,就可以導出jar包,接口如何使用參考源碼

  4、源碼藍牙接收特徵配置成通知方式,其他方式自行拓展。

  5、要添加很多新的行爲,其實是很容易拓展的,比如添加一個配置特徵,專門配置藍牙參數的。你讀得懂源碼,很容易添加

四、庫的用法

  demo裏面,有具體的栗子,仔細閱讀下,很多註釋的,應該容易理解

  1、建立自己的藍牙設備對象,demo中有兩種藍牙設備,藍牙控制led的設備(LedDevice)、藍牙控制加熱器設的備(HeaterDevice),他們繼承藍牙庫的對外設備(BLEAppDevice),添加自己的新特徵,如led燈顏色,heater定時時間。藍牙對象必須包含自己的服務、發送、接收三種uuid,以及自定義一個設備類型id重寫三個抽象方法,把uuid寫進去。構造方法必須如下的方式,固定兩個參數,並且調用父類的構造方法。

public class LedDevice extends BLEAppDevice {
    private final String TAG = "BLELedDevice";
    
    public static final Integer DEVICE_TYPE_ID = 1002;
    public static final UUID SERVICE_UUID = UUID.fromString("0000ff**-0000-1000-8000-00805f9b34fb");
    private final UUID RX_CHAR_UUID = UUID.fromString("0000ff**-0000-1000-8000-00805f9b34fb");
    private final UUID TX_CHAR_UUID = UUID.fromString("0000ff**-0000-1000-8000-00805f9b34fb");

    @Override
    public UUID getServiceUUID() {
        return SERVICE_UUID;
    }
    @Override
    public UUID getRxUUID() {
        return RX_CHAR_UUID;
    }
    @Override
    public UUID getTxUUID() {
        return TX_CHAR_UUID;
    }
    
    public String dualColor = "";
    public String hardwareVersion = "";
    public int powerState = 0;
    public String nickname = "";
    
    public LedDevice(BluetoothDevice device, DataParserAdapter adapter) {
        super(device, adapter);
        
    }
}

 

  2、建立自己藍牙設備的數據包結構對象(可選),繼承DataParserAdapter,重寫相應方法。框架內部根據你定義的結構,自動幫你把藍牙迴應的數據包提煉出來(主要是處理斷包、粘包問題),最終的數據包通過onDeviceRespSpliceData(BLEPacket message)方法回調給你。當然你也可以不用架構的處理算法,自己拼包,在DataCircularBuffer 類中,pushOriginalDataToBuffer(byte[] originalData)方法,是各個藍牙設備數據推過來的入口,在這裏接入自己的算法。

  如果不建立DataParserAdapter對象,則默認爲null,藍牙迴應的數據,通過onDevicesRespOriginalData(BLEPacket message) 方法回調給你。

  3、建立自己的藍牙管理對象,繼承BLEBaseManager,重寫必要的、可選的方法。藍牙的各種信息交換,都是通過這個類回調給你。很重要!仔細閱讀BLEServerListener接口裏的方法說明,重寫自己需要的方法。

  (1)必須重寫 onGetDevicesServiceUUID()方法,把自己定義的設備類型ID和設備的service uuid,用map寫進去。框架連接上設備後,讀取設備的service uuid,根據這個map分辨出是那種類型的設備。

  (2)必須重寫BLEAppDevice onCreateDevice(BluetoothDevice bluetoothDevice, int deviceType)方法,框架識別設備類型後,回調給你,你根據設備類型,創建設備對象實例。

  (3)onAddScanDevice(BluetoothDevice bluetoothDevice)方法,框架掃描到設備,就會回調這個方法。

  (4)onAddNewDevice(BLEAppDevice device)方法,框架連接成功一個設備,各種狀態完備後,回調這個方法。

  這些方法在BLEServerListener接口都有詳細說明

public class BLEManager extends BLEBaseManager {

    private final String TAG = "BLEManager";

    private static BLEManager instance = new BLEManager();
    public static BLEManager getInstance() {
        return instance;
    }

    @Override
    public HashMap<Integer, UUID> onGetDevicesServiceUUID() {
        HashMap<Integer, UUID> map = new HashMap();
        map.put(HeaterDevice.DEVICE_TYPE_ID, HeaterDevice.SERVICE_UUID);
        map.put(LedDevice.DEVICE_TYPE_ID, LedDevice.SERVICE_UUID);

        return map;
    }

    @Override
    public void onScanOver() {
        Log.w(TAG, "onScanOve。。");
    }

    @Override
    public BLEAppDevice onCreateDevice(BluetoothDevice bluetoothDevice, int deviceType) {
        if (deviceType == HeaterDevice.DEVICE_TYPE_ID) {
            //數據包解析適配器爲null,藍牙設備迴應的數據在 onDevicesRespOriginalData(BLEPacket message)
            return new HeaterDevice(bluetoothDevice, null);
        }
        else if (deviceType == LedDevice.DEVICE_TYPE_ID) {
            // 設置了數據包解析適配器,數據回調在 onDeviceRespSpliceData(BLEPacket message)
            return new LedDevice(bluetoothDevice, new LedDataAdapter());
        }
        else {
            return null;
        }
    }

    @Override
    public void onAddScanDevice(BluetoothDevice bluetoothDevice){
        EventBus.getDefault().post(new AddScanDeviceEvent(bluetoothDevice));
    }

    @Override
    public void onConnectUnTypeDevice(BluetoothDevice bluetoothDevice, int type) {
        EventBus.getDefault().post(new ConnectUnTypeDeviceEvent(bluetoothDevice, type));
    }

    @Override
    public void onConnectDevice(BLEAppDevice device, int type){
        EventBus.getDefault().post(new ConnectDeviceEvent(device, type));
    }

    @Override
    public void onAddNewDevice(BLEAppDevice device){
        EventBus.getDefault().post(new AddNewDeviceEvent(device));
    }
    @Override
    public void onUpdateDeviceInfo(BLEAppDevice device) {
        EventBus.getDefault().post(new updateDeviceInfoEvent(device));
    }
    @Override
    public void onDeviceSendResult(String result){
        EventBus.getDefault().post(new BleSendResultEvent(result));
    }

    @Override
    public void onDeviceRespSpliceData(BLEPacket message) {
        LogUtil.i(TAG, "onDeviceRespSpliceDat: [" + BytesUtil.BytesToHexStringPrintf(message.bleData) + "] bleId: " + message.bleId);
//        DataManager.getInstance().DecodeRespData(message.bleData, message.bleId);
    }

    @Override
    public void onDevicesRespOriginalData(BLEPacket message) {
        LogUtil.v(TAG, "onDevicesRespOriginalDat: [" + BytesUtil.BytesToHexStringPrintf(message.bleData) + "] bleId: " + message.bleId);
    }


}

 

  建立三個對象,就可以使用此框架了,如此簡單!

  4、初始化藍牙框架,APP獲得藍牙相應權限後,調用BLEBaseManager的 initBle(..)方法初始化藍牙。見demo

 注意

  1、多設備同時工作,必定引起併發競爭問題,自己要做好同步!demo只是使用方法,沒有處理那些問題

  2、此框架藍牙接收特徵配置成通知方式,其他方式自行拓展,工作太忙沒有太多時間去整理,見諒!

 

demo運行起來的效果

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章