Android 低功耗藍牙開發(資料互動)
- 前言
- 正文
- 一、BluetoothGattCallback
- 1. onPhyUpdate
- 2. onPhyRead
- 3. onServicesDiscovered
- 4. onCharacteristicRead
- 5. onCharacteristicWrite
- 6. onCharacteristicChanged
- 7. onDescriptorRead
- 8. onDescriptorWrite
- 9. onReliableWriteCompleted
- 10. onReadRemoteRssi
- 11. onMtuChanged
- 二、使用
- 1. 連接配接裝置
- 2. 擷取MTU Size
- 3. 發現服務
- 4. 打開通知
- 5. 寫入資料
- 6. 收到資料
- 7. Phy值讀取和改變
- 8. 讀取特性、描述符、RSSI
- 三、源碼
前言
在上一篇低功耗藍牙開發文章中,我講述了掃描和連接配接,本篇文章講述資料的互動。當了解了資料互動後就可以開始進行低功耗藍牙硬體和手機App軟體相結合的項目,例如藍牙音箱、藍牙燈、藍牙鎖等等。
正文
因為本篇文章會接着上一篇文章進行一個續寫,上一篇文章 Android 低功耗藍牙開發(掃描、連接配接),沒看過的可以先看看,這樣可以平穩過度,當然如果對掃描和連接配接都沒有問題的可以直接從本篇文章開始看。
一、BluetoothGattCallback
在進行編碼之前首先要了解一個很重要的東西,那就是BluetoothGattCallback,這個類非常重要,可以說你能不能進行低功耗藍牙的資料互動全看它了。
之前在進行低功耗藍牙連接配接的時候使用的是Gatt連接配接,不知道你是否還記得。回顧一下:
可以看到通過連接配接gatt,使用了抽象類BluetoothGattCallback,重寫了裡面的一個onConnectionStateChange方法,進行裝置連接配接狀态的回調。這個類裡面還有一些方法,可以用于針對開發中的使用進行調用。下面來介紹一下:
方法 | 描述 |
onPhyUpdate | 實體層改變回調 |
onPhyRead | 裝置實體層讀取回調 |
onConnectionStateChange | Gatt連接配接狀态回調 |
onServicesDiscovered | 發現服務回調 |
onCharacteristicRead | 特性讀取回調 |
onCharacteristicWrite | 特性寫入回調 |
onCharacteristicChanged | 特性改變回調 |
onDescriptorRead | 描述讀取回調 |
onDescriptorWrite | 描述寫入回調 |
onReliableWriteCompleted | 可靠寫入完成回調 |
onReadRemoteRssi | 讀取遠端裝置信号值回調 |
onMtuChanged | MtuSize改變回調 |
onConnectionUpdated | 連接配接更新回調 |
這裡光有一個表好像是沒有啥用,在介紹詳細的API方法及裡面的屬性值之前先做好準備工作。
BluetoothGattCallback是一個抽象類,那麼自然需要一個實作類,在之前的文章中我是通過匿名實作裡面的onConnectionStateChange方法對低功耗藍牙裝置進行連接配接和斷開的監聽的。
不過在實際開發中這樣的做法并不可取,因為一個藍牙項目裡面不可能隻有一個地方需要使用這個監聽,那麼此時就需要封裝一個類去單獨實作BluetoothGattCallback中的方法,然後再根據需要取使用。
下面在com.llw.bledemo下建立一個callback包,包裡面建立一個BleCallback類,然後繼承BluetoothGattCallback,重寫裡面的onConnectionStateChange方法,代碼如下:
public class BleCallback extends BluetoothGattCallback {
private static final String TAG = BleCallback.class.getSimpleName();
/**
* 連接配接狀态改變回調
*
* @param gatt gatt
* @param status gatt連接配接狀态
* @param newState 新狀态
*/
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
if (status == BluetoothGatt.GATT_SUCCESS) {
switch (newState) {
case BluetoothProfile.STATE_CONNECTED://連接配接成功
Log.d(TAG, "連接配接成功");
break;
case BluetoothProfile.STATE_DISCONNECTED://斷開連接配接
Log.e(TAG, "斷開連接配接");
break;
default:
break;
}
} else {
Log.e(TAG, "onConnectionStateChange: " + status);
}
}
}
為了差別于上一篇文章,我這裡會建立一個DataExchangeActivity來做資料的互動,不會影響到上一篇文章的内容。然後在MainActivity,點選清單item時調用的connectDevice方法中跳轉到DataExchangeActivity中,通過傳遞藍牙對象過去。
接下來看看DataExchangeActivity中做了什麼?
public class DataExchangeActivity extends AppCompatActivity {
private static final String TAG = DataExchangeActivity.class.getSimpleName();
/**
* Gatt
*/
private BluetoothGatt bluetoothGatt;
/**
* 裝置是否連接配接
*/
private boolean isConnected = false;
/**
* Gatt回調
*/
private BleCallback bleCallback;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_data_exchange);
//初始化
bleCallback = new BleCallback();
//擷取上個頁面傳遞過來的裝置
BluetoothDevice device = getIntent().getParcelableExtra("device");
//連接配接gatt 設定Gatt回調
bluetoothGatt = device.connectGatt(this, false, bleCallback);
}
/**
* 斷開裝置連接配接
*/
private void disconnectDevice() {
if (isConnected && bluetoothGatt != null) {
bluetoothGatt.disconnect();
}
}
/**
* Toast提示
*
* @param msg 内容
*/
private void showMsg(String msg) {
Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
}
}
很簡單這個代碼,就是接收上個頁面傳遞過來的藍牙對象,然後進行gatt連接配接。直接傳入執行個體化之後的bleCallback即可,請注意關于gatt的處理都是在子線程中進行的,可以驗證一下:
運作一下,進入互動頁面。
下面進行GattCallback中的API介紹。
1. onPhyUpdate
/**
* 實體層改變回調
*
* @param gatt gatt
* @param txPhy 發送速率 1M 2M
* @param rxPhy 接收速率 1M 2M
* @param status 更新操作的狀态
*/
@Override
public void onPhyUpdate(BluetoothGatt gatt, int txPhy, int rxPhy, int status) {
super.onPhyUpdate(gatt, txPhy, rxPhy, status);
}
通過gatt.setPreferredPhy()方法觸發,例如:
//設定 2M
gatt.setPreferredPhy(BluetoothDevice.PHY_LE_2M, BluetoothDevice.PHY_LE_2M, BluetoothDevice.PHY_OPTION_NO_PREFERRED);
2. onPhyRead
/**
* 讀取實體層回調
*
* @param gatt gatt
* @param txPhy 發送速率 1M 2M
* @param rxPhy 接收速率 1M 2M
* @param status 更新操作的狀态
*/
@Override
public void onPhyRead(BluetoothGatt gatt, int txPhy, int rxPhy, int status) {
super.onPhyRead(gatt, txPhy, rxPhy, status);
}
通過gatt.readPhy();進行觸發,該方法不需要傳入參數。
3. onServicesDiscovered
/**
* 發現服務回調
*
* @param gatt gatt
* @param status gatt狀态
*/
@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
super.onServicesDiscovered(gatt, status);
}
通過gatt.discoverServices(); 觸發,沒有輸入參數。
4. onCharacteristicRead
/**
* 特性讀取回調
*
* @param gatt gatt
* @param characteristic 特性
* @param status gatt狀态
*/
@Override
public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
super.onCharacteristicRead(gatt, characteristic, status);
}
通過gatt.readCharacteristic(characteristic);觸發,需要建構一個BluetoothGattCharacteristic 對象,在後面的執行個體中會示範。
5. onCharacteristicWrite
/**
* 特性寫入回調
*
* @param gatt gatt
* @param characteristic 特性
* @param status gatt狀态
*/
@Override
public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
super.onCharacteristicWrite(gatt, characteristic, status);
}
通過gatt.writeCharacteristic(characteristic);觸發,需要BluetoothGattCharacteristic 對象,在後面的執行個體中會示範。
6. onCharacteristicChanged
/**
* 特性改變回調
*
* @param gatt gatt
* @param characteristic 特性
*/
@Override
public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
super.onCharacteristicChanged(gatt, characteristic);
}
此回調的觸發需要遠端裝置特性改變,通俗的說就是,你需要給裝置發送消息之後,才會觸發這個回調。在後面的執行個體中會示範。
7. onDescriptorRead
/**
* 描述符擷取回調
*
* @param gatt gatt
* @param descriptor 描述符
* @param status gatt狀态
*/
@Override
public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
super.onDescriptorRead(gatt, descriptor, status);
}
通過gatt.readDescriptor(descriptor);觸發,需要傳入BluetoothGattDescriptor對象,在後面的執行個體中會示範。
8. onDescriptorWrite
/**
* 描述符寫入回調
*
* @param gatt gatt
* @param descriptor 描述符
* @param status gatt狀态
*/
@Override
public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
super.onDescriptorWrite(gatt, descriptor, status);
}
通過gatt.writeDescriptor(descriptor);觸發,需要傳入BluetoothGattDescriptor 對象,在後面的執行個體中會示範。
9. onReliableWriteCompleted
/**
* 可靠寫入完成回調
*
* @param gatt gatt
* @param status gatt狀态
*/
@Override
public void onReliableWriteCompleted(BluetoothGatt gatt, int status) {
super.onReliableWriteCompleted(gatt, status);
}
通過gatt.executeReliableWrite();觸發,不需要參數,在實際中用的不是很多。
10. onReadRemoteRssi
/**
* 讀取遠端裝置的信号強度回調
*
* @param gatt gatt
* @param rssi 信号強度
* @param status gatt狀态
*/
@Override
public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) {
super.onReadRemoteRssi(gatt, rssi, status);
}
通過gatt.readRemoteRssi();觸發,無需參數,實際中使用不多。
11. onMtuChanged
/**
* Mtu改變回調
*
* @param gatt gatt
* @param mtu new MTU size
* @param status gatt狀态
*/
@Override
public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) {
super.onMtuChanged(gatt, mtu, status);
}
通過gatt.requestMtu(512);觸發,需要傳入請求的Mtu大小,最大是512,這裡機關是位元組,512是理論最大值,如果不設定就是預設23位元組,而且傳輸本身用掉3位元組,實際上攜帶資料隻有20位元組。在後面的執行個體中會示範。
最後的一個onConnectionUpdated回調無法進行覆寫,就不介紹了,下面進入使用API環節。
二、使用
1. 連接配接裝置
第一步是連接配接,代碼在上面已經寫好,連接配接上裝置之後,
2. 擷取MTU Size
下一步就是擷取MtuSize。
然後會觸發onMtuChanged回調,
3. 發現服務
在onMtuChanged回調中去發現服務。
然後就會觸發onServicesDiscovered回調,在這個回調中要做的就是打開通知開關。這個在之前沒有提到,因為它不在基礎的回調API中,但是打開通知開關屬于描述符的内容,是以當你設定了之後會觸發onDescriptorWriteh回調,還是先來看這個通知怎麼打開吧。
4. 打開通知
為了規範一些,将使用到的方法封裝起來,這樣便于管理,增加一個BleHelper類和BleConstant類。
建立一個utils包,包下建兩個類,下面先看這個BleConstant類
public class BleConstant {
/**
* 服務 UUID
*/
public static final String SERVICE_UUID = "0000ff01-0000-1000-8000-00805f9b34fb";
/**
* 特性寫入 UUID
*/
public static final String CHARACTERISTIC_WRITE_UUID = "0000ff02-0000-1000-8000-00805f9b34fb";
/**
* 特性讀取 UUID
*/
public static final String CHARACTERISTIC_READ_UUID = "0000ff10-0000-1000-8000-00805f9b34fb";
/**
* 描述 UUID
*/
public static final String DESCRIPTOR_UUID = "00002902-0000-1000-8000-00805f9b34fb";
/**
* 電池服務 UUID
*/
public static final String BATTERY_SERVICE_UUID = "0000180f-0000-1000-8000-00805f9b34fb";
/**
* 電池特征(特性)讀取 UUID
*/
public static final String BATTERY_CHARACTERISTIC_READ_UUID = "00002a19-0000-1000-8000-00805f9b34fb";
/**
* OTA服務 UUID
*/
public static final String OTA_SERVICE_UUID = "5833ff01-9b8b-5191-6142-22a4536ef123";
/**
* OTA特征(特性)寫入 UUID
*/
public static final String OTA_CHARACTERISTIC_WRITE_UUID = "5833ff02-9b8b-5191-6142-22a4536ef123";
/**
* OTA特征(特性)表示 UUID
*/
public static final String OTA_CHARACTERISTIC_INDICATE_UUID = "5833ff03-9b8b-5191-6142-22a4536ef123";
/**
* OTA資料特征(特性)寫入 UUID
*/
public static final String OTA_DATA_CHARACTERISTIC_WRITE_UUID = "5833ff04-9b8b-5191-6142-22a4536ef123";
}
這裡面都是正常的UUID常量值,就是一些服務和特性的辨別符,這個UUID常量值由SIG聯盟所規定的,當然也可以根據自己的硬體去做設定,值不是固定的,請根據實際的硬體為主。
下面是BleHelper類,代碼如下:
public class BleHelper {
/**
* 啟用指令通知
*/
public static boolean enableIndicateNotification(BluetoothGatt gatt) {
//擷取Gatt 服務
BluetoothGattService service = gatt.getService(UUID.fromString(BleConstant.OTA_SERVICE_UUID));
if (service == null) {
return false;
}
//擷取Gatt 特征(特性)
BluetoothGattCharacteristic gattCharacteristic = service.getCharacteristic(UUID.fromString(BleConstant.OTA_CHARACTERISTIC_INDICATE_UUID));
return setCharacteristicNotification(gatt, gattCharacteristic);
}
/**
* 設定特征通知
* return true, if the write operation was initiated successfully
*/
private static boolean setCharacteristicNotification(BluetoothGatt gatt, BluetoothGattCharacteristic gattCharacteristic) {
//如果特性具備Notification功能,傳回true就代表裝置設定成功
boolean isEnableNotification = gatt.setCharacteristicNotification(gattCharacteristic, true);
if (isEnableNotification) {
//建構BluetoothGattDescriptor對象
BluetoothGattDescriptor gattDescriptor = gattCharacteristic.getDescriptor(UUID.fromString(BleConstant.DESCRIPTOR_UUID));
gattDescriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
//寫入描述符
return gatt.writeDescriptor(gattDescriptor);
} else {
return false;
}
}
}
代碼沒有什麼難度,下面就是在發現服務的回調中調用BleHelper中的方法。
當開啟通知失敗時斷開gatt連接配接。下面會進入onDescriptorWrite回調
當通知開啟成功可以就可以進行資料的互動了,不過在此之前先運作一下,看程式是否按照我們所想的運作,看一下日志:
Very Good!
現在基本的前置工作都準備好了,下一步就是資料的讀寫了,首先來看看寫資料到裝置。
5. 寫入資料
正常來說寫入資料的話肯定是要對裝置做點什麼,列如一個藍牙燈,控制這個燈開關,那麼這就是一條指令,指令的内容是App與裝置端協商好的,這個要以實際的需求為主。假設我對一個藍牙手環要進行資料的寫入,那麼肯定會有很多的指令,是以可以封裝一個方法集中處理,依然寫在BleHelper中。方法如下:
/**
* 發送指令
* @param gatt gatt
* @param command 指令
* @param isResponse 是否響應
* @return
*/
public static boolean sendCommand(BluetoothGatt gatt, String command, boolean isResponse) {
//擷取服務
BluetoothGattService service = gatt.getService(UUID.fromString(BleConstant.OTA_SERVICE_UUID));
if (service == null) {
Log.e("TAG", "sendCommand: 服務未找到");
return false;
}
//擷取特性
BluetoothGattCharacteristic characteristic = service.getCharacteristic(UUID.fromString(BleConstant.OTA_CHARACTERISTIC_WRITE_UUID));
if (characteristic == null) {
Log.e("TAG", "sendCommand: 特性未找到");
return false;
}
//寫入類型 WRITE_TYPE_DEFAULT 預設有響應, WRITE_TYPE_NO_RESPONSE 無響應。
characteristic.setWriteType(isResponse ?
BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT : BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE);
//将字元串command轉Byte後進行寫入
characteristic.setValue(ByteUtils.hexStringToBytes(command));
boolean result = gatt.writeCharacteristic(characteristic);
Log.d("TAG", result ? "寫入初始化成功:" + command : "寫入初始化失敗:" + command);
return result;
}
下面解釋一下這個方法的内容,首先通過服務UUID擷取到Gatt服務,然後通過寫資料特性UUID從服務中擷取寫資料特性,這裡的UUID的值請根據自己的實際情況填寫,不知道就問硬體工程師。然後根據傳入的isResponse去設定是否需要響應,這裡要弄清楚有響應和無響應的差別,有響應的速度比無響應慢,但是有響應更安全,因為你可以對每一次發出的資料進行一個确認,是否發送到,有無丢失。不過這樣的話效率會比較低,一般來說實際開發中大部分指令型消息都會選擇無響應,資料型消息會選擇有響應。
這裡增加一個工具類,代碼如下:
public class ByteUtils {
/**
* Convert hex string to byte[]
*
* @param hexString the hex string
* @return byte[]
*/
public static byte[] hexStringToBytes(String hexString) {
if (hexString == null || hexString.equals("")) {
return null;
}
hexString = hexString.toUpperCase();
int length = hexString.length() / 2;
char[] hexChars = hexString.toCharArray();
byte[] d = new byte[length];
for (int i = 0; i < length; i++) {
int pos = i * 2;
d[i] = (byte) (charToByte(hexChars[pos]) << 4 | charToByte(hexChars[pos + 1]));
}
return d;
}
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();
}
/**
* Convert char to byte
*
* @param c char
* @return byte
*/
private static byte charToByte(char c) {
return (byte) "0123456789ABCDEF".indexOf(c);
}
}
然後就是對消息的内容轉Byte,這裡的指令長度有一個最大值就是之前通過onMtuChange回調時得到的數值,247 去掉3位元組傳輸實際上就是244位元組,那麼你一次傳輸的最大位元組就是244,這個值你不能寫死,因為你要根據Android版本和藍牙裝置硬體去适配。最終通過setValue将值放入特性,然後通過寫入特性傳遞給裝置。然後傳回一個boolean值,這個值隻是表明寫入特性的初始化成功,不代表就真的寫入到裝置中了,那麼寫入到裝置成功的辨別是什麼呢?
先不急,我們先調用這個方法,
修改頁面的布局檔案activity_data_exchange.xml,代碼如下:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="bottom"
android:orientation="vertical"
android:paddingBottom="@dimen/dp_20"
tools:context=".DataExchangeActivity">
<EditText
android:id="@+id/et_command"
android:hint="請輸入指令"
android:layout_width="match_parent"
android:layout_height="60dp"/>
<Button
android:id="@+id/btn_send_command"
android:layout_marginTop="10dp"
android:text="發送指令"
android:insetTop="0dp"
android:insetBottom="0dp"
android:layout_width="match_parent"
android:layout_height="50dp"/>
</LinearLayout>
然後回到DataExchangeActivity,
先聲明變量
private EditText etCommand;
然後在onCreate中
然後先寫入一條指令給裝置。例如0102,這對于我這個裝置來說是一個切換模式的指令,這條指令不需要響應,那麼在哪裡确認這個指令寫入到了裝置呢?通過onCharacteristicWrite。
先修改一下這個回調方法中的内容。在BleCallback中,
下面運作一下:
下面來看看控制欄的日志列印:
寫入成功。下面來看收到資料的處理
6. 收到資料
如果你需要收到資料,那麼就需要使用有響應的設定,這裡設定為true。
當裝置的特性改變時
我這裡列印一下,然後運作。
01020,是我的藍牙裝置中定義一個值,收到0081 則表示正常,然後看控制台。
這裡當我們進行有響應的資料寫入時,裝置收到後會先觸發onCharacteristicChanged然後再觸發onCharacteristicWrite。也就是先發送指令,裝置收到回複後,再是你的指令寫入成功,注意這個執行的順序,這很重要,在實際開發中請注意這一點,然後再去寫相應的業務邏輯。
還有一些其他的API也需要介紹一下怎麼使用的,例如onPhyRead和onPhyUpdate。
7. Phy值讀取和改變
首先來看這個值的讀取,比如我們在通知開啟成功之後去讀取這個裝置的Phy
這個讀取的方法要求你的Android版本必須要在8.0和8.0以上,是以如果你的Android裝置是低版本的就不用考慮去使用這個API了,因為系統不支援。我是Android10.0是以沒問題,調用這個方法API就會觸發onPhyRead回調。我們在回調的時候列印一下内容,看看目前的硬體Phy是什麼值。
運作一下看控制台列印了什麼
都是1 就代表1M的發送和接收速率,那麼你也可以改成2M,可以這麼做,當我讀取到速率為1M時就請求2M的速率。
然後就會觸發onPhyUpdate回調,我們列印一下:
運作之後檢視控制台:
Beautiful!現在我們知道這個Phy怎麼改的了,那麼在什麼時候改呢?當你要傳大資料的時候。例如你要對藍牙裝置中的軟體進行更新,那麼更新檔案是比較大的,此時在條件允許提高傳輸速率可以降低等待時間。
8. 讀取特性、描述符、RSSI
一般來說這三個回調用的比較少,如果你不熟悉的話,前期可以使用。它們在不同的時候使用,由于擷取特性和描述符需要一個參數,是以你需要在有這個參數的時候去調用它,比如當寫入特性回調被觸發時,
再比如特性改變時。
然後會觸發onCharacteristicRead回調,在這個回調中列印一下特性的uuid。
其實說起來這個方法比較的雞肋,這可能也是為什麼使用的比較少的原因了,因為當我能知道特性是什麼的時候,我直接就能拿到特性對象所攜帶的資訊,根本不需要再去通過gatt.readCharacteristic(characteristic);去檢視特性。說是這麼說,不過該介紹的還是要有的,知道就可以了。另一個描述符的讀取也是一樣的道理,可以在描述符寫入回調時調用,
同時我還調用了gatt.readRemoteRssi,因為擷取RSSI不需要參數,隻要你的裝置保持了連接配接,那麼可以在任何時候擷取RSSI,然後我們在對應的地方去列印一下:
下面運作一下:
這裡可以看到我寫入了0102之後裝置的位址會發生改變,是以我退出了目前頁面,再連接配接裝置之後,發送了010200,這裡我們看到了RSSI和描述符的UUID,不過特性的UUID并沒有列印出來,這是為什麼呢?gatt.readCharacteristic(characteristic);執行後會傳回一個boolean結果,列印一下這個結果看看。
運作列印一下:
那麼來看看為什麼會是false。
這裡我突然想到一種可能性,是不是讀取這個特性的對象有問題,我現在的這個特性的uuid是之前寫特性的uuid,是以拿不到讀特性的回調。然後試了一下,發現還是false,拿不到特性,這個就和硬體有關系了,藍牙硬體會根據功能的需求,對特性進行改動,有一些特性不重要的就去掉了,是以針對我這個藍牙裝置來說就拿不到讀特性。