最近項目上須要藍牙通信功能,因此本身私下裏學習了一下藍牙通信相關的知識。一直覺得藍牙通信是調用系統層的API實現的,好比咱們平時用的利用藍牙傳照片和文件,咱們只須要選擇一下接受設備就能夠了,可是本身實現藍牙遠程通訊以後,才明白其中的一些細節問題,有些流程仍是須要咱們本身去作的。
一、首先是藍牙通訊機制android
藍牙通訊也是採用Socket機制,通訊雙方有一方爲服務器端,另外一方爲客戶端,可能有服務器
人會以爲通訊雙方都同樣的android設備,怎麼還有服務器端和客戶端之分呢,哪一方應該是服務器端,哪一方應該是客戶端呢?答案是主動發起通訊請求的一方爲客戶端,另外一方天然爲服務器端了。架構
二、藍牙通訊的工做流程app
2.1 服務端先創建一個服務端套接字Socket,而後該套接字開始監聽客戶端的鏈接;socket
2.2 客戶端也創建一個socket,而後向服務端發起鏈接,這時候若是沒有異常就算兩個設備鏈接成功了;ide
2.3 這時候客戶端和服務端都會持有一個Socket,利用該Socket能夠發送和接收消息。學習
上面大概介紹了藍牙通訊的工做流程,下面該講點乾貨了。
一、藍牙通訊涉及到的類ui
BluetoothAdapterthis
該類是提供藍牙服務的接口,普通的用戶app能夠利用該類使用系統的藍牙服務,若是咱們想要獲取該類的對象,用以下語句:.net
BluetoothAdapterbluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
該類比較常見的API有:
IsEnabled(),該方法判斷藍牙是否打開,若是藍牙已經打開,該方法返回true,不然返回false。
若是藍牙沒有打開,咱們想要打開藍牙,有兩種方案,一種是跳轉到系統藍牙設置頁面
Intent intent = new Intent(Settings.ACTION_BLUETOOTH_SETTINGS);
startActivity(intent);
另外一種是直接在咱們的應用裏面打開,這裏也有兩種方法,一種是給用戶提示的,用戶能夠選擇是否打開藍牙,用戶體驗要好一些,寫法以下:
Intent enabler = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivity(enabler);
另外一種方法是直接打開藍牙,不讓用戶選擇,比較簡單粗暴哈,寫法以下:
bluetoothAdapter.enable()
enable()方法是有返回值的,若是打開成功,返回true,不然返回false,能夠根據返回值判斷是否打開成功,不過這種方法須要Manifest.permission.BLUETOOTH_ADMIN權限。
listenUsingRfcommWithServiceRecord(Stringname, UUID uuid)方法,該方法是建立一個BluetoothServerSocket對像。
getBondedDevices(),該方法返回與該設備已經配對完成的遠程設備的BluetoothDevice集合。
還有些方法是用於藍牙搜索、配對的API,若是隻是作藍牙通訊,這些API用不上,本文這裏先不寫了。
BluetoothDevice
這個類包含了藍牙設備的一些信息,好比mac地址、名稱和類型等,咱們在藍牙設置界面常常會看到搜索到的藍牙設備信息的前面都有一個圖標,這個圖標有手機、耳機、電腦等,這個圖標就是根據BluetoothDevice對象的類型信息來區別的。這個類中還有一個很重要的API,正是用這個API來建立客戶端的BluetoothSocket.
createRfcommSocketToServiceRecord(UUIDuuid)根據UUID建立並返回一個BluetoothSocket.
BluetoothServerSocket
這個類一種只有三個方法,兩個重載的accept(),accept(inttimeout)二者的區別在於後面的方法指定了過期時間,須要注意的是,執行這兩個方法的時候,直到接收到了客戶端的請求(或是過時以後),都會阻塞線程,應該放在新線程裏運行!這兩個方法都會返回BluetoothSocket對象,即服務端的Socket對象。
BluetoothSocket
藍牙通訊服務端和客戶端都持有一個該類對象,該類的主要API有:
close(),關閉
connect()鏈接
getInptuStream()獲取輸入流
getOutputStream()獲取輸出流
getRemoteDevice()獲取遠程設備,這裏指的是獲取bluetoothSocket指定鏈接的那個遠程藍牙設備
上面是類的講述,下面講一下這些類在藍牙通訊中是怎麼運用的。藍牙通訊主要涉及到兩個環節:通訊鏈接的創建、發送數據和接收數據。
二、具體用法
2.1 通訊鏈接的創建
要想創建藍牙通訊,根據上面的流程圖,要先創建服務器,而後客戶端去請求鏈接。
服務器端創建套接字,等待客戶端鏈接,調用BluetoothAdapter的listenUsingRfcommWithServiceRecord方法,產生一個BluetoothServerSocket對象,而後調用BluetoothServerSocket對象的accept方法,注意accept方法會產生阻塞,直到一個客戶端鏈接創建,因此服務器端的socket的創建須要在一個子線程中去作,代碼以下:
public class ServerThread extends Thread
{
@Override
public void run() {
super.run();
UUID uuid = UUID.fromString(SPP_UUID);
try {
BluetoothServerSocket serverSocket = bluetoothAdapter. listenUsingRfcommWithServiceRecord(SERVER_SOCKET_NAME,uuid);
mSocket = serverSocket.accept();
Log.d(TAG,"server create success");
//鏈接創建以後即啓動一個接收數據線程,該邏輯屬於自定義業務,不是強制性流程,接收數據的時間點能夠根據本身的業務邏輯去定。
new ReadThread().start();
} catch (IOException e) {
e.printStackTrace();
}
}
}
客戶端去鏈接服務器端,須要先持有服務器端的BluetoothDevice對象,先調用BluetoothDevice的createRfcommSocketToServiceRecord方法,這個方法會產生一個客戶端的BluetoothSocket對象,而後調用該對象的connect方法,該過程最好也是單獨起一個線程去作,代碼以下:
private class ClientThread extends Thread
{
@Override
public void run() {
super.run();
UUID uuid = UUID.fromString(SPP_UUID);
try {
mSocket = bondedDevice.createRfcommSocketToServiceRecord(uuid);
mSocket.connect();
Log.d(TAG,"connect server success");
new ReadThread().start();
} catch (IOException e) {
e.printStackTrace();
}
}
}
2.2 發送數據和接收數據
發送數據和接收數據對於服務端和客戶端來說都是同樣的,使用方法也同樣,由於使用的都是經過一個類。發送數據須要調用BluetoothSocket的getOutputStream(),接收數據須要調用getInputStream()方法。具體代碼以下:
// 讀取數據
private class ReadThread extends Thread {
public void run() {
byte[] buffer = new byte[1024];
int bytes;
InputStream is = null;
try {
is = mSocket.getInputStream();
while (true) {
if ((bytes = is.read(buffer)) > 0) {
byte[] buf_data = new byte[bytes];
for (int i = 0; i < bytes; i++) {
buf_data[i] = buffer[i];
}
}
} catch (IOException e1) {
e1.printStackTrace();
} finally {
try {
is.close();
} catch (IOException e1) {
e1.printStackTrace();
}
}
}
}
private class WriteThread extends Thread
{
private String msg;
public WriteThread(String msg)
{
this.msg = msg;
}
@Override
public void run() {
super.run();
if (mSocket == null) {
Toast.makeText(BluetoothActivity.this, "沒有鏈接", Toast.LENGTH_SHORT).show();
return;
}
try {
OutputStream os = mSocket.getOutputStream();
os.write(msg.getBytes());
Log.d(TAG,"send over");
} catch (IOException e) {
e.printStackTrace();
}
}
}
上面只是本人關於藍牙通訊的一些經驗,若是應用到項目裏面還須要好好設計一下架構,有許多細節問題須要根據本身的業務去處理,好比通訊異常的處理、通訊方式(是先發再讀仍是邊發邊讀)、各工做線程什麼時候結束等。有問題歡迎留言,一塊兒討論。