Android藍牙4.0 Ble讀寫數據詳解 -2
上一篇說了如何掃描與鏈接藍牙 這篇文章講講與藍牙的數據傳輸,與一些踩到的坑。
先介紹一款調試工具,專門調試Ble藍牙的app。名字叫:nRF-Connect 谷歌應用商店也能下載到。
這里我先連接一個藍牙設備 貼幾個截圖。
UUID的話 就相當于鑰匙,藍牙設備當中有通道,那么通道是需要UUID進行匹配的
當連接上設備之后,可以看到UUID的通道 接下來,按照設備廠商提供的文檔,找到我們需要的UUID通道
比如說我這里需要的是0x6a的Service通道 然后點開最后一個Service通道查看
展開Service后 可以看到有兩個Characteristic通道
我們看Properties屬性 一個是NOTIFY 一個是WRITE 也有可能會有READ這個屬性的通道
可以拿這個app輸出寫出指令給藍牙,在不清楚是藍牙的問題還是自己的問題的時候,這個工具還是挺好使的。
Notify的話,需要注意這個Descriptors的UUID 這個在注冊Notify的時候,需要用到,這里雖然看不全,但是之后可以通過打印得到。

簡單說一下這三種屬性的通道的用途
WRITE:顧名思義,寫的意思,該通道是用來向藍牙設備寫出數據的通道
READ:向藍牙設備進行讀取數據的通道 這個通道有一個坑 后續會詳細寫上
Notify:該通道需要注冊監聽,這是一個通知的通道,藍牙向你傳輸數據的話,就能直接收到監聽。
我這邊的話 因為一些原因,所以沒有使用READ通道獲取數據 只用了Notify通道 當然 也會講講怎么使用READ
準備工作
先將UUID管理起來,我這里的話 采用靜態常量的形式保存起來了。
public class UUIDManager {
/**
* 服務的UUID
*/
public static final String SERVICE_UUID = "00006a00-0000-1000-8000-00805f9b34fb";
/**
* 訂閱通知的UUID
*/
public static final String NOTIFY_UUID = "00006a02-0000-1000-8000-00805f9b34fb";
/**
* 寫出數據的UUID
*/
public static final String WRITE_UUID = "00006a02-0000-1000-8000-00805f9b34fb";
/**
* NOTIFY里面的Descriptor UUID
*/
public static final String NOTIFY_DESCRIPTOR = "00002902-0000-1000-8000-00805f9b34fb";
}
處理通知回調接口
藍牙的數據回調,其實并不是回調到主線程當中,所以如果接收到數據之后,就進行視圖操作的話,是會失敗的。
所以我打算切換到主線程進行回調,當然,也可以使用EventBus,不過我不喜歡這東西就沒去用。
回調接口的話,打算使用list集合存儲起來,然后回調到各個需要數據的地方。 創建以下三個類
/**
* Created by Pencilso on 2017/4/20.
* 藍牙數據回調監聽接口
*/
public interface BlueNotifyListener {
public void onNotify(Message notify);
}
/**
* Created by Pencilso on 2017/4/25.
* 處理回調所有接口
*/
public class NotifyThread implements Runnable {
private List<BlueNotifyListener> listeners;
private Message notify;
@Override
public void run() {
if (notify == null || listeners==null)
return;
try {
Iterator<BlueNotifyListener> iterator = listeners.iterator();
while (iterator.hasNext()) {
BlueNotifyListener next = iterator.next();
if (next == null)
iterator.remove();
else
next.onNotify(notify);
}
} catch (Exception e) {
e.printStackTrace();
}
}
public void setListeners(List<BlueNotifyListener> listeners) {
this.listeners = listeners;
}
public void setNotify(Message notify) {
this.notify = notify;
}
}
/**
* Created by Pencilso on 2017/4/26.
* 藍牙的Code類 用來自定義回調的標識
*/
public class BlueCodeUtils {
/**
* 藍牙狀態 已連接
*/
public static final int BLUETOOTH_STATE_CONNECT = 0x1;
/**
* 藍牙狀態 已斷開
*/
public static final int BLUETOOTH_STATE_DISCONNECT = 0x2;
//*******這些只是自定義的code 就不復制太多了
}
編寫藍牙的功能
新建BluetoothBinder類 繼承自BluetoothGattCallback
然后把藍牙的功能模塊寫在這里面。
主要的功能都在這個類里面,我把這個放到了服務里面,所以在創建BluetoothBinder的時候需要傳遞一個Service對象
import android.annotation.SuppressLint;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattDescriptor;
import android.bluetooth.BluetoothGattService;
import android.bluetooth.BluetoothManager;
import android.bluetooth.BluetoothProfile;
import android.content.Context;
import android.os.Build;
import android.os.Message;
import android.support.annotation.RequiresApi;
import android.util.Log;
import java.util.List;
import java.util.UUID;
import cc.petnet.trenmotion.Interface.IBluetoothInterface;
import cc.petnet.trenmotion.utils.HandleUtils;
import cc.petnet.trenmotion.utils.LogUtils;
/**
* Created by Pencilso on 2017/4/20.
* 藍牙操作的Binder
*/
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2)
public class BluetoothBinder extends BluetoothGattCallback implements IBluetoothInterface {
private static BluetoothBinder bluetoothBinder;
private final Service bluetoothService;//service服務
private final BluetoothAdapter adapter;//藍牙的適配器
private List<BlueNotifyListener> notifyList;//監聽的集合
private BluetoothManager bluetoothManager;//藍牙管理者
private BluetoothGattService gattService;//通道服務
private BluetoothGatt bluetoothGatt;
public static IBluetoothInterface getInstace() {
return bluetoothBinder;
}
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2)
protected BluetoothBinder(BluetoothService bluetoothService) {
bluetoothBinder = this;
this.bluetoothService = bluetoothService;
bluetoothManager = (BluetoothManager) bluetoothService.getSystemService(Context.BLUETOOTH_SERVICE);
adapter = bluetoothManager.getAdapter();
}
@Override
public void onDestroy() {
bluetoothBinder = null;
}
@Override
public <T extends BlueNotifyListener> void addNotifyListener(T notifyListener) {
if (notifyListener != null)
notifyList.add(notifyListener);
}
@Override
public void removeNotifyListener(BlueNotifyListener notifyListener) {
if (notifyListener != null)
notifyList.remove(notifyListener);
}
/**
* 廣播藍牙監聽消息
* 因為藍牙發送過來的消息 并不是處于主線程當中的
* 所以如果直接對藍牙的數據展示視圖的話 會展示不了的 這里的話 封裝到主線程當中遍歷回調數據
*
* @param notify
*/
public void traverseListener(Message notify) {
NotifyThread notifyThread = new NotifyThread();//創建一個遍歷線程
notifyThread.setListeners(notifyList);
notifyThread.setNotify(notify);
HandleUtils.getInstace().post(notifyThread);
}
/**
* 系統的藍牙是否已經打開
*
* @return
*/
@Override
public boolean isEnable() {
return adapter.isEnabled();
}
@Override
public void enableBluetooth(boolean enable) {
if (enable)
adapter.enable();
else
adapter.disable();
}
@SuppressWarnings("deprecation")
@SuppressLint("NewApi")
@Override
public void startScanner(BluetoothAdapter.LeScanCallback callback) {
adapter.startLeScan(callback);
}
@SuppressLint("NewApi")
@SuppressWarnings("deprecation")
@Override
public void stopScanner(BluetoothAdapter.LeScanCallback callback) {
adapter.stopLeScan(callback);
}
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2)
@Override
public void connectDevices(String address) {
BluetoothDevice remoteDevice = adapter.getRemoteDevice(address);
BluetoothGatt bluetoothGatt = remoteDevice.connectGatt(bluetoothService, false, this);
}
/**
* 藍牙設備狀態的監聽
*
* @param gatt
* @param status
* @param newState 藍牙的狀態被改變
*/
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
super.onConnectionStateChange(gatt, status, newState);
Message message = new Message();
switch (newState) {//對藍牙反饋的狀態進行判斷
case BluetoothProfile.STATE_CONNECTED://已鏈接
message.what = BlueCodeUtils.BLUETOOTH_STATE_CONNECT;
gatt.discoverServices();
break;
case BluetoothProfile.STATE_DISCONNECTED://已斷開
message.what = BlueCodeUtils.BLUETOOTH_STATE_DISCONNECT;
break;
}
traverseListener(message);
/**
* 這里還有一個需要注意的,比如說藍牙設備上有一些通道是一些參數之類的信息,比如最常見的版本號。
* 一般這種情況 版本號都是定死在某一個通道上,直接讀取,也不需要發送指令的。
* 如果遇到這種情況,一定要在發現服務之后 再去讀取這種數據 不要一連接成功就去獲取
*/
}
/**
* 發現服務
*
* @param gatt
* @param status
*/
@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
super.onServicesDiscovered(gatt, status);
gattService = gatt.getService(UUID.fromString(UUIDManager.SERVICE_UUID));// 獲取到服務的通道
bluetoothGatt = gatt;
//獲取到Notify的Characteristic通道 這個根據協議來定 如果設備廠家給的協議不是Notify的話 就不用做以下操作了
BluetoothGattCharacteristic notifyCharacteristic = gattService.getCharacteristic(UUID.fromString(UUIDManager.NOTIFY_UUID));
BluetoothUtils.enableNotification(gatt, true, notifyCharacteristic);//注冊Notify通知
}
/**
* 向藍牙寫入數據
*
* @param data
*/
@SuppressLint("NewApi")
@Override
public boolean writeBuletoothData(String data) {
if (bluetoothService == null) {
return false;
}
BluetoothGattCharacteristic writeCharact = gattService.getCharacteristic(UUID.fromString(UUIDManager.WRITE_UUID));
bluetoothGatt.setCharacteristicNotification(writeCharact, true); // 設置監聽
// 當數據傳遞到藍牙之后
// 會回調BluetoothGattCallback里面的write方法
writeCharact.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT);
// 將需要傳遞的數據 打碎成16進制
writeCharact.setValue(BluetoothUtils.getHexBytes(data));
return bluetoothGatt.writeCharacteristic(writeCharact);
}
/**
* 藍牙Notify推送過來的數據
*
* @param gatt
* @param characteristic
*/
@Override
public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
super.onCharacteristicChanged(gatt, characteristic);
//如果推送的是十六進制的數據的寫法
String data = BluetoothUtils.bytesToHexString(characteristic.getValue()); // 將字節轉化為String字符串
Message message = new Message();
message.what = BlueCodeUtils.BLUETOOTH_PUSH_MESSAGE;
message.obj = data;
traverseListener(message);//回調數據
// String data = characteristic.getStringValue(0); // 使用場景 例如某個通道里面靜態的定死了某一個值,就用這種寫法獲取 直接獲取到String類型的數據
}
/**
* 這里有一個坑 一定要注意,如果設備返回數據用的不是Notify的話 一定要注意這個問題
* 這個方法是 向藍牙設備寫出數據成功之后回調的方法,寫出成功之后干嘛呢? 主動去藍牙獲取數據,沒錯,自己主動去READ通道獲取藍牙數據
* 如果用的是Notify的話 不用理會該方法 寫出到藍牙之后 等待Notify的監聽 即onCharacteristicChanged方法回調。
*
* @param gatt
* @param characteristic
* @param status
*/
@Override
public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
super.onCharacteristicWrite(gatt, characteristic, status);
if (status == BluetoothGatt.GATT_SUCCESS) { //寫出成功 接下來 該去讀取藍牙設備的數據了
//這里的READUUID 應該是READ通道的UUID 不過我這邊的設備沒有用到READ通道 所以我這里就注釋了 具體使用 視情況而定
// BluetoothGattCharacteristic readCharact = gattService.getCharacteristic(UUID.fromString(READUUID));
// gatt.readCharacteristic(readCharact);
}
}
/**
* 調用讀取READ通道后返回的數據回調
* 比如說 在onCharacteristicWrite里面調用 gatt.readCharacteristic(readCharact);之后 會回調該方法
*
* @param gatt
* @param characteristic
* @param status
*/
@Override
public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
super.onCharacteristicRead(gatt, characteristic, status);
//如果推送的是十六進制的數據的寫法
String data = BluetoothUtils.bytesToHexString(characteristic.getValue()); // 將字節轉化為String字符串
Message message = new Message();
message.what = BlueCodeUtils.BLUETOOTH_PUSH_MESSAGE;
message.obj = data;
traverseListener(message);//回調數據
// String data = characteristic.getStringValue(0); // 使用場景 例如某個通道里面靜態的定死了某一個值,就用這種寫法獲取 直接獲取到String類型的數據
}
}
其實這個BluetoothBinder還定義了一個接口IBluetoothInterface,然后讓BluetoothBinder實現,并且將BluetoothBinder設置單利,暴露方法,提供返回IBluetoothInterface對象
import android.bluetooth.BluetoothAdapter;
import cc.petnet.trenmotion.service.bluetooth.BlueNotifyListener;
/**
* Created by Pencilso on 2017/4/20.
*/
public interface IBluetoothInterface {
/**
*
* @param notifyListener 添加監聽事件
* @param <T> BlueNotifyListener監聽回調接口
*/
<T extends BlueNotifyListener> void addNotifyListener(T notifyListener);
/**
*
* @param notifyListener 刪除監聽接口
*/
void removeNotifyListener(BlueNotifyListener notifyListener);
/**
* 系統是否開啟了藍牙
*
* @return
*/
boolean isEnable();
/**
* 打開或者關閉系統藍牙
*
* @param enable
*/
void enableBluetooth(boolean enable);
/**
* 啟動掃描
*
* @param callback
*/
void startScanner(BluetoothAdapter.LeScanCallback callback);
/**
* 停止掃描
*
* @param callback
*/
void stopScanner(BluetoothAdapter.LeScanCallback callback);
void onDestroy();
/**
* 連接設備
*
* @param address
*/
void connectDevices(String address);
/**
* 向藍牙寫出數據
* @param data
* @return
*/
public boolean writeBuletoothData(String data);
}
Handler工具 主要用來切換到主線程當中
public class HandleUtils {
private static Handler handler = null;
public static Handler getInstace() {
if (handler == null)
handler = new Handler();
return handler;
}
}
BluetoothUtils
public class BluetoothUtils {
/**
* 是否開啟藍牙的通知
*
* @param enable
* @param characteristic
* @return
*/
@SuppressLint("NewApi")
public static boolean enableNotification(BluetoothGatt bluetoothGatt, boolean enable, BluetoothGattCharacteristic characteristic) {
if (bluetoothGatt == null || characteristic == null) {
return false;
}
if (!bluetoothGatt.setCharacteristicNotification(characteristic, enable)) {
return false;
}
//獲取到Notify當中的Descriptor通道 然后再進行注冊
BluetoothGattDescriptor clientConfig = characteristic.getDescriptor(UUID.fromString(UUIDManager.NOTIFY_DESCRIPTOR));
if (clientConfig == null) {
return false;
}
if (enable) {
clientConfig.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
} else {
clientConfig.setValue(BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE);
}
return bluetoothGatt.writeDescriptor(clientConfig);
}
/**
* 將字節 轉換為字符串
*
* @param src 需要轉換的字節數組
* @return 返回轉換完之后的數據
*/
public static String bytesToHexString(byte[] src) {
StringBuilder stringBuilder = new StringBuilder("");
if (src == null || src.length <= 0) {
return null;
}
for (int i = 0; i < src.length; i++) {
int v = src[i] & 0xFF;
String hv = Integer.toHexString(v);
if (hv.length() < 2) {
stringBuilder.append(0);
}
stringBuilder.append(hv);
}
return stringBuilder.toString();
}
/**
* 將字符串轉化為16進制的字節
*
* @param message
* 需要被轉換的字符
* @return
*/
public static byte[] getHexBytes(String message) {
int len = message.length() / 2;
char[] chars = message.toCharArray();
String[] hexStr = new String[len];
byte[] bytes = new byte[len];
for (int i = 0, j = 0; j < len; i += 2, j++) {
hexStr[j] = "" + chars[i] + chars[i + 1];
bytes[j] = (byte) Integer.parseInt(hexStr[j], 16);
}
return bytes;
}
}
藍牙基本上到這已經結束了。