Android藍芽開發流程實踐
概述
工作需要用到Android藍芽開發,所以在這裡對Android藍芽開發做一個整理。
先了解下Android藍芽開發的基礎知識:ofollow,noindex">官方文件戳這裡
我們需要知道,現在的藍芽分為經典藍芽和BLE(低功耗藍芽)兩種,經典藍芽和低功耗藍芽的區別戳這裡 ,經典藍芽適合傳輸資料量比較大的傳輸,比如影象、視訊、音樂等,耗電也大;BLE適合耗電小,實時性要求高和資料量小的傳輸,比如智慧穿戴裝置、遙控類、滑鼠、鍵盤、感測裝置如心跳帶,血壓計,溫度感測器等。
對於Android開發者來說,我們要知道 Android 4.3 及以上版本才支援BLE,常說的藍芽單模和雙模指的是僅支援BLE和同時支援BLE和經典藍芽,經典藍芽和BLE之間不能通訊 ,Android手機同時支援經典藍芽和BLE,但是掃描藍芽的時候,只能掃描其中一種,如果是Android手機跟其他裝置通過藍芽通訊,首先要確認裝置支援的藍芽協議。下面記錄的是經典藍芽開發步驟。
藍芽開發步驟
通常Android藍芽開發包含以下5個步驟:
- 開啟
- 掃描
- 配對
- 連線
- 通訊
開啟藍芽
- 許可權(需要藍芽和GPS許可權,Android 6.0以上要加上執行時許可權授權)
在 AndroidManifest 中宣告許可權:
<uses-permission android:name="android.permission.BLUETOOTH" /> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
獲取定位授權:
//要模糊定位許可權才能搜尋到藍芽 PermissionUtil.requestEach(this, new PermissionUtil.OnPermissionListener() { @Override public void onSucceed() { //授權成功後開啟藍芽 openBlueTooth(); } @Override public void onFailed(boolean showAgain) { } }, PermissionUtil.LOCATION);
- 判斷裝置是否支援藍芽
BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); if (mBluetoothAdapter == null) { //如果mBluetoothAdapter為null,該裝置不支援藍芽,不過基本上都支援的 }
- 判斷藍芽是否開啟,沒有就開啟
if (!mBluetoothAdapter.isEnabled()) { //若沒開啟則開啟藍芽 mBluetoothAdapter.enable(); }
掃描藍芽
- 可以掃描有指定 UUID 服務的藍芽裝置,UUID 不是裝置的標識,而是某個服務的標識,什麼是UUID戳這裡
- 可以掃描全部藍芽裝置
- 注意:藍芽裝置被某裝置(包括當前的裝置)配對/連線後,可能不會再被掃描到
//掃描:經典藍芽 mBluetoothAdapter.startDiscovery(); //掃描:低功耗藍芽,需要加上停止掃描規則,掃描到指定裝置或者一段時間後,這裡設定10秒後停止掃描 new Handler().postDelayed(new Runnable() { @Override public void run() { mBluetoothAdapter.stopLeScan(MainActivity.this); Log.i(TAG, "=== 停止掃描了 === "); } }, SCAN_PERIOD); mBluetoothAdapter.startLeScan(this);
通過廣播來監聽掃描結果:
//廣播接收器 BroadcastReceiver receiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); switch (action) { case BluetoothAdapter.ACTION_DISCOVERY_STARTED: //掃描開始 break; case BluetoothDevice.ACTION_FOUND: //發現藍芽 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); break; case BluetoothAdapter.ACTION_DISCOVERY_FINISHED: //掃描結束 break; } } };
配對藍芽
- 配對與連線之間的區別:配對意味著兩個裝置之間知道彼此的存在,通過配對金鑰,建立一個加密的連線。而連線意味著裝置之間共享一個通訊通道(UUID),能夠彼此傳輸資料
/** * 藍芽配對,配對結果通過廣播返回 * @param device */ public void pin(BluetoothDevice device) { if (device == null || !mBluetoothAdapter.isEnabled()) { return; } //配對之前把掃描關閉 if (mBluetoothAdapter.isDiscovering()) { mBluetoothAdapter.cancelDiscovery(); } //判斷裝置是否配對,沒有就進行配對 if (device.getBondState() == BluetoothDevice.BOND_NONE) { try { Method createBondMethod = device.getClass().getMethod("createBond"); Boolean returnValue = (Boolean) createBondMethod.invoke(device); returnValue.booleanValue(); } catch (Exception e) { e.printStackTrace(); } } }
廣播監聽配對結果:
case BluetoothDevice.ACTION_BOND_STATE_CHANGED: //配對狀態變化廣播 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice .EXTRA_DEVICE); switch (device.getBondState()) { case BluetoothDevice.BOND_NONE: Log.i(TAG, "--- 配對失敗 ---"); break; case BluetoothDevice.BOND_BONDING: Log.i(TAG, "--- 配對中... ---"); break; case BluetoothDevice.BOND_BONDED: Log.i(TAG, "--- 配對成功 ---"); break; } break;
連線藍芽
- 配對成功之後,兩臺裝置之間建立連線,一臺充當client的角色,另一臺充當server的角色,由client發起連線;
- 通過 UUID 建立 BluetoothSocket 進行連線,兩個端的UUID要一致,client和server都自己開發的話,可以由服務端建立一個UUID
- 發起連線和監聽連線都要在子執行緒中執行
在client端的子執行緒中發起連線:
/** * * 發起藍芽連線的執行緒 * 作者: 程式碼來自於Google官方 -> API指南 -> 藍芽模組 * 日期: 18/12/14 */ public class ConnectThread extends Thread { private final BluetoothSocket mmSocket; private final BluetoothDevice mmDevice; private final BluetoothAdapter mBluetoothAdapter; private ConnectCallBack callBack; public ConnectThread(BluetoothDevice device, BluetoothAdapter bluetoothAdapter, ConnectCallBack callBack) { // Use a temporary object that is later assigned to mmSocket, // because mmSocket is final BluetoothSocket tmp = null; mmDevice = device; mBluetoothAdapter = bluetoothAdapter; this.callBack = callBack; // Get a BluetoothSocket to connect with the given BluetoothDevice try { // MY_UUID is the app's UUID string, also used by the server code tmp = device.createRfcommSocketToServiceRecord(UUID.fromString(ServerActivity.uuidStr)); } catch (IOException e) { } mmSocket = tmp; } public void run() { // Cancel discovery because it will slow down the connection mBluetoothAdapter.cancelDiscovery(); try { // Connect the device through the socket. This will block // until it succeeds or throws an exception mmSocket.connect(); } catch (IOException connectException) { // Unable to connect; close the socket and get out try { mmSocket.close(); } catch (IOException closeException) { } return; } // Do work to manage the connection (in a separate thread) //manageConnectedSocket(mmSocket); //啟動資料傳輸的執行緒 if(callBack != null) { callBack.onConnectSucceed(mmSocket); } } /** Will cancel an in-progress connection, and close the socket */ public void cancel() { try { mmSocket.close(); } catch (IOException e) { } } public interface ConnectCallBack { public void onConnectSucceed(BluetoothSocket serverSocket); } }
在server端的子執行緒中監聽連線:
/** * * 監聽藍芽連線執行緒 * 作者: 程式碼來自於Google官方 -> API指南 -> 藍芽模組 * 日期: 18/12/14 */ public class AcceptThread extends Thread { private static final String TAG = "BluetoothDemo"; private final BluetoothServerSocket mmServerSocket; private AcceptCallBack callBack; public AcceptThread(BluetoothAdapter bluetoothAdapter, AcceptCallBack callBack) { this.callBack = callBack; // 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 = bluetoothAdapter.listenUsingRfcommWithServiceRecord("bluetoothdemo", UUID .fromString(ServerActivity.uuidStr)); } catch (IOException e) { } mmServerSocket = tmp; } public void run() { BluetoothSocket socket = null; // Keep listening until exception occurs or a socket is returned while (true) { Log.i(TAG, "AcceptThread監聽中..."); try { socket = mmServerSocket.accept(); } catch (IOException e) { break; } // If a connection was accepted if (socket != null) { try { // Do work to manage the connection (in a separate thread) //manageConnectedSocket(socket); //啟動資料傳輸的執行緒 if(callBack != null) { callBack.onAcceptSucceed(socket); } Log.i(TAG, "AcceptThread連線成功"); mmServerSocket.close(); } catch (IOException e) { e.printStackTrace(); } break; } } } /** * Will cancel the listening socket, and cause the thread to finish */ public void cancel() { try { mmServerSocket.close(); } catch (IOException e) { } } public interface AcceptCallBack { public void onAcceptSucceed(BluetoothSocket serverSocket); } }
通訊
- 連線成功後,通過socket得到I/O流,讀取資料和寫入資料,在兩個裝置之間傳輸資料。
/** * 傳送和接收資料 * 作者: 程式碼來自於Google官方 -> API指南 -> 藍芽模組 * 日期: 18/12/14 */ public class ConnectedThread extends Thread { private static final String TAG = "ConnectedThread"; private final BluetoothSocket mmSocket; private final InputStream mmInStream; private final OutputStream mmOutStream; private Handler mHandler; public ConnectedThread(BluetoothSocket socket, Handler handler) { mmSocket = socket; InputStream tmpIn = null; OutputStream tmpOut = null; mHandler = handler; // 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 if(mHandler != null) { mHandler.obtainMessage(ServerActivity.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) { } } }
ConnectThread、AcceptThread 和 ConnectedThread 都是Google官方文件裡面的示例,我自己加了些回撥方法而已,如果覺得比較亂,可以直接去看官方文件。官方文件戳這裡
程式碼上傳到了GitHub/">GitHub
, 功能比較簡單,兩臺手機一個充當client,一個充當server,嚴格按照開啟 >> 掃描 >> 配對 >> 連線 >> 通訊
5個步驟走才能實現通訊。僅供參考。