android 藍芽4.0 ble 低功耗藍芽
一:概述
這段時間做了藍芽4.0的專案,就是一個藍芽裝置控制手機進行拍照。並且有很多按鍵,不同的按鍵對應到手機上有不同的功能,並且組合起來也有不同的功能。
低功耗藍芽有中央裝置後周邊裝置的概念手機就是一箇中央裝置,像我這次試用的一個控制器, 我試過小米體重秤。來測試玩。 a.GATT 這是藍芽技術聯盟定義的一個協議。 b.Service 這個是許多或者一個特徵值的集合。 c.Characteristic 這是特徵值。我們需要使用的資料就是這個。 我們可以讀取或者在其值在變化的時候會接收到其變化的回撥。 d.Descriptor 這個是特徵值的描述。最開始我有點不太懂這個東西到底哪裡能用到。結果在設定 Characteristic 為可以通知的時候才看到,就是輔助Characteristic 的。這個谷歌官方的解釋
可以看看我做的一個小demo的效果(一個控制器按鍵後):
二:原始碼解析
現在就通過原始碼講解下我這個小demo:
因為這個是需要藍芽許可權,並且在android 系統4.3後才支援的介面。所以在使用藍芽之前都需要申請許可權和判斷是否可以使用.
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
//這裡的true標示手機如果不支援低功耗藍芽就直接不讓安裝,如果你不想這樣的話,可以寫false
<uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/>
這裡是檢查是否支援低功耗藍芽。
// Use this check to determine whether BLE is supported on the device. Then
// you can selectively disable BLE-related features.
if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE _BLUETOOTH_LE)) {
Toast.makeText(this,"不支援藍芽4.0", Toast.LENGTH_SHORT).show();
finish();
}
獲取mBluetoothAdapter 需要用他進行判斷藍芽的開關,和搜尋藍芽裝置。
// Initializes Bluetooth adapter.
final BluetoothManager bluetoothManager =
(BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
mBluetoothAdapter = bluetoothManager.getAdapter();
if(mBluetoothAdapter == null){
Toast.makeText(this,"獲取失敗!", Toast.LENGTH_SHORT).show();
finish();
}
猜這個是幹嘛的,居然猜到是判斷是否開藍芽了。喲西。
如果沒有開藍芽當然就讓使用者開了。會有一個彈窗出來讓使用者選擇是開還是關。
if (!mBluetoothAdapter.isEnabled()) {
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
}
如果使用者選擇不開的話,就直接結束當前的activity。
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
// User chose not to enable Bluetooth.
if (requestCode == REQUEST_ENABLE_BT && resultCode == Activity.RESULT_CANCELED) {
finish();
return;
}
super.onActivityResult(requestCode, resultCode, data);
}
好了。藍芽也開了。那麼開始搜尋藍芽了。mLeScanCallback是一個回撥,當搜尋到一個藍芽的時候就回調他的介面
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;
mBluetoothAdapter.stopLeScan(mLeScanCallback);
}
}, SCAN_PERIOD);
mScanning = true;
mBluetoothAdapter.startLeScan(mLeScanCallback);
} else {
mScanning = false;
mBluetoothAdapter.stopLeScan(mLeScanCallback);
}
}
喏,回撥就長這個樣子。還行吧。
BluetoothDevice 重寫了equals的方法用裝置的address作為作為判斷的
所以 listDevice.contains(device) 是可以判斷的。好吧。這是java的基礎知識。還是嘮叨兩句。
private BluetoothAdapter.LeScanCallback mLeScanCallback =
new BluetoothAdapter.LeScanCallback() {
@Override
public void onLeScan(final BluetoothDevice device, int rssi,
byte[] scanRecord) {
runOnUiThread(new Runnable() {
@Override
public void run() {
if(!listDevice.contains(device)){
//不重複新增
listDevice.add(device);
deviceAdapter.setListDevice(listDevice);
}
}
});
}
};
我們找到了藍芽之後幹嘛呢?你覺得還能幹嘛呢?肯定就是開始玩dota啦(想的美)。
好了我們準備開始連線藍芽。
/*mBluetoothLeService是一個android上的後臺服務,不是低功耗藍芽中的服務啊。這個service是從谷歌demo中拷貝的,改動了部分。*/
mBluetoothLeService.connect(device.getAddress());
這個connect中具體的實現,我看見了mBluetoothGatt,就是那個破協議。
然後還看見mGattCallback一個回撥,想想應該是連線成功後的一些回撥。
public boolean connect(final String address) {
if (mBluetoothAdapter == null || address == null) {
Log.w(TAG, "BluetoothAdapter not initialized or unspecified address.");
return false;
}
if (mBluetoothDeviceAddress != null && address.equals(mBluetoothDeviceAddress)
&& mBluetoothGatt != null) {
if (mBluetoothGatt.connect()) {
mConnectionState = STATE_CONNECTING;
return true;
} else {
return false;
}
}
final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address);
if (device == null) {
Log.w(TAG, "Device not found. Unable to connect.");
return false;
}
// We want to directly connect to the device, so we are setting the autoConnect
// parameter to false.
mBluetoothGatt = device.connectGatt(this, false, mGattCallback);
Log.d(TAG, "Trying to create a new connection.");
mBluetoothDeviceAddress = address;
mConnectionState = STATE_CONNECTING;
return true;
}
下面的就是連線成功後具體的回撥。各部分幹什麼的在註釋中可以看看。
broadcastUpdate(…)是傳送廣播。
private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
String intentAction;
if (newState == BluetoothProfile.STATE_CONNECTED) {
//藍芽連線成功後
broadcastUpdate(intentAction);
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
//藍芽斷開連線
broadcastUpdate(intentAction);
}
}
@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
if (status == BluetoothGatt.GATT_SUCCESS) {
//這個就是發現了藍芽4.0的服務
broadcastUpdate(ACTION_GATT_SERVICES_DISCOVERED);
} else {
Log.w(TAG, "onServicesDiscovered received: " + status);
}
}
@Override
public void onCharacteristicRead(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic,
int status) {
if (status == BluetoothGatt.GATT_SUCCESS) {
//這個是讀取Characteristic,通俗點就是讀取我們想要的資料
broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic);
}
}
@Override
public void onCharacteristicChanged(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic) {
//這個是我們監聽的某個Characteristic變化了。然後傳送一個廣播
broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic);
}
};
再看看我們接收廣播的時候做了什麼 這個廣播接收的部分程式碼。因為程式碼貼的太多感覺有點不爽了。
else if (BluetoothLeService.
ACTION_GATT_SERVICES_DISCOVERED.equals(action)) {
//services被發現的狀態
//在這裡找到要要讀的特徵值或者需要通知的特徵值
LogServiceAndChara(mBluetoothLeService.getSupportedGattServices());
}
我們在看看LogServiceAndChara()函式到底幹了啥。(這個函式有點亂。T_T,就貼出部分程式碼吧)
首先我們遍歷所有服務然後找我們想要的特殊服務(-_-我啥都不知道。)這個特殊服務怎麼找?
用uuid,找到了我們想找的服務後,然互就開始找特徵值,也是用uuid識別。
private void LogServiceAndChara(List<BluetoothGattService> gattServices){
// Loops through available GATT Services.
for (BluetoothGattService gattService : gattServices) {
if(gattService.getUuid().toString().equals(SampleGattAttributes.DEVICE_SERVICE)){
//找到了這個服務
List<BluetoothGattCharacteristic> gattCharacteristics =
gattService.getCharacteristics();
for(BluetoothGattCharacteristic gattCharacteristic : gattCharacteristics){ if(gattCharacteristic.getUuid().toString().equals(SampleGattAttributes.DEVICE_CHARACTER)){
//找到了對應的服務
if((gattCharacteristic.getProperties() | BluetoothGattCharacteristic.PROPERTY_READ)>0){
if(mNotifyCharacteristic != null){
mBluetoothLeService.setCharacteristicNotification(gattCharacteristic,false);
mNotifyCharacteristic = null;
}
mBluetoothLeService.readCharacteristic(gattCharacteristic);
}
if((gattCharacteristic.getProperties() | BluetoothGattCharacteristic.PROPERTY_NOTIFY)>0){
mBluetoothLeService.setCharacteristicNotification(gattCharacteristic,true);
mNotifyCharacteristic = gattCharacteristic;
}
}
}
}
}
}
這兩個分別是特徵值和服務的uuid
public static String DEVICE_CHARACTER = "0000dfb1-0000-1000-8000-00805f9b34fb";
public static String DEVICE_SERVICE = "0000dfb0-0000-1000-8000-00805f9b34fb";
找到了我們想要的特徵值後我們就要監聽他了,或者讀取他,我這裡是用的監聽。
這裡有個點我糾結了挺久的地方。
BluetoothGattDescriptor descriptor = characteristic.getDescriptor(
UUID.fromString(SampleGattAttributes.CLIENT_CHARACTERISTIC_CONFIG));
我列印了所有特徵值的descriptor的uuid都是一個樣,我覺得應該不是硬體上自定義的。應該是藍芽技術聯盟定義的公用的。
確定了就是他們定義公用的,然後用
descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
設定這個特徵值為可以為監聽狀態(就是可以變化的時我能收到通知)
public void setCharacteristicNotification(BluetoothGattCharacteristic characteristic,
boolean enabled) {
if (mBluetoothAdapter == null || mBluetoothGatt == null) {
Log.w(TAG, "BluetoothAdapter not initialized");
return;
}
mBluetoothGatt.setCharacteristicNotification(characteristic, enabled);
//關於CLIENT_CHARACTERISTIC_CONFIG這個descriptor的解釋
BluetoothGattDescriptor descriptor = characteristic.getDescriptor(
UUID.fromString(SampleGattAttributes.CLIENT_CHARACTERISTIC_CONFIG));
descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
mBluetoothGatt.writeDescriptor(descriptor);
}
在接收到變化或讀取到後都能在這裡接收到(這個是來自廣播中的方法)
else if (BluetoothLeService.ACTION_DATA_AVAILABLE.equals(action)) {
//接收到資料的狀態
str += intent.getStringExtra(BluetoothLeService.EXTRA_DATA);
str += "\n\r";
tv.setText(str);
}
加個好友共同學習(不是公眾號):
因為小弟水平有限,如果有寫的有問題,希望指出。