關於藍牙通信的開發
-
權限問題:
-
需要權限如下:
<uses-permission android:name="android.permission.BLUETOOTH"/> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
-
並且需要在代碼中動態進行申請(萬惡的權限限制a)
-
if (Build.VERSION.SDK_INT >= 23) { if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED){ ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.ACCESS_COARSE_LOCATION}, 10); } }
-
-
打開藍牙
-
打開藍牙主要使用
Intent
進行申請Toast.makeText(context, "藍牙未打開", Toast.LENGTH_SHORT).show(); Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);//請求打開藍牙 context.startActivity(intent);
當調用上面申請後,系統會彈出是否打開藍牙。選擇是就可以了。
-
-
查找附近已匹配或可用設備
這一步驟分爲兩類:一是已匹配的設備,而是可用設備。
-
已匹配設備:以前跟當前藍牙設備已經做過了匹配操作,當前藍牙設備記住了其藍牙信息,包括地址MAC、Name等等
/** * 獲取已經配對的藍牙信息 * adapter是藍牙的適配器BluetoothAdapter, 代表本地藍牙適配器(藍牙無線電)。BluetoothAdapter是所有藍牙交互的入口。使用這個你可以發現其他藍牙設備,查詢已配對的設備列表,使用一個已知的MAC地址來實例化一個BluetoothDevice,以及創建一個BluetoothServerSocket來爲監聽與其他設備的通信。 * 獲取方式爲 BluetoothAdapter.getDefaultAdapter() */ public ArrayList<String> getAdaptedBlueTooth() { Toast.makeText(context, "開始獲取", Toast.LENGTH_SHORT).show(); bluetoothList = new ArrayList<>(); if (adapter != null) {//是否有藍牙設備 if (adapter.isEnabled()) {//藍牙設備是否可用 Set<BluetoothDevice> devices = adapter.getBondedDevices();//獲取到已經匹配的藍牙對象 if (devices.size() > 0) { for (Iterator iterator = devices.iterator(); iterator.hasNext(); ) { BluetoothDevice device = (BluetoothDevice) iterator.next(); bluetoothList.add(device.getAddress() + "設備名:" + device.getName()); } return bluetoothList; } else { Toast.makeText(context, "沒有綁定的藍牙設備", Toast.LENGTH_SHORT).show(); searchForAvailableBluetooth(); } } else { Toast.makeText(context, "藍牙未打開", Toast.LENGTH_SHORT).show(); Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);//請求打開藍牙 context.startActivity(intent); } } else { Toast.makeText(context, "沒有發現藍牙", Toast.LENGTH_SHORT).show(); } return null; }
-
附近可用藍牙設備:發現附近可用藍牙設備,在Android中發現設備是一個過程,通過註冊receiver對響應的信息進行獲取。只有在附近藍牙設備是可被檢測的狀態下才會迴應發現請求,分享出自身的一些屬性值,如:設備名稱,類,MAC地址等
//首先第一步是創建一個接收回應信息的廣播,並且對信息進行相應的處理 //第二步,註冊廣播,並且開啓掃描,開啓掃描只需要調用startDiscovery(),成功返回true,失敗返回false。 //第三步,記得關閉掃描後,也要把廣播註冊掉 /** * 創建廣播,接收掃描到的藍牙信息 */ private class BluetoothReceiver extends BroadcastReceiver { /** * 這個receive每一次掃描到都會被調用 * @param context * @param intent */ @Override public void onReceive(Context context, Intent intent) { BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); bluetoothList.add(device.getAddress() + "設備名:" + device.getName());//接收穫取到的藍牙地址信息 //這裏可以使用handler進行數據的傳遞,更新UI界面 } } /** * 註冊一個廣播,並且掃描課配對的藍牙信息 */ public void searchForAvailableBluetooth(){ //1.註冊一個廣播,用於接收“發現設備”的廣播 intentFilter = new IntentFilter(BluetoothDevice.ACTION_FOUND); receiver = new BluetoothReceiver(); if(context != null){ context.registerReceiver(receiver, intentFilter); isRegisted = true; } //2.創建藍牙適配器,並開始掃描 //注意這裏需要對權限進行動態申請!!!!!!!!!!!!!!!!!!!!!!! BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); if (Build.VERSION.SDK_INT >= 23) { if (ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions((Activity)context, new String[]{Manifest.permission.ACCESS_COARSE_LOCATION}, 10); } } if(adapter.startDiscovery()){ Toast.makeText(context, "掃描開始",Toast.LENGTH_SHORT).show(); }else{ Toast.makeText(context, "掃描未開始",Toast.LENGTH_SHORT).show(); } } /** * 關閉掃描過程 */ public void tryToCancel(){ //結束掃描過程,並且關閉廣播 if (adapter.isDiscovering()) { adapter.cancelDiscovery(); } if(isRegisted){ context.unregisterReceiver(receiver); } }
-
-
連接設備
-
概念理解:配對和連接之間有一點是不同的。配對以爲着兩臺設備是知道彼此的存在的,它們有一個共享的鏈接密匙,可以用於授權以及建立一個加密的連接。而連接意味着設備目前共享一個RFCOMM通道,並且可以彼此傳輸數據。目前的安卓藍牙接口要求設備在建立一個RFCOMM連接之前先進行配對。(當你使用藍牙接口初始化一個加密的連接時,配對是自動執行的)。
-
那麼我們分爲兩步,一是配對,二是連接
-
配對:使用
createBond
。Method createBondMethod = null; createBondMethod = BluetoothDevice.class.getMethod("createBond"); if((boolean)createBondMethod.invoke(device)){ Toast.makeText(context, "device 配對成功", Toast.LENGTH_SHORT).show(); }
-
連接:連接需要分爲發起方
client
以及接受方server
,通過上面的發現設備等,能夠獲取到要連接的藍牙設備的相應的信息BluetoothDevice
,通過在相同的RFCOMM
通道上有一個連接的BluetoothSocket
時,就是相互連接上了。-
服務端:服務端需要持有一個打開的
BluetoothServerSocket
來作爲服務器端。該socket的目的是爲了監聽外來的連接請求,當一個請求被接受之後,提供一個連接的BluetoothSocket
.//注意:此處例子使用的Android開發中文api提供的例子,我自己寫的也是基於該例子進行一些相應的邏輯變化。 /** * 主要步驟如下: * 1. 通過adapter調用listenUsingRfcommWithServiceRecord(String, UUID)得到一個BluetoothServerSocket。其中,這個String是你的服務的標誌名稱,系統將會把它寫入設備中的一個新的服務發現協議(SDP)數據庫條目中(名字是任意的,並且可以只是你應用的名字)。UUID同樣被包含在SDP條目中,並且將會成爲和客戶端設備連接協議的基礎。也就是說,當客戶端嘗試連接這個設備時,它將會攜帶一個UUID用於唯一指定它想要連接的服務器。這些UUIDs必須匹配以便該連接可以被接受(在下一步中)。 通過調用accept()開始監聽連接請求。 * 2. 通過調用accept()開始監聽請求。這是一個阻塞調用,只有當一個遠程設備使用一個UUID發送了一個連接請求,並且該UUID和正在監聽的服務器socket註冊的UUID相匹配時,一個連接纔會被接受。成功後,accept() 將會返回一個已連接的 BluetoothSocket。(所以這意味着客戶端需要知道服務端的UUID!!!!!!!)(因爲是阻塞的,所以需要卡一個線程進行等待) * 3. close() */ 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) { } 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) { break; } // If a connection was accepted if (socket != null) { // Do work to manage the connection (in a separate thread) manageConnectedSocket(socket); //這裏就可以對該socket進行操作了,可以獲取到他的相應的一些信息,例如:inputstream、outputstream //例如我的操作,直接監聽客戶端發送的信息(這裏已經涉及到了藍牙數據交換了0-0) inputStream = socket.getInputStream(); byte[] buffer = new byte[1024]; int bytes; while(true){ outputStream= socket.getOutputStream();//判斷客戶端是否斷開 bytes = inputStream.read(buffer); if(bytes == 0 || bytes == -1){ ToastString("數據返回爲" + bytes); Message message = handler.obtainMessage(); message.what = MainActivity.CONNECTBREAK; handler.sendMessage(message); break; }//判斷客戶端是否斷開 Message message = handler.obtainMessage(); message.what = MainActivity.GETMESSAGE; Bundle bundle = new Bundle(); bundle.putString(MainActivity.MESSAGEKEY, new String(buffer,0,bytes)); message.setData(bundle); handler.sendMessage(message); } break; } } } /** Will cancel the listening socket, and cause the thread to finish */ public void cancel() { try { mmServerSocket.close(); } catch (IOException e) { } } }
-
客戶端:客戶端首先是需要一個
BluetoothDevice
(上面的步驟已經獲取到了),然後使用它獲取到BluetoothSocket
,然後調用connect()
即可初始化一個連接。/** 步驟 1. 使用BluetoothDevice,通過調用createRfcommSocketToServiceRecord(UUID)來得到一個BluetoothSocket. 此處的UUID必須跟服務器使用UUID是相匹配的。 2. 調用connect()初始化一個連接。 執行這個調用時,系統將會在遠程設備上執行一個SDP查找工作,來匹配UUID。如果查找成功,並且遠程設備接受了連接,它將會在連接過程中分享RFCOMM通道,而 connect() 將會返回。這個方法是阻塞的。如果,處於任何原因,該連接失敗了或者connect()超時了(大約12秒以後),那麼它將會拋出一個異常。 因爲connect()是一個阻塞調用,這個連接過程應該總是在一個單獨的線程中執行。 需要注意的是:你應該總是確保在你調用connect()時設備沒有執行設備查找工作。如果正在查找設備,那麼連接嘗試將會很大程度的減緩,並且很有可能會失敗。 */ 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; // 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(MY_UUID); } 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); } /** Will cancel an in-progress connection, and close the socket */ public void cancel() { try { mmSocket.close(); } catch (IOException e) { } } }
-
-
-
-
設備間數據交換
-
數據交換就很簡單了,分爲兩部分:1.寫入;2.讀取
-
寫入:寫入操作只需要對上述拿到的
socket
的OutputStream
進行操作,通過write(byte[])
方法進行數據的寫入即可:public void writeMessage(byte[] bytes){ try { output.write(bytes); } catch (IOException e) { e.printStackTrace(); } }
-
讀取:讀取操作只需要對上述拿到的
socket
的InputStream
進行操作,通過read(byte[])
方法進行讀取即可。需要注意的是,read方法是一個阻塞的方法,所以我們需要在新的線程的循環中進行讀取數據。//此處只展示讀取代碼,上面寫服務器端的時候有一個比較完整的實例 inputStream = socket.getInputStream(); byte[] buffer = new byte[1024]; int bytes; while(true){ outputStream= socket.getOutputStream();//判斷客戶端是否斷開 bytes = inputStream.read(buffer); if(bytes == 0 || bytes == -1){ ToastString("數據返回爲" + bytes); Message message = handler.obtainMessage(); message.what = MainActivity.CONNECTBREAK; handler.sendMessage(message); break; }//判斷客戶端是否斷開 Message message = handler.obtainMessage(); message.what = MainActivity.GETMESSAGE; Bundle bundle = new Bundle(); bundle.putString(MainActivity.MESSAGEKEY, new String(buffer,0,bytes)); message.setData(bundle); handler.sendMessage(message); }
-
完結!!!
寫藍牙這個小demo,我查了很多文章,但是都感覺暈頭轉向,很多方面都不是很懂,最後還是去看了Android的文檔比較實在!
在這裏附送 Android藍牙文檔,寫的很仔細,我上面的可能是以一個demo的形式進行展現。
查看完整藍牙通信demo的代碼,可以去我GitHub上面看一下:
GitHub的地址:https://github.com/shuhaoLIN/-Bluetooth-socket-
暫時寫的比較粗糙,後面再看看怎麼改善一下!