Android 藍牙連接通訊實作心得
最近因為專案需求,進行了一些藍牙通訊的研究。
首先來看官方線上文件:Bluetooth overview - Connect devices
前面描述開啟藍牙或搜尋、被搜尋……等較為基本的部份先跳過。從連接裝置(Connect devices)這一節開始,首先要將兩個裝置配對,然後使用 BluetoothAdapter 的 getBondedDevices() 方法取得已配對的裝置資訊,並從中取出 MAC address。
接下來我們需要一組 UUID (通用唯一識別號)作為藍牙服務的識別碼。在網路上有許多 UUID 產生器,例如:https://www.uuidgenerator.net/。找一個喜歡的使用即可。
因為藍牙的連接模式是由一個 Client 端主動發起連線給被動監聽的 Server 端,因此程式碼需要分別撰寫 Server 與 Client 端的動作。
首先來看接收藍牙連接的 Server 端程式碼:
接受連接的步驟為:
然後是藍牙連接的 Client 端程式碼:
發起連接的步驟為:
到這邊可以發現到,Client 和 Server 都在第三步驟調用了 manageConnectedSocket(socket) 副程式,但是官方線上文件並沒有進一步說明這個副程式的內容。
所幸我在官方範例程式中找到了藍牙連接的範例程式:BluetoothChat
從中得知,原來是要啟動另一個 Thread:ConnectedThread
而 ConnectedThread 的程式碼如下:
ConnectedThread 透過 BluetoothSocket 取得了 InputStream 和 OutputStream 來處理數據傳輸,然後再透過 read(byte[]) 和 write(byte[]) 方法來讀取和寫入數據。
之後只要在 Activity(或 Fragment)中宣告一個 Handler,就可以用來接收對方傳來的數據並更新 UI;也能直接調用 ConnectedThread 的 write(byte[]) 來寫入數據給對方。
最後附上一張傳輸成功的截圖
首先來看官方線上文件:Bluetooth overview - Connect devices
前面描述開啟藍牙或搜尋、被搜尋……等較為基本的部份先跳過。從連接裝置(Connect devices)這一節開始,首先要將兩個裝置配對,然後使用 BluetoothAdapter 的 getBondedDevices() 方法取得已配對的裝置資訊,並從中取出 MAC address。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Set<BluetoothDevice> pairedDevices = mBluetoothAdapter.getBondedDevices(); | |
// If there are paired devices | |
if (pairedDevices.size() > 0) { | |
// Loop through paired devices | |
for (BluetoothDevice device : pairedDevices) { | |
// Add the name and address to an array adapter to show in a ListView | |
mArrayAdapter.add(device.getName() + "\n" + device.getAddress()); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
private final String NAME = "MyBTService"; | |
private final UUID MY_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB"); |
首先來看接收藍牙連接的 Server 端程式碼:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
private class AcceptThread extends Thread { | |
private final BluetoothServerSocket mmServerSocket; | |
public AcceptThread() { | |
// Use a temporary object that is later assigned to mmServerSocket | |
// because mmServerSocket is final. | |
BluetoothServerSocket tmp = null; | |
try { | |
// MY_UUID is the app's UUID string, also used by the client code. | |
tmp = mBluetoothAdapter.listenUsingRfcommWithServiceRecord(NAME, MY_UUID); | |
} catch (IOException e) { | |
Log.e(TAG, "Socket's listen() method failed", e); | |
} | |
mmServerSocket = tmp; | |
} | |
public void run() { | |
BluetoothSocket socket = null; | |
// Keep listening until exception occurs or a socket is returned. | |
while (true) { | |
try { | |
socket = mmServerSocket.accept(); | |
} catch (IOException e) { | |
Log.e(TAG, "Socket's accept() method failed", e); | |
break; | |
} | |
if (socket != null) { | |
// A connection was accepted. Perform work associated with | |
// the connection in a separate thread. | |
manageMyConnectedSocket(socket); | |
mmServerSocket.close(); | |
break; | |
} | |
} | |
} | |
// Closes the connect socket and causes the thread to finish. | |
public void cancel() { | |
try { | |
mmServerSocket.close(); | |
} catch (IOException e) { | |
Log.e(TAG, "Could not close the connect socket", e); | |
} | |
} | |
} |
- 調用 listenUsingRfcommWithServiceRecord(String, UUID) 取得 BluetoothServerSocket
- 調用 accept() 開始監聽連線請求
- 在 manageConnectedSocket(socket) 副程式中管理 socket 連線。這部份後面再詳述。
- 調用 close() 關閉監聽連線請求
然後是藍牙連接的 Client 端程式碼:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
private class ConnectThread extends Thread { | |
private final BluetoothSocket mmSocket; | |
private final BluetoothDevice mmDevice; | |
public ConnectThread(BluetoothDevice device) { | |
// Use a temporary object that is later assigned to mmSocket | |
// because mmSocket is final. | |
BluetoothSocket tmp = null; | |
mmDevice = device; | |
try { | |
// Get a BluetoothSocket to connect with the given BluetoothDevice. | |
// MY_UUID is the app's UUID string, also used in the server code. | |
tmp = device.createRfcommSocketToServiceRecord(MY_UUID); | |
} catch (IOException e) { | |
Log.e(TAG, "Socket's create() method failed", e); | |
} | |
mmSocket = tmp; | |
} | |
public void run() { | |
// Cancel discovery because it otherwise slows down the connection. | |
mBluetoothAdapter.cancelDiscovery(); | |
try { | |
// Connect to the remote device through the socket. This call blocks | |
// until it succeeds or throws an exception. | |
mmSocket.connect(); | |
} catch (IOException connectException) { | |
// Unable to connect; close the socket and return. | |
try { | |
mmSocket.close(); | |
} catch (IOException closeException) { | |
Log.e(TAG, "Could not close the client socket", closeException); | |
} | |
return; | |
} | |
// The connection attempt succeeded. Perform work associated with | |
// the connection in a separate thread. | |
manageMyConnectedSocket(mmSocket); | |
} | |
// Closes the client socket and causes the thread to finish. | |
public void cancel() { | |
try { | |
mmSocket.close(); | |
} catch (IOException e) { | |
Log.e(TAG, "Could not close the client socket", e); | |
} | |
} | |
} |
- 調用 createRfcommSocketToServiceRecord(UUID) 取得 BluetoothSocket。
- 調用 connect() 發起連接
- 在 manageConnectedSocket(socket) 副程式中管理 socket 連線。
到這邊可以發現到,Client 和 Server 都在第三步驟調用了 manageConnectedSocket(socket) 副程式,但是官方線上文件並沒有進一步說明這個副程式的內容。
所幸我在官方範例程式中找到了藍牙連接的範例程式:BluetoothChat
從中得知,原來是要啟動另一個 Thread:ConnectedThread
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
private void manageConnectedSocket(BluetoothSocket socket) { | |
connectedThread = new ConnectedThread(socket); | |
connectedThread.start(); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
private class ConnectedThread extends Thread { | |
private final BluetoothSocket mmSocket; | |
private final InputStream mmInStream; | |
private final OutputStream mmOutStream; | |
public ConnectedThread(BluetoothSocket socket) { | |
mmSocket = socket; | |
InputStream tmpIn = null; | |
OutputStream tmpOut = null; | |
// Get the input and output streams, using temp objects because | |
// member streams are final | |
try { | |
tmpIn = socket.getInputStream(); | |
tmpOut = socket.getOutputStream(); | |
} catch (IOException e) { } | |
mmInStream = tmpIn; | |
mmOutStream = tmpOut; | |
} | |
public void run() { | |
byte[] buffer = new byte[1024]; // buffer store for the stream | |
int bytes; // bytes returned from read() | |
// Keep listening to the InputStream until an exception occurs | |
while (true) { | |
try { | |
// Read from the InputStream | |
bytes = mmInStream.read(buffer); | |
// Send the obtained bytes to the UI activity | |
mHandler.obtainMessage(MESSAGE_READ, bytes, -1, buffer) | |
.sendToTarget(); | |
} catch (IOException e) { | |
break; | |
} | |
} | |
} | |
/* Call this from the main activity to send data to the remote device */ | |
public void write(byte[] bytes) { | |
try { | |
mmOutStream.write(bytes); | |
} catch (IOException e) { } | |
} | |
/* Call this from the main activity to shutdown the connection */ | |
public void cancel() { | |
try { | |
mmSocket.close(); | |
} catch (IOException e) { } | |
} | |
} |
之後只要在 Activity(或 Fragment)中宣告一個 Handler,就可以用來接收對方傳來的數據並更新 UI;也能直接調用 ConnectedThread 的 write(byte[]) 來寫入數據給對方。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
private class UIHandler extends Handler { | |
@Override | |
public void handleMessage(Message msg) { | |
super.handleMessage(msg); | |
switch (msg.what) { | |
case MESSAGE_READ: | |
byte[] writeBuf = (byte[]) msg.obj; | |
String writeMessage = new String(writeBuf); | |
msgList.add(writeMessage); | |
break; | |
default: | |
break; | |
} | |
} | |
} |
留言
張貼留言