以面向物件的思維,搭建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