基於藍芽介面卡的PC與Android端通訊
Demo
首先,直接給Demo,對於只想使用的朋友,直接下載使用即可。Demo其實也是從網上爬來的,之後做了各種除錯和修改。
原有Demo程式碼下載,可見地址。
修改後Demo效果如下。效果不太清晰,見諒。
(1)PC端
(2)Android端
細節實現
Android端
android端做為客戶端要與PC通訊,需要完成以下幾步。新增藍芽許可權
<uses-permission android:name="android.permission.BLUETOOTH" /> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
註冊廣播接收器
個人認為該步驟可選。但最好存在。
原因在於,雖然藍芽介面卡可以獲取周圍藍芽裝置的列表,但對於周圍藍芽裝置的掃描比較耗時。
返回藍芽裝置列表時,可能仍處於搜尋過程中。
當掃描完成時,廣播接收器將受到action為ACTION_DISCOVERY_FINISHED的廣播。此時,獲取裝置列表,將比較全面。
建議接收器監聽,以下三個action。
IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(BluetoothDevice.ACTION_FOUND);//發現裝置 intentFilter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);//掃描完畢 intentFilter.addAction(BluetoothAdapter.ACTION_DISCOVERY_STARTED);//掃描結束 registerReceiver(broadcastReceiver, intentFilter);
開啟藍芽
if (!bluetoothAdapter.isEnabled()) {
bluetoothAdapter.enable();
}
開啟介面卡後,android將開始遍歷周圍可以被訪問的藍芽裝置。該動作將觸發廣播。
當然,也可以通過觸發bluetoothAdapter.startDiscovery()重新掃描。
選定目標藍芽裝置
由介面卡獲取裝置列表,並根據PC的bluetooth MAC地址,選定目標藍芽裝置。SERVICE_ADDRESS為PC端藍芽mac地址,格式為XX:XX:XX:XX:XX:XX
Set<BluetoothDevice> mySet = bluetoothAdapter.getBondedDevices(); for (BluetoothDevice device : mySet) { if (device.getAddress().equalsIgnoreCase(SERVICE_ADDRESS) ) { service = device; break; } }
建立RfcommSocket並建立連線
private static final String serverUUID = "00001101-0000-1000-8000-00805F9B34FB"
private BluetoothSocket bluetoothSocket;
bluetoothSocket = service.createRfcommSocketToServiceRecord(UUID.fromString(serverUUID));
bluetoothSocket.connect();
這裡需要說明的有兩點。
1.連線操作比較耗時,視具體裝置不同。所以,需要將連線操作放到子執行緒中完成。
2.在Android端,需要使用UUID,完成與其他藍芽裝置的連線。關於UUID,之後小結會提到。
收發資料
收發資料也較為耗時,需要放在子執行緒實現。
OutputStream outputStream;
try {
outputStream = bluetoothSocket.getOutputStream();
outputStream.write("A message from android device".getBytes());
showMessage("Successfully send message");
} catch (IOException e) {
showMessage(e.getMessage()+", during output");
}
InputStream inputStream;
try {
inputStream = bluetoothSocket.getInputStream();
byte[] buffer = new byte[200];
inputStream.read(buffer);
showMessage("Concurrently receive message : " + new String(buffer));
} catch (IOException e) {
showMessage(e.getMessage()+", during get input");
}
關閉RfcommSocket
bluetoothSocket.close();
PC端
設定PC藍芽裝置可見
LocalDevice.getLocalDevice().setDiscoverable(DiscoveryAgent.GIAC);
建立連線流監聽器
private StreamConnectionNotifier streamConnectionNotifier;
streamConnectionNotifier = (StreamConnectionNotifier) Connector.open("btspp://localhost:" + SERVER_UUID.toString());
注意,此處使用的UUID,必須與android端的UUID一致。
開啟監聽
StreamConnection streamConnection = null;
streamConnection = streamConnectionNotifier.acceptAndOpen();
acceptAndOpen()方法呼叫後,將進入等待。
獲取輸入流和輸出流
while (isListening) {
if ((inputStream.available()) <= 0) {
Thread.sleep(1000);
}
System.out.println("message is comming");
outputStream.write("hello android BT".getBytes());
inputStream.read(buffer);
String message = new String(buffer);
System.out.println("Receive message : " + message);
if (message.contains("EXIT_APP")) {
System.out.println("Listener closed");
isListening = false;
}
}
關閉連線
inputStream.close();
outputStream.close();
streamConnection.close();
問題
BUG
java.io.IOException: read failed, socket might closed or timeout, read ret: -1
在連線時,常會遇到該BUG。網上很多方法說,可以通過修改UUID的方式,來FIX該BUG。
但翻看createRfcommSocketToServiceRecord方法的註釋,發現該UUID不能隨便修改。
* <p>Hint: If you are connecting to a Bluetooth serial board then try
* using the well-known SPP UUID 00001101-0000-1000-8000-00805F9B34FB.
* However if you are connecting to an Android peer then please generate
* your own unique UUID.
因此,若是採用串列埠通訊,必須使用“00001101-0000-1000-8000-00805F9B34FB”。
造成連線失敗的另一原因,可能是channel ID的問題。
在建立Socket時,createRfcommSocketToServiceRecord使用UUID作為唯一傳參,而預設channel為-1。
網上建議,通過反射使用BluetoothDevice的隱藏public createRfcommSocket方法,利用傳參指定的channel值,建立Socket。
channel的取值範圍為1至30.
Demo中給出了工具方法。
public BluetoothSocket cretateBluetoothSocketbyChannel(BluetoothDevice Device,int channel,boolean autoForward){
BluetoothSocket socket=null;
try {
showMessage("Trying fallback on channel "+channel);
socket =(BluetoothSocket) Device.getClass().getMethod("createRfcommSocket", new Class[] {int.class}).invoke(Device,channel);
socket.connect();
Log.d(TAG,"createRfcommSocket on channel "+channel);
showMessage("Successfully connect");
} catch (Exception e) {
showMessage(e.getMessage());
if(channel<30){
if(autoForward){
socket=cretateBluetoothSocketbyChannel(Device,channel+1,autoForward);
}else {
showMessage("Connect Failed");
}
}
}
return socket;
}
另外,就該問題,吐槽一下介面卡。介面卡的效能也是參差不齊。此前摁著綠聯的藍芽介面卡,試了3天。同樣的程式碼,問題百出。後來轉用奧視通(ost108),幾分鐘便過了。不管是新增裝置時的認證過程的人性化設計,還是裝置服務驅動的安裝速度,天壤之別~
驅動
Demo在實現時,遇到找不到裝置的情況。個人感覺是由於介面卡所提供的驅動問題造成的。在解除安裝後,使用通用驅動可以解決該問題。
Bluecove版本
在64位OS下開發,需要使用Bluecove 64bit版本,本Demo使用為64bit。下載地址
參考文獻
在開發過程中,以下文章給予了很多幫助,一併列下。
https://blog.csdn.net/tingfengzheshuo/article/details/45292201
http://royal2xiaose.iteye.com/blog/1420138
https://blog.csdn.net/old_me_mory/article/details/18962701
https://blog.csdn.net/peceoqicka/article/details/51979469(著重感謝)
結語
第一次涉及藍芽專案,耗時較長,且深入不夠,若有疏漏,還望提出。