1. 程式人生 > >以面向物件的思維,搭建Android與多ble藍芽裝置併發通訊小框架

以面向物件的思維,搭建Android與多ble藍芽裝置併發通訊小框架

  此框架支援多種不同型別的ble裝置,同時連線、收發資料,互不干擾。比如APP同時連兩個LED藍芽燈、兩個手環、一個藍芽加熱器,當然連線單個ble裝置,或者只連線一種ble裝置同樣適用本框架。

前言

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

  Android連線多藍芽裝置、藍芽與多裝置連線、藍芽ble多裝置併發操作、Android連線不了、Android ble開發框架、Android 連線藍芽總結

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

  (連線不可超過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、此框架藍芽接收特徵配置成通知方式,其他方式自行拓展,工作太忙沒有太多時間去整理,見諒!

&n