前言:最近,新換了一家公司,公司的軟件需要通過藍牙與硬件進行通訊,於是趁此機會將Android藍牙詳細的了解了一下。本篇內容是基於普通藍牙。
Android系統已經為我們提供了操作藍牙的API,我們只要通過這些API便可以操控藍牙,實現打開藍牙設備,搜索周圍藍牙設備,與已連接的設備進行數據傳輸等操作。
閱讀本文后你將會有一下收獲
- 知道怎樣打開手機藍牙。
- 知道怎樣獲取已經進行藍牙配對過的設備。
- 知道怎樣進行設備之間的連接以及通訊。
- 知道怎樣設置藍牙設備可進行搜索到以及設置可被搜索的時長
一、藍牙操作
打開手機藍牙
設置藍牙權限
要在應用中使用藍牙功能,必須聲明藍牙權限 BLUETOOTH
。您需要此權限才能執行任何藍牙通信,例如請求連接、接受連接和傳輸數據等。設置權限的代碼如下
<uses-permission android:name="android.permission.BLUETOOTH" />
判斷是否支持藍牙
在打開手機藍牙之前首先判斷手機是否支持藍牙,判斷是否支持藍牙的代碼如下
BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if (mBluetoothAdapter == null) {
Toast.makeText(this,"當前設備不支持藍牙!",Toast.LENGTH_SHORT).show();
}
解釋一下BluetoothAdapter的作用
BluetoothAdapter代表本地設備藍牙適配器,BluetoothAdapter可讓您執行基本的藍牙任務,如啟動設備發現,查詢綁定(配對)設備列表,使用已知的MAC地址實例化 BluetoothDevice,並創建 BluetoothServerSocket以偵聽來自其他設備的連接請求,並開始掃描藍牙LE設備。
如果設備支持藍牙,則進行打開藍牙的操作
打開藍牙
調用 BluetoothAdapter的isEnabled()
方法來檢查當前是否已啟用藍牙。 如果此方法返回 false,則表示藍牙處於停用狀態。想要啟用藍牙,則需要設置Intent的Action為ACTION_REQUEST_ENABLE
,然后通過startActivityForResult()
來啟動藍牙。具體的代碼如下
if (!mBluetoothAdapter.isEnabled()) {
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
}
這段代碼執行完后,手機則會彈出是否允許開啟藍牙的提示框,如下圖
當用戶點擊“拒絕”或則“允許”的時候 Activity 將會在 onActivityResult()
回調中收到結果代碼。
當成功開啟藍牙時 Activity 將會在 onActivityResult()
回調中收到 RESULT_OK
結果代碼。 如果由於某個錯誤(或用戶響應“拒絕”)而沒有啟用藍牙,則結果代碼為 RESULT_CANCELED
。我們便可以重寫 onActivityResult()
方法來判斷藍牙是否已經成功開啟。
查詢設備
查詢已經配對的設備
在搜索設備之前,我們應該先查找已經進行配對的設備,如果目標設備已經進行過配對,則不需要進行設備搜索。因為,執行設備發現對於藍牙適配器而言是一個非常繁重的操作過程,並且會消耗大量資源。可以通過BluetoothAdapter的getBondedDevices()
方法來查詢已經配對的設備,具體代碼如下
private void checkAlreadyConnect() {
//獲取已經配對的集合
Set<BluetoothDevice> pairedDevices = mBluetoothAdapter.getBondedDevices();
if (pairedDevices.size() > 0) {
for (BluetoothDevice device : pairedDevices) {
mArrayAdapter.add(device.getName() + "\n" + device.getAddress());
}
mArrayAdapter.notifyDataSetChanged();
}
}
搜索設備
要搜索周圍的設備,只需調用BluetoothAdapter 的startDiscovery()
方法即可。
注:搜索設備是在異步進程中,通常會有12秒的時間來進行查詢掃描,之后對每台發現的設備進行頁面掃描,以檢索其藍牙名稱。
調用startDiscovery()
方法的時候還需要新增如下權限
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
在發現設備后系統會進行ACTION_FOUND
的廣播,因此,我們需要一個廣播接收者來接收廣播,以下代碼為發現設備后如何注冊來處理廣播
// 新建一個 BroadcastReceiver來接收ACTION_FOUND廣播
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
// 發現設備
if (BluetoothDevice.ACTION_FOUND.equals(action)) {
//獲得 BluetoothDevice
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
//向mArrayAdapter中添加設備信息
mSearchAdapter.add(device.getName() + "\n" + device.getAddress());
mSearchAdapter.notifyDataSetChanged();
}
}
};
//設置IntentFilter
IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
注意:執行設備發現對於藍牙適配器而言是一個非常繁重的操作過程,並且會消耗大量資源。 在找到要連接的設備后,確保始終使用
cancelDiscovery()
停止發現,然后再嘗試連接。不應該在處於連接狀態時執行發現操作。
啟用可檢測性
Android 設備默認處於不可檢測到狀態。 用戶可通過系統設置將設備設為在有限的時間內處於可檢測到狀態,或者,應用可請求用戶在不離開應用的同時啟用可檢測性。
如果您希望將本地設備設為可被其他設備檢測到,請使用 ACTION_REQUEST_DISCOVERABLE
操作 Intent 調用 startActivityForResult(Intent, int)
。 這將通過系統設置發出啟用可檢測到模式的請求(無需停止您的應用)。 默認情況下,設備將變為可檢測到並持續 120 秒鍾。 您可以通過添加EXTRA_DISCOVERABLE_DURATION
Intent Extra 來定義不同的持續時間。 應用可以設置的最大持續時間為 3600 秒,值為 0 則表示設備始終可檢測到。 任何小於 0 或大於 3600 的值都會自動設為 120 秒。 例如,以下片段會將持續時間設為 300 秒:
Intent discoverableIntent = new
Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300);
startActivity(discoverableIntent);
進行設備連接
引用官方的文檔
要在兩台設備上的應用之間創建連接,必須同時實現服務器端和客戶端機制,因為其中一台設備必須開放服務器套接字,而另一台設備必須發起連接(使用服務器設備的 MAC 地址發起連接)。 當服務器和客戶端在同一 RFCOMM 通道上分別擁有已連接的
BluetoothSocket
時,二者將被視為彼此連接。
由上面的引用可知,要想創建兩個應用之間的連接,必須同時實現服務器端和客戶端機制,下面,分別介紹怎樣實現為服務器端和客戶端機制。
實現為服務器
當您需要連接兩台設備時,其中一台設備必須通過保持開放的
BluetoothServerSocket
來充當服務器。 服務器套接字的用途是偵聽傳入的連接請求,並在接受一個請求后提供已連接的BluetoothSocket
。 從BluetoothServerSocket
獲取BluetoothSocket
后,可以(並且應該)舍棄BluetoothServerSocket
,除非您需要接受更多連接。
下面是作為服務端的代碼
private class AcceptThread extends Thread {
private final BluetoothServerSocket mmServerSocket;
public AcceptThread() {
BluetoothServerSocket tmp = null;
try {
tmp = mAdapter.listenUsingRfcommWithServiceRecord(NAME,
MY_UUID);
} catch (IOException e) {
}
mmServerSocket = tmp;
mState = STATE_LISTEN;
}
public void run() {
setName("AcceptThread");
BluetoothSocket socket = null;
while (mState != STATE_CONNECTED) {
try {
socket = mmServerSocket.accept();
} catch (IOException e) {
break;
}
if (socket != null) {
synchronized (BluetoothService.this) {
switch (mState) {
case STATE_LISTEN:
case STATE_CONNECTING:
connected(socket, socket.getRemoteDevice());
break;
case STATE_NONE:
case STATE_CONNECTED:
try {
socket.close();
} catch (IOException e) {
}
break;
}
}
}
}
}
public void cancel() {
try {
mmServerSocket.close();
} catch (IOException e) {
}
}
}
這里解釋一下,為什么要新開一個線程來作為服務端,因為通過調用 accept()
這是一個阻塞調用。
注意:mAdapter.listenUsingRfcommWithServiceRecord(NAME,MY_UUID);中的MY_UUID必須與客服端連接時的UUID一致。
總結一下作為服務端的步驟:
- 通過調用
listenUsingRfcommWithServiceRecord(String, UUID)
獲取BluetoothServerSocket
。 - 通過調用
accept()
開始偵聽連接請求。 - 如果不想讓更多的設備連接,則在連接后調用
close()
關閉。
實現為客戶端
先看下作為客戶端的代碼
private class ConnectThread extends Thread {
private final BluetoothSocket mmSocket;
private final BluetoothDevice mmDevice;
public ConnectThread(BluetoothDevice device) {
mmDevice = device;
BluetoothSocket tmp = null;
try {
tmp = device.createRfcommSocketToServiceRecord(
MY_UUID);
} catch (IOException e) {
}
mmSocket = tmp;
mState = STATE_CONNECTING;
}
public void run() {
setName("ConnectThread");
mAdapter.cancelDiscovery();
try {
mmSocket.connect();
} catch (IOException e) {
try {
mmSocket.close();
} catch (IOException e2) {
}
return;
}
synchronized (BluetoothService.this) {
mConnectThread = null;
}
connected(mmSocket, mmDevice);
}
public void cancel() {
try {
mmSocket.close();
} catch (IOException e) {
}
}
}
這里有一點需要注意,也是官方文檔中強調的
注:在調用
connect()
時,應始終確保設備未在執行設備發現。 如果正在進行發現操作,則會大幅降低連接嘗試的速度,並增加連接失敗的可能性。
因此,在進行連接之前必須調用 mAdapter.cancelDiscovery();
來關閉查找設備。這里總結一下作為客戶端的步驟:
- 使用
BluetoothDevice
,通過調用createRfcommSocketToServiceRecord(UUID)
獲取BluetoothSocket
。(這里的UUID必須與服務端的保持一致) - 通過調用
connect()
發起連接。
如果兩台設備之前尚未配對,則在連接過程中,Android 框架會自動向用戶顯示配對請求通知或對話框,如下圖所示。
進行配對之后就可以進行通信及數據的傳輸了。
數據傳輸
通過上面的幾步,這時已經可以實現設備之間的連接了。下面說一下設備之間的通信。
其實在兩台設備連接成功后,每台設備都會有一個已連接的 BluetoothSocket
。我們則可以利用 BluetoothSocket
,來進行數據的傳輸。看代碼
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;
// 獲取BluetoothSocket的input and output streams
try {
tmpIn = socket.getInputStream();
tmpOut = socket.getOutputStream();
} catch (IOException e) {
Log.e(TAG, "temp sockets not created", e);
}
mmInStream = tmpIn;
mmOutStream = tmpOut;
mState = STATE_CONNECTED;
}
public void run() {
Log.i(TAG, "BEGIN mConnectedThread");
byte[] buffer = new byte[1024];
int bytes;
while (mState == STATE_CONNECTED) {
try {
bytes = mmInStream.read(buffer);
Log.d(TAG, "已經連接");
mHandler.obtainMessage(Constants.MESSAGE_READ, bytes, -1, buffer)
.sendToTarget();
} catch (IOException e) {
Log.e(TAG, "disconnected", e);
break;
}
}
}
//寫數據
public void write(byte[] buffer) {
try {
mmOutStream.write(buffer);
//發送消息到主線程
mHandler.obtainMessage(Constants.MESSAGE_WRITE, -1, -1, buffer)
.sendToTarget();
} catch (IOException e) {
Log.e(TAG, "Exception during write", e);
}
}
public void cancel() {
try {
mmSocket.close();
} catch (IOException e) {
Log.e(TAG, "close() of connect socket failed", e);
}
}
}
從上面代碼中可以看到,首先是通過BluetoothSocket來拿到InputStream和OutputStream,然后利用read
,write
方法來讀取和寫入數據。
二、項目結構
三、結束語
本文的內容是基於普通藍牙進行描述的,主要講解了怎樣操作藍牙及進行設備間的通訊。不過現在好多都是在BLE藍牙設備間進行通訊了,當然,我也會針對BLE藍牙設備在寫一篇文章,本文就是為后面的BLE藍牙講解做准備的。
轉載請注明出處:www.wizardev.com
Android藍牙
注:本文著作權歸作者,由demo大師代發,拒絕轉載,轉載需要作者授權