什么是BLE(低功耗藍牙)
BLE(Bluetooth Low Energy蟹但,低功耗藍牙)是對傳統(tǒng)藍牙BR/EDR技術的補充略板。
盡管BLE和傳統(tǒng)藍牙都稱之為藍牙標準亭螟,且共享射頻攀例,但是嘉汰,BLE是一個完全不一樣的技術。
BLE不具備和傳統(tǒng)藍牙BR/EDR的兼容性摩疑。它是專為小數(shù)據(jù)率、離散傳輸?shù)膽枚O計的畏铆。
通信距離上也有改變雷袋,傳統(tǒng)藍牙的傳輸距離幾十米到幾百米不等,BLE則規(guī)定為100米辞居。
概述
在Android4.3(API等級18)平臺上開始支持低功耗藍牙中央設備角色楷怒,而且提供可供應用去發(fā)現(xiàn)服務、查詢服務和讀寫特性的相關API接口瓦灶。與傳統(tǒng)藍牙相比鸠删,低功耗藍牙的設計對電量消耗更低,這允許Android應用與其他的低功耗設備通信時對電量的需求更低贼陶,如距離傳感器刃泡、心率監(jiān)視器和醫(yī)療健康設備等等巧娱。
一、聲明BLE權限
為了在你的應用中使用藍牙功能畏陕,你必須聲明藍牙權限“android.permission.BLUETOOTH”窒百。你需要使用這個權限如執(zhí)行所有的藍牙通信早像,如請求連接,接受連接和傳輸數(shù)據(jù)老翘。
如果想要你的應用去初始化設備發(fā)現(xiàn)或者操縱藍牙設置,你還必須聲明“android.permission.BLUETOOTH_ADMIN”權限锻离。
在應用的AndroidManifest.xml文件中聲明藍牙權限铺峭。如:
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
如果想要聲明你的應用僅對低功耗藍牙是有效的,在app的manifest中還應包含下面這句:
<uses-feature android:name="android.hardware.bluetooth_le"
android:required="true" />
二汽纠、設置BLE
獲取 BluetoothAdapter
所有的藍牙活動都需要藍牙適配器卫键。BluetoothAdapter代表設備本身的藍牙適配器(藍牙無線)。整個系統(tǒng)只有一個藍牙適配器疏虫,而且你的app使用它與系統(tǒng)交互永罚。
// Initializes Bluetooth adapter.
final BluetoothManager bluetoothManager =
(BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
mBluetoothAdapter = bluetoothManager.getAdapter();
}
開啟藍牙
調(diào)用isEnabled())去檢測藍牙當前是否開啟。如果該方法返回false,藍牙被禁用卧秘。下面的代碼檢查藍牙是否開啟呢袱,如果沒有開啟,將顯示錯誤提示用戶去設置開啟藍牙
// Ensures Bluetooth is available on the device and it is enabled. If not,
// displays a dialog requesting user permission to enable Bluetooth.
if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) {
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
}
搜索藍牙設備
為了發(fā)現(xiàn)BLE設備翅敌,使用startLeScan())方法羞福。這個方法需要一個參數(shù)BluetoothAdapter.LeScanCallback。你必須實現(xiàn)它的回調(diào)函數(shù)蚯涮,那就是返回的掃描結(jié)果治专。因為掃描非常消耗電量,你應當遵守以下準則:
只要找到所需的設備遭顶,停止掃描张峰。
不要在循環(huán)里掃描,并且對掃描設置時間限制棒旗。以前可用的設備可能已經(jīng)移出范圍喘批,繼續(xù)掃描消耗電池電量。
boolean mScanning = false;
int SCAN_PERIOD = 1000;
/**
* 定時掃描
*
* @param enable
*/
private void scanLeDevice(final boolean enable) {
if (enable) {
// Stops scanning after a pre-defined scan period.
// 預先定義停止藍牙掃描的時間(因為藍牙掃描需要消耗較多的電量)
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
mScanning = false;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
mBluetoothAdapter.stopLeScan(callback);
}
}
}, SCAN_PERIOD);
mScanning = true;
// 定義一個回調(diào)接口供掃描結(jié)束處理
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
mBluetoothAdapter.startLeScan(callback);
}
} else {
mScanning = false;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
mBluetoothAdapter.stopLeScan(callback);
}
}
}
掃描回調(diào)--掃描到可用設備
List<BluetoothDevice> mBluetoothDeviceList = new ArrayList<>();
final BluetoothAdapter.LeScanCallback callback = new BluetoothAdapter.LeScanCallback() {
@Override
public void onLeScan(final BluetoothDevice device, int rssi, byte[] scanRecord) {
if (device.getName() != null) {
mBluetoothDeviceList.add(device);
mMyBlueAdapter.notifyDataSetChanged();
}
Log.e(TAG, "run: scanning..." + device.getName() + "," + device.getAddress());
}
};
GATT連接
搜索結(jié)束后铣揉,我們可得到一個搜索結(jié)果 BluetoothDevice 饶深,它表示搜到的藍牙設備
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
mBluetoothAdapter.stopLeScan(callback);
mBluetoothGatt = connectGatt.connectGatt(MainActivity.this, true, mBluetoothGattCallback);
}
- 可以建立一個GATT連接,它需要一個 回調(diào)mBluetoothGattCallback參數(shù)逛拱。
- 在回調(diào)方法的 onConnectionStateChange 中敌厘,我們可以通過 status 判斷是否GATT連接成功
- 在GATT連接建立成功后,我們調(diào)用 mBluetoothGatt.discoverServices() 方法 發(fā)現(xiàn)GATT服務朽合。
如果搜到服務將會觸發(fā)onServicesDiscovered回調(diào)
public BluetoothGatt mBluetoothGatt;
// 狀態(tài)改變
BluetoothGattCallback mBluetoothGattCallback = new BluetoothGattCallback() {
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
super.onConnectionStateChange(gatt, status, newState);
Log.e(TAG, "onConnectionStateChange: thread "
+ Thread.currentThread() + " status " + newState);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
if (status != BluetoothGatt.GATT_SUCCESS) {
String err = "Cannot connect device with error status: " + status;
// 當嘗試連接失敗的時候調(diào)用 disconnect 方法是不會引起這個方法回調(diào)的俱两,所以這里
// 直接回調(diào)就可以了饱狂。
gatt.close();
Log.e(TAG, err);
return;
}
if (newState == BluetoothProfile.STATE_CONNECTED) {
Log.e(TAG, "Attempting to start service discovery:" +
mBluetoothGatt.discoverServices());
Log.e(TAG, "connect--->success" + newState + "," + gatt.getServices().size());
setState(ConnectionState.STATE_CONNECTING);
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
Log.e(TAG, "Disconnected from GATT server.");
Log.e(TAG, "connect--->failed" + newState);
setState(ConnectionState.STATE_NONE);
}
}
}
@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
if (status == BluetoothGatt.GATT_SUCCESS) {
Log.e(TAG, "onServicesDiscovered received: SUCCESS");
setState(ConnectionState.STATE_CONNECTED);
initCharacteristic();
try {
Thread.sleep(200);//延遲發(fā)送,否則第一次消息會不成功
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
Log.e(TAG, "onServicesDiscovered error falure " + status);
setState(ConnectionState.STATE_NONE);
}
}
@Override
public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
super.onCharacteristicWrite(gatt, characteristic, status);
Log.e(TAG, "onCharacteristicWrite status: " + status);
}
@Override
public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
super.onDescriptorWrite(gatt, descriptor, status);
Log.e(TAG, "onDescriptorWrite status: " + status);
}
@Override
public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
super.onDescriptorRead(gatt, descriptor, status);
Log.e(TAG, "onDescriptorRead status: " + status);
}
@Override
public void onCharacteristicRead(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic,
int status) {
Log.e(TAG, "onCharacteristicRead status: " + status);
}
@Override
public void onCharacteristicChanged(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic) {
Log.e(TAG, "onCharacteristicChanged characteristic: " + characteristic);
readCharacteristic(characteristic);
}
};
發(fā)現(xiàn)服務 (觸發(fā)onServicesDiscovered)
在發(fā)現(xiàn)服務后锋华,會觸發(fā) GATT回調(diào)的onServicesDiscovered 方法嗡官,我們需要在這里初始化我們的操作,包括:
- 查看服務毯焕⊙苄龋或者便利查找指定的(和目標硬件UUID符合的)服務。
- 獲得指定服務的特征 characteristicRead 纳猫、characteristicWrite
- 訂閱“特征”發(fā)生變化的通知”
public synchronized void initCharacteristic() {
if (mBluetoothGatt == null)
throw new NullPointerException();
List<BluetoothGattService> services = mBluetoothGatt.getServices();
Log.e(TAG, services.toString());
BluetoothGattService service = mBluetoothGatt.getService(uuidServer);
characteristicRead = service.getCharacteristic(uuidCharRead);
characteristicWrite = service.getCharacteristic(uuidCharWrite);
if (characteristicRead == null)
throw new NullPointerException();
if (characteristicWrite == null)
throw new NullPointerException();
mBluetoothGatt.setCharacteristicNotification(characteristicRead, true);
BluetoothGattDescriptor descriptor = characteristicRead.getDescriptor(uuidDescriptor);
if (descriptor == null)
throw new NullPointerException();
descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
mBluetoothGatt.writeDescriptor(descriptor);
}
訂閱“特征”發(fā)生變化的通知”
調(diào)用 mBluetoothGatt.setCharacteristicNotification() 方法婆咸,傳入一個特征 characteristic 對象。
當這個特征里的數(shù)據(jù)發(fā)生變化(接收到數(shù)據(jù)了)芜辕,會觸發(fā) 回調(diào)方法的 onCharacteristicChanged 方法尚骄。我們在這個回調(diào)方法中讀取數(shù)據(jù)。
mBluetoothGatt.setCharacteristicNotification(characteristicRead, true);
BluetoothGattDescriptor descriptor = characteristicRead.getDescriptor(uuidDescriptor);
if (descriptor == null)
throw new NullPointerException();
descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
mBluetoothGatt.writeDescriptor(descriptor);
讀取數(shù)據(jù)
GATT的回調(diào)中有 onCharacteristicChanged 方法侵续,我們在這里可以獲得接收的數(shù)據(jù)
調(diào)用 characteristic.getValue() 方法倔丈,獲得字節(jié)
public void readCharacteristic(BluetoothGattCharacteristic characteristic) {
if (mBluetoothAdapter == null || mBluetoothGatt == null) {
Log.e(TAG, "BluetoothAdapter not initialized");
return;
}
mBluetoothGatt.readCharacteristic(characteristic);
byte[] bytes = characteristic.getValue();
String str = new String(bytes);
mHandler.obtainMessage(READ_MESSAGE, str).sendToTarget();
Log.e(TAG, "## readCharacteristic, 讀取到: " + str);
}
寫入數(shù)據(jù)
寫入數(shù)據(jù)時,我們需要先獲得特征状蜗,特征存在于服務內(nèi)需五,一般在發(fā)現(xiàn)服務的 onServicesDiscovered 時,查找到特征對象轧坎。
public void write(byte[] cmd) {
Log.e(TAG, "write:" + new String(cmd));
if (cmd == null || cmd.length == 0)
return;
// synchronized (LOCK) {
characteristicWrite.setValue(cmd);
characteristicWrite.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT);
mBluetoothGatt.writeCharacteristic(characteristicWrite);
Log.e(TAG, "write:--->" + new String(cmd));
// }
}
關閉藍牙連接
/**
* 關閉
*/
public void close() {
if (mBluetoothGatt == null) {
return;
}
mBluetoothGatt.close();
mBluetoothGatt = null;
setState(ConnectionState.STATE_NONE);
}
注意
調(diào)用 BluetoothGattCharactristic#setValue 傳入需要寫入的數(shù)據(jù)(藍牙最多單次1支持 20 個字節(jié)數(shù)據(jù)的傳輸宏邮,如果需要傳輸?shù)臄?shù)據(jù)大于這一個字節(jié)則需要分包傳輸)
static void performPeriodicTask( void )
{
attHandleValueNoti_t noti;
//dummy handle
noti.handle = 0x2E;
noti.len = 20;
uint8 i;
for (i= 0; i < 20; i++)
{
noti.value[i] = message_counter;
}
if (!(GATT_Notification(0, ¬i, FALSE))) //if sucessful
{
message_counter++;
}
}
Android ble低功耗藍牙開發(fā)-客戶端
Android ble低功耗藍牙開發(fā)-服務端
源碼傳送門
源碼: