藍牙數據傳輸問題
對于藍牙來說google已經封裝好了很多api所以使用起來并不會很難,但是實際開發(fā)中藍牙開發(fā)最頭疼的問題不是如何去調用api,而是數據的交互方面,如長連接,數據續(xù)傳,硬件接受速率等問題.
打開藍牙有幾種方式?
首先我們先了解下幾種常用的打開方式.
- 第一種方法相對簡單,直接調用系統(tǒng)對話框啟動藍牙:
在AndroidManifest文件中添加需要的權限,高版本也不需要動態(tài)授權:
<uses-permission android:name="android.permission.BLUETOOTH" />
//處理回調對話框
startActivityForResult(new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE), 1);
- 第二種方法,靜默開啟,不會有方法一的對話框:
在AndroidManifest文件中添加需要的權限: - 在AndroidManifest中配置需要的權限.
- 對于6.0的運行時權限進行適配,在java中動態(tài)授權.
- 最后直接調api開啟
BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
mBluetoothAdapter.enable(); //開啟
//mBluetoothAdapter.disable(); //關閉
如何搜索藍牙設備?
搜索分為:主動搜索和被動搜索。
- 主動搜索
- 創(chuàng)建BluetoothAdapter對象
- 配對的藍牙設備列表
- 定義發(fā)送接收廣播
藍牙的UUID是什么?有什么用?
UUID(Universally Unique Identifier) 統(tǒng)一表示定義.
藍牙是通過串口發(fā)送AT命令,藍牙默認是在數據模式的,要配置為AT命令模式,對其進行設置,不過UUID在出廠前是設置過的.
對于藍牙設備,每個服務都有一個與它對應的UUID(唯一的).
如:
信息同步服務:00001104-0000-1000-8000-00805F9B34FB 文件傳輸服務:00001106-0000-1000-8000-00805F9B34FB
如何使用藍牙進行數據傳輸?
藍牙模塊通信最重要的地方就是數據的發(fā)送和接收,其傳輸數據與Socket類似。
- 在網絡中使用Socket和ServerSocket控制客戶端和服務端的數據讀寫。
- 而藍牙通訊也由客戶端和服務端Socket來完成。藍牙客戶端Socket是BluetoothSocket,藍牙服務端Socket是BluetoothServerSocket。這兩個類都在android.bluetooth包中。
- 無論是BluetoothSocket,還是BluetoothServerSocket,都需要一個UUID(全局唯一標識符,Universally Unique Identifier),UUID相當于Socket的端口,而藍牙地址相當于Socket的IP。
實際開發(fā)中需要注意的地方.
需要注意的
- 因為涉及涉及到I/O編程,所以需要注意兩端的編碼'utf-8'要一致.
- 客戶端與服務端的UUID也要相同.
- 藍牙屬于底層數據傳輸,所以實際開發(fā)更多發(fā)送的是16進制數據.
- 因為涉及到了I/O編程,所以對線程控制這塊(同步,鎖機制)需要注意使用.
- 對于一些大容量字節(jié)數組的發(fā)送需要注意的地方.
- 為了用戶更好的體檢避免流量的過度浪費,使用阻塞式的InputStream讀取.
1. 因為涉及涉及到I/O編程,所以需要注意兩端的編碼'utf-8'要一致.
os.write("datas....".getBytes("utf-8"));
2. 客戶端與服務端的UUID也要相同.
//客戶端Socket
device.createRfcommSocketToServiceRecord(MY_UUID);
//服務端Socket
mBluetoothAdapter.listenUsingRfcommWithServiceRecord(NAME, MY_UUID);
3. 藍牙屬于底層數據傳輸,所以實際開發(fā)更多發(fā)送的是16進制數據.
public static String bytesToHexString(byte[] bytes) {
String result = "";
for (int i = 0; i < bytes.length; i++) {
String hexString = Integer.toHexString(bytes[i] & 0xFF);
if (hexString.length() == 1) {
hexString = '0' + hexString;
}
result += hexString.toUpperCase();
}
return result;
}
5. 對于一些大容量字節(jié)數組的發(fā)送需要注意的地方.
我們需要發(fā)送64個字節(jié)的數組,如果一次性發(fā)送過去,單片機那里可能無法及時處理以致沒有任何回應,因為單片機那里是設置了數據接收的延時時間。要想暢通的與藍牙模塊通信,考慮這個時間差非常重要。調整字節(jié)的發(fā)送速率,就成為非常關鍵的一步。值得注意的是,數據的發(fā)送是非常快的,就是因為這樣才會導致單片機那里無法及時處理,所以,每次發(fā)送后的延時是非常重要的。我們單片機那里的延時是10毫秒,所以我們選擇發(fā)送完每個字節(jié)后就延時10毫秒再發(fā)下個字節(jié)。
for (byte b : bytes) {
out.write(b);
Thread.sleep(10);
}
6.為了用戶更好的體檢避免流量的過度浪費,使用阻塞式的InputStream讀取.
在使用InputStream的時候,必須注意,InputStream的讀取是阻塞的。這點在一般的情況下是不會影響到我們的程序,但是記住這個情況對于代碼的設計是非常重要的,尤其是在考慮用戶體驗的時候。
無參數的read()是每次只從流中讀取一個字節(jié),這種做法效率非常低,但是簡單,像是讀取整數值這種情況,使用read()就非常好,但如果是16進制字符串呢?使用InputStream.read(byte[] b)或者InputStream.read(byte[] b,int off,int len)方法,這樣一次就能讀取多個字節(jié)。
如果是讀取多個字節(jié),我們常常使用InputStream.available()方法來獲取數據流中可讀字節(jié)的個數。讀取本地數據的時候,該方法發(fā)揮得非常好,但如果是讀取非本地數據,就可能出現字節(jié)遺漏的問題,像是要讀取100個字節(jié),可能就是90個,甚至是0個。
出現0個的情況就是單片機那邊沒有響應或者字節(jié)還沒發(fā)送過來,這時我們就需要一個循環(huán)來保證我們能夠拿到數據:
int count = 0;
while (count == 0) {
count = in.available();
}
byte[] bytes = new byte[count];
in.read(bytes);
但像是上面的90個字節(jié)的情況就是字節(jié)遺漏。對于這種情況,解決方法也很簡單:
byte[] bytes = new byte[count];
int readCount = 0; // 已經成功讀取的字節(jié)的個數
while (readCount < count) {
readCount += in.read(bytes, readCount, count - readCount);
}