低功耗藍(lán)牙也就是藍(lán)牙4.0以后的版本登刺,在android手機(jī)是4.3以后才支持卢厂。在項(xiàng)目中,開始使用了傳統(tǒng)藍(lán)牙去進(jìn)行連接河劝,發(fā)現(xiàn)失敗了壁榕,始終沒辦法連接上。后來發(fā)現(xiàn)藍(lán)牙硬件是4.0后的藍(lán)牙版本丧裁,所以下面記錄下android4.3版本后的藍(lán)牙使用护桦。
首先,需要明白一些專業(yè)的術(shù)語煎娇,GATT二庵,characteristics,Properties缓呛,Descriptor催享,Service,UUID哟绊。
GATT
GATT是藍(lán)牙連接后讀寫藍(lán)牙數(shù)據(jù)的通用規(guī)范因妙。它基于ATT通訊協(xié)議(Attribute Protocol),目的是在傳輸信息過程中使用盡量少的數(shù)據(jù)票髓。在傳輸時(shí)攀涵,信息將以特征值(characteristics)和服務(wù)(services)的信息傳輸。這些信息都具有唯一的UUID洽沟。下面分別介紹一些基本概念以故。
characteristics
特征值●刹伲可以理解為一個(gè)數(shù)據(jù)類型怒详。包括一個(gè)值(Value),一個(gè)屬性(Property)和0至多個(gè)對值的描述(Descriptor)。
Properties
定義了characteristic的Value如何被使用踪区,以及characteristic的Descriptor如何被訪問昆烁。
Descriptor
是與Characteristic值相關(guān)的描述,例如范圍缎岗、計(jì)量單位等静尼。
Service
是Characteristic的集合。一個(gè)Service一般包含多個(gè)Characteristic。
UUID
唯一標(biāo)示符鼠渺,每個(gè)Service蜗元,Characteristic,Descriptor系冗,都一個(gè)唯一的UUID。以后在對它們操作時(shí)薪鹦,可以通過它們的UUID查找到對應(yīng)的Service掌敬,Characteristic或者Descriptor
判斷是否支持藍(lán)牙
public boolean checkIfSupportBle() {
return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE);}
開啟藍(lán)牙
這里有兩種方式:
public void enableBluetooth(Activity activity) {
if (bleAdapter == null || !bleAdapter.isEnabled()) {
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
activity.startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
}
}
public void enableBluetooth() {
if (bleAdapter == null || !bleAdapter.isEnabled()) {
bleAdapter.enable();
}
}
掃描廣播包
有兩種方式獲得BluetoothAdapter
第一種通過先獲取BluetoothManager,然后獲得BlueAdapter
private BluetoothManager bleManager;
private BluetoothAdapter bleAdapter;
bleManager = (BluetoothManager) mContext.getSystemService(Context.BLUETOOTH_SERVICE);
bleAdapter = bleManager.getAdapter();
第二種是直接獲取BluetoothAdapter
BluetoothAdapter bleAdapter = BluetoothAdapter.getDefaultAdapter();
獲取藍(lán)牙匹配設(shè)備
public List<BluetoothDevice> getBoundDevices() {
List<BluetoothDevice> devices = new ArrayList<>();
Set<BluetoothDevice> boundDevices = bleAdapter.getBondedDevices();
for (BluetoothDevice device : boundDevices) {
//對device進(jìn)行其他操作池磁,比如連接等奔害。
devices.add(device);
}
return devices;
}
開始掃描
public void startLeScan(BluetoothAdapter.LeScanCallback leScanCallback) {
bleAdapter.startLeScan(leScanCallback);
}
//加入藍(lán)牙過濾
public void startLeScan(UUID[] uuids, BluetoothAdapter.LeScanCallback leScanCallback) {
bleAdapter.startLeScan(uuids, leScanCallback);
}
關(guān)閉藍(lán)牙搜索
public void stopLeScan(BluetoothAdapter.LeScanCallback leScanCallback) {
bleAdapter.stopLeScan(leScanCallback);
}
實(shí)現(xiàn)BluetoothAdapter.LeScanCallback接口
public class MyLeScanCallback implements BluetoothAdapter.LeScanCallback {
private String mac;
private Context mContext;
private LeScanInterface leScanInterface;
public MyLeScanCallback(Context mContext, String mac) {
this.mac = mac;
this.mContext = mContext;
}
public MyLeScanCallback(Context mContext) {
this.mContext = mContext;
}
@Override
public void onLeScan(BluetoothDevice bluetoothDevice, int i, byte[] bytes) {
String bleMac = bluetoothDevice.getAddress();
if (TextUtils.equals(mac, bleMac)) {
BleManager manager = BleManager.getInstance(mContext);
manager.setDevice(bluetoothDevice);
}
if (leScanInterface != null) {
leScanInterface.onMyLeScan(bluetoothDevice, i, bytes);
}
}
public void setOnLeScan(LeScanInterface leScanInterface) {
this.leScanInterface = leScanInterface;
}
/**
* 藍(lán)牙掃描回調(diào)接口
*/
public interface LeScanInterface {
/**
* 藍(lán)牙掃描回調(diào)
* @param bluetoothDevice
* @param i
* @param bytes
*/
void onMyLeScan(BluetoothDevice bluetoothDevice, int i, byte[] bytes);
}
}
Android5.0以后將藍(lán)牙搜索封裝成獨(dú)立的對象,新的寫法
BluetoothLeScanner bluetoothLeScanner = bluetoothAdapter.getBluetoothLeScanner();
bluetoothLeScanner.startScan(mScanCallback);
private ScanCallback scanCallback = new ScanCallback() {
@Override
public void onScanResult(int callbackType, ScanResult results) {
super.onScanResult(callbackType, results);
BluetoothDevice device = results.getDevice();
if (!devices.contains(device)) { //判斷是否已經(jīng)添加
devices.add(device);//也可以添加devices.getName()到列表,這里省略 }
// callbackType:回調(diào)類型
// result:掃描的結(jié)果地熄,不包括傳統(tǒng)藍(lán)牙 }
};
支持不同版本的寫法
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
...
//定義的低于android5.0掃描方法
startScan()
} else {
...
//定義的高于android5.0掃描方法
startLeScan()
}
建立藍(lán)牙連接
/**
* 連接藍(lán)牙設(shè)備
*
* @param auto
* @param mGattCallback
* @return
*/
public BluetoothGatt connectDevice(boolean auto, BluetoothGattCallback mGattCallback) {
bleGatt = device.connectGatt(mContext, auto, mGattCallback);
Log.e(TAG, "生成BluetoothGatt----->" + this.bleGatt);
return bleGatt;
}
獲取device
/**
* 通過mac地址直接得到BluetoothDevice
*
* @param mac
* @return
*/
public BluetoothDevice getRemoteDevice(String mac) {
device = bleAdapter.getRemoteDevice(mac);
return device;
}
繼承BluetoothGattCallback類华临,復(fù)寫相關(guān)的方法
public class BleGattCallback extends BluetoothGattCallback {
private static final String TAG = BleGattCallback.class.getName();
private Context mContext;
private CharacterInterface characterInterface;
private BleStateInterface bleStateInterface;
public BleGattCallback(Context mContext) {
this.mContext = mContext;
}
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
super.onConnectionStateChange(gatt, status, newState);
if (status == BluetoothGatt.GATT_SUCCESS) {
Log.e(TAG, "成功建立藍(lán)牙通道");
if (newState == BluetoothProfile.STATE_CONNECTED) {
//發(fā)現(xiàn)服務(wù)
gatt.discoverServices();
Log.e(TAG, "藍(lán)牙連接成功!");
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
Log.e(TAG, "藍(lán)牙連接斷開!");
}
if (bleStateInterface != null) {
bleStateInterface.onConnectedChange(newState);
}
} else {
Log.e(TAG, "建立藍(lán)牙通道失敗");
if (bleStateInterface != null) {
bleStateInterface.onBleGattStatus(status);
}
}
}
@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
super.onServicesDiscovered(gatt, status);
if (status == BluetoothGatt.GATT_SUCCESS) {
BleManager.getInstance(mContext).enableNotification(gatt, true);
}
}
@Override
public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic
characteristic, int status) {
super.onCharacteristicWrite(gatt, characteristic, status);
BleManager.getInstance(mContext).writeData();
}
@Override
public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic
characteristic) {
super.onCharacteristicChanged(gatt, characteristic);
if (characterInterface != null) {
characterInterface.notifyChanged(gatt, characteristic);
}
}
public void setOnCharacterInterface(CharacterInterface characterInterface) {
this.characterInterface = characterInterface;
}
public void setOnConnectedChange(BleStateInterface bleStateInterface) {
this.bleStateInterface = bleStateInterface;
}
public interface CharacterInterface {
/**
* 特征值改變通知
*
* @param gatt
* @param characteristic
*/
void notifyChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic);
}
public interface BleStateInterface {
/**
* 藍(lán)牙連接狀態(tài),注意回調(diào)方法不在主線程
*
* @param newState
*/
void onConnectedChange(int newState);
/**
* 藍(lán)牙通道建立狀態(tài)回調(diào)端考,失敗的話需要關(guān)閉BlueToothGatt和BlueToothService,然后重新調(diào)用藍(lán)牙重連
*
* @param status
*/
void onBleGattStatus(int status);
}
}
封裝成完整的類
public class BleManager {
private BluetoothAdapter bleAdapter;
private BluetoothManager bleManager;
private BluetoothGatt bleGatt;
private BluetoothGattService gattService;
private BluetoothDevice device;
private Context mContext;
private static volatile BleManager singleton;
private final int REQUEST_ENABLE_BT = 1;
private StringBuffer buffer = new StringBuffer();
private String TAG = getClass().getName();
private BleManager(Context mContext) {
this.mContext = mContext;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR2) {
bleManager = (BluetoothManager) mContext.getSystemService(Context.BLUETOOTH_SERVICE);
bleAdapter = bleManager.getAdapter();
} else {
bleAdapter = BluetoothAdapter.getDefaultAdapter();
}
}
/**
* 獲取藍(lán)牙管理類的單例
*
* @param context
* @return
*/
public static BleManager getInstance(Context context) {
if (singleton == null) {
synchronized (BleManager.class) {
if (singleton == null) {
singleton = new BleManager(context);
}
}
}
return singleton;
}
/**
* 判斷是否支持藍(lán)牙設(shè)備
*
* @return
*/
public boolean checkIfSupportBle() {
return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE);
}
/**
* 開啟藍(lán)牙設(shè)備
*/
public void enableBluetooth(Activity activity) {
if (bleAdapter == null || !bleAdapter.isEnabled()) {
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
activity.startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
}
}
public void enableBluetooth() {
if (bleAdapter == null || !bleAdapter.isEnabled()) {
bleAdapter.enable();
}
}
/**
* 獲取藍(lán)牙綁定了的列表設(shè)備
*/
public List<BluetoothDevice> getBoundDevices() {
List<BluetoothDevice> devices = new ArrayList<>();
Set<BluetoothDevice> boundDevices = bleAdapter.getBondedDevices();
for (BluetoothDevice device : boundDevices) {
//對device進(jìn)行其他操作雅潭,比如連接等。
devices.add(device);
}
return devices;
}
/**
* 通用藍(lán)牙掃描方法却特,持續(xù)時(shí)間大概10s扶供,掃描到藍(lán)牙發(fā)出廣播進(jìn)行接收
*/
public void startDiscover() {
bleAdapter.startDiscovery();
}
/**
* 該方法目前穩(wěn)定
*
* @param leScanCallback
*/
public void startLeScan(BluetoothAdapter.LeScanCallback leScanCallback) {
bleAdapter.startLeScan(leScanCallback);
}
/**
* 通過UUID過濾篩選出合適的藍(lán)牙設(shè)備
*
* @param uuids
* @param leScanCallback
*/
public void startLeScan(UUID[] uuids, BluetoothAdapter.LeScanCallback leScanCallback) {
bleAdapter.startLeScan(uuids, leScanCallback);
}
/**
* 代替果實(shí)的startLeScan(),但是不穩(wěn)定
*
* @param scanCallback
*/
@RequiresApi (api = Build.VERSION_CODES.LOLLIPOP)
public void startBleScan(ScanCallback scanCallback) {
bleAdapter.getBluetoothLeScanner().startScan(scanCallback);
}
/**
* 關(guān)閉藍(lán)牙搜索
*
* @param leScanCallback
*/
public void stopLeScan(BluetoothAdapter.LeScanCallback leScanCallback) {
bleAdapter.stopLeScan(leScanCallback);
}
/**
* 藍(lán)牙是否開啟
*
* @return
*/
public boolean isOpen() {
return bleAdapter.isEnabled();
}
/**
* 關(guān)閉藍(lán)牙設(shè)備
*
* @param scanCallback
*/
@RequiresApi (api = Build.VERSION_CODES.LOLLIPOP)
public void stopBleScan(ScanCallback scanCallback) {
bleAdapter.getBluetoothLeScanner().stopScan(scanCallback);
}
public void setBleGatt(BluetoothGatt bleGatt) {
this.bleGatt = bleGatt;
}
public void setGattService(BluetoothGattService gattService) {
this.gattService = gattService;
}
public BluetoothGatt getBleGatt() {
return bleGatt;
}
public BluetoothGattService getGattService() {
return gattService;
}
public BluetoothDevice getDevice() {
return device;
}
public void setDevice(BluetoothDevice device) {
this.device = device;
}
/**
* 開啟藍(lán)牙設(shè)備
*/
public void openBle() {
bleAdapter.enable();
}
/**
* 關(guān)閉藍(lán)牙設(shè)備
*/
public void closeBle() {
bleAdapter.disable();
}
/**
* 通過mac地址直接得到BluetoothDevice
*
* @param mac
* @return
*/
public BluetoothDevice getRemoteDevice(String mac) {
device = bleAdapter.getRemoteDevice(mac);
return device;
}
/**
* 連接藍(lán)牙設(shè)備
*
* @param auto
* @param mGattCallback
* @return
*/
public BluetoothGatt connectDevice(boolean auto, BluetoothGattCallback mGattCallback) {
bleGatt = device.connectGatt(mContext, auto, mGattCallback);
Log.e(TAG, "生成BluetoothGatt----->" + this.bleGatt);
return bleGatt;
}
/**
* 獲取對應(yīng)應(yīng)用的服務(wù)
* @return
*/
public BluetoothGattService getDefaultGattService() {
this.gattService = bleGatt.getService(UUID.fromString(UUIDManager.SERVICE_UUID));
return gattService;
}
@SuppressLint ("NewApi")
public boolean enableNotification(BluetoothGatt bluetoothGatt, boolean enable) {
if (gattService == null) {
getDefaultGattService();
}
BluetoothGattCharacteristic characteristic = gattService.getCharacteristic(UUID.fromString(UUIDManager.NOTIFY_UUID));
if (bluetoothGatt == null || characteristic == null) {
return false;
}
if (!bluetoothGatt.setCharacteristicNotification(characteristic, enable)) {
return false;
}
//獲取到Notify當(dāng)中的Descriptor通道 然后再進(jìn)行注冊
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);
}
/**
* 藍(lán)牙設(shè)備傳數(shù)據(jù)
*
* @param data
* @return
*/
private boolean writeBluetoothData(String data) {
BluetoothGattCharacteristic writeCharacter = null;
if (gattService == null) {
gattService = getDefaultGattService();
if (gattService == null) {
return false;
}
}
writeCharacter = gattService.getCharacteristic(UUID.fromString(UUIDManager.WRITE_UUID));
// 設(shè)置監(jiān)聽
this.bleGatt.setCharacteristicNotification(writeCharacter, true);
// 當(dāng)數(shù)據(jù)傳遞到藍(lán)牙之后
// 會(huì)回調(diào)BluetoothGattCallback里面的write方法
writeCharacter.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT);
// 將需要傳遞的數(shù)據(jù) 打碎成16進(jìn)制
try {
writeCharacter.setValue(CommonUtils.getHexBytes(data));
} catch (Exception e) {
e.printStackTrace();
}
return this.bleGatt.writeCharacteristic(writeCharacter);
}
/**
* 向藍(lán)牙設(shè)備寫入數(shù)據(jù)
*/
public void writeData(String content) {
buffer.append(content);
writeData();
}
public void writeData() {
int length = buffer.length();
String writeData = "";
if (length >= 20) {
writeData = buffer.substring(0, 20);
} else {
writeData = buffer.toString();
}
if (writeData.length() > 0) {
writeBluetoothData(writeData);
}
if (writeData.length() > 0) {
buffer.delete(0, writeData.length());
}
}
/**
* 關(guān)閉藍(lán)牙連接
*/
public void closeGatt() {
if (bleGatt != null) {
bleGatt.disconnect();
bleGatt.close();
Log.e(TAG, "連接藍(lán)牙斷開bleGatt" + this.bleGatt);
Log.e(TAG, "連接藍(lán)牙斷開gattService" + this.gattService);
bleGatt = null;
gattService = null;
}
}
}
UUID的獲取
一般而言UUID會(huì)由硬件工程師提供的,但是有時(shí)候硬件工程師也不知道裂明,這時(shí)就需要自己去檢測了椿浓。首先通過尋找服務(wù)Service的UUID,然后通過找出特征值的UUID闽晦,并且判斷特征值的UUID的屬性是讀扳碍,寫還是通知。也可以可以借助引用nRF Connect連接藍(lán)牙得到相應(yīng)的值仙蛉。
/**
* 展示服務(wù)Services和characteristic對應(yīng)的UUID晋修,以及具備的屬性恩沛。
*
* @param gattServices
*/
public boolean queryGattServices(List<BluetoothGattService> gattServices) {
if (gattServices == null) {
return false;
}
for (BluetoothGattService myService : gattServices) {
//找出服務(wù)的UUID
String uuId = myService.getUuid().toString();
List<BluetoothGattCharacteristic> gattCharacteristics =myService.getCharacteristics();
for (final BluetoothGattCharacteristic gattCharacteristic: gattCharacteristics) {
Log.e(TAG,"---->char uuid:"+gattCharacteristic.getUuid());
int charaProp = gattCharacteristic.getProperties();
//所有Characteristics按屬性分類
if ((charaProp | BluetoothGattCharacteristic.PROPERTY_READ) > 0) {
Log.d(TAG, "gattCharacteristic的UUID為:" + gattCharacteristic.getUuid());
Log.d(TAG, "gattCharacteristic的屬性為: 可讀");
}
if ((charaProp | BluetoothGattCharacteristic.PROPERTY_WRITE) > 0) {
Log.d(TAG, "gattCharacteristic的UUID為:" + gattCharacteristic.getUuid());
Log.d(TAG, "gattCharacteristic的屬性為: 可寫");
}
if ((charaProp | BluetoothGattCharacteristic.PROPERTY_NOTIFY) > 0) {
Log.d(TAG, "gattCharacteristic的UUID為:" + gattCharacteristic.getUuid() + gattCharacteristic);
Log.d(TAG, "gattCharacteristic的屬性為: 具備通知屬性");
}
}
return false;
}
}
這樣就能找到對應(yīng)服務(wù)的UUID和特征值characteristic的UUID,從而進(jìn)行相應(yīng)的讀寫和通知的操作。讀寫通知有可能公用一個(gè)通道苞慢,即特征值UUID有可能相同,但是這不影響通信穗泵。對于外部藍(lán)牙硬件瓮孙,一般通過通知的方式,接收返回的數(shù)據(jù)麸祷,開啟藍(lán)牙通知的方式為BleManager的enableNotification()方法澎怒。
補(bǔ)充藍(lán)牙讀的寫法:
bleGatt.readCharacteristic(characteristic);
如果要使用,放在BluetoothGattCallback復(fù)寫方法onCharacteristicRead()方法下進(jìn)行邏輯操作。