android藍牙簡單開發示例教程
概述
前段時間學習瞭一些藍牙開發的知識,記錄一下Android中藍牙的簡單開發。下面是最重要的兩個類。
BluetoothAdapter : 藍牙適配器,通過getDefaultAdapter ()去獲取一個實例,如果設備不支持藍牙的話,返回的是一個null對象,通過它,可以打開、關閉藍牙,掃描設備、向指定設備創建socket通道…
BluetoothDevice : 代表一個設備對象,可以通過它獲取設備的名字、地址、類型等,也可以創建匹配,建立socket通道等等。
1、權限申請
<uses-permission android:name="android.permission.BLUETOOTH"/> 使用藍牙所需要的權限 <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/> 使用掃描和設置藍牙的權限(申明這一個權限必須申明上面一個權限)
Android6以上版本,掃描其他藍牙還需要位置權限
// Android 9 以下版本 <user-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/> // Android 9 以上 <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
2、打開藍牙
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); // 如果設備不支持藍牙 if (mBluetoothAdapter == null){ return; } // 設備支持藍牙功能,調用startActivityForResult去啟動藍牙 if (!mBluetoothAdapter.isEnabled()){ startBlueTooth.launch(new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE)); }
打開藍牙功能是通過startActivity去啟動的,但是startActivity這個函數已經過期瞭,所以我使用官方推薦的Activity Result替代它
ActivityResultLauncher<Intent> startBlueTooth = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), new ActivityResultCallback<ActivityResult>() { @Override public void onActivityResult(ActivityResult result) { if (result==null){ Toast.makeText(BlueToothActivity.this, "open failed", Toast.LENGTH_SHORT).show(); }else { if (result.getResultCode() == RESULT_CANCELED){ Toast.makeText(BlueToothActivity.this,"用戶取消",Toast.LENGTH_SHORT); } } } });
3、接收藍牙狀態的改變
通過廣播去接收藍牙狀態的改變
class BluetoothStateChangeReceiver extends BroadcastReceiver{ public int DEFAULT_VALUE_BLUETOOTH = 1000; @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(action)){ int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE,DEFAULT_VALUE_BLUETOOTH); switch(state){ case BluetoothAdapter.STATE_ON: Log.d(TAG, "onReceive: open"); break; case BluetoothAdapter.STATE_OFF: Log.d(TAG, "onReceive: off"); break; case BluetoothAdapter.STATE_TURNING_ON : Log.d(TAG, "onReceive: 正在打開"); break; case BluetoothAdapter.STATE_TURNING_OFF: Log.d(TAG, "onReceive: 正在關閉"); break; } } } }
別忘瞭廣播的註冊和解註冊
IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED); stateReceiver = new BluetoothStateChangeReceiver() ; registerReceiver(stateReceiver,filter);
4、掃描其他的設備
同樣通過廣播接收,action是BluetoothDevice.ACTION_FOUND
class MyReceiver extends BroadcastReceiver{ @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (BluetoothDevice.ACTION_FOUND.equals(action)) { // 從intent對象中獲取藍牙設備的信息 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); // 當發現新設備不存在於配對列表中時添加 if (device.getBondState() != BluetoothDevice.BOND_BONDED) { blueNames.add(device.getName()+"\t"+device.getAddress()); } blueAdpater.notifyDataSetChanged(); Log.d(TAG, "onReceive: " + device.getName()); } } }
動態註冊廣播
IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND); registerReceiver(mReceiver,filter);
開啟掃描
mBluetoothAdapter.startDiscovery();
5、藍牙配對
public class BondReceiver extends BroadcastReceiver{ @Override public void onReceive(Context context, Intent intent) { if (BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(intent.getAction())){ BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); switch(device.getBondState()){ case BluetoothDevice.BOND_BONDED: Log.d(TAG, "onReceive: 配對完成"); break; case BluetoothDevice.BOND_BONDING: Log.d(TAG, "onReceive: 正在配對"); break; case BluetoothDevice.BOND_NONE: Log.d(TAG, "onReceive: 取消配對"); break; } } } }
6、獲取已經配對的設備
已經配對的設備會被存儲起來,通過BluetoothAdpater直接獲取即可
Set<BluetoothDevice> paireDevices = mBluetoothAdapter.getBondedDevices(); if (paireDevices.size()>0){ for (BluetoothDevice pairedDevice : pairedDevices) { blueNames.add(pairedDevice.getName()+" "+pairedDevice.getAddress()); Log.d(TAG, "onClick: "+pairedDevice.getName()); } }
7、連接設備
想要在兩臺設備之間創建連接,必須實現客戶端和服務端機制,他們之間使用套接字機制進行連接,服務端開放服務器套接字,客戶端通過MAC地址向服務端發起連接。客戶端和服務端以不同的方式獲得BluetoothSocket
,當客戶端和服務端在同一個RFCOMM通道上分別擁有已連接的BluetoothSocket
時,將他們視為彼此已經連接,於是每臺設備都獲得輸入和輸出流式傳輸,並開始傳輸數據。
連接技術
一種實現技術是自動將每臺設備準備為一個服務器,從而使每臺設備開放一個服務套接字並偵聽連接,在此情況下,任何一臺設備都可以發起與另一臺設備的連接並稱為客戶端。
服務器
設置服務器套接字並接受連接,步驟依次如下
1、調用listenUsingRfcommWithServiceRecord()獲取一個BluetoothServerSocket
, 該函數需要兩個參數,第一個是服務器的名稱,自己取一個即可,第二個是UUID,用來對信息做唯一性標識,我們可以從網上眾多UUID生成器中隨機的生成一個,然後使用UUID.fromString(String)初始化一個UUID。
2、通過accept()函數開始偵聽連接請求
隻有遠程設備發送的連接請求中UUID與使用此套接字註冊的UUID相匹配時服務器才會接受請求,accept函數會返回已連接的BluetoothSocket
3、連接成功後調用close()
關閉BluetoothSocket
private class AcceptThread extends Thread{ private final BluetoothServerSocket mmServerSocket; private String mSocketType; public AcceptThread(boolean secure){ BluetoothServerSocket tmp = null; mSocketType = secure ? "secure" : "Insercure"; try{ if (secure){ tmp = bluetoothAdapter.listenUsingRfcommWithServiceRecord(NAME_SECURE,MY_UUID_SECURE); }else{ tmp = bluetoothAdapter.listenUsingRfcommWithServiceRecord(NAME_INSECURE,MY_UUID_INSECURE); } } catch (IOException e) { Log.e(TAG,"socket type"+ mSocketType + "listen() failed",e); } mmServerSocket = tmp; } @Override public void run() { Log.d(TAG, "Socket Type: " + mSocketType + "BEGIN mAcceptThread" + this); setName("AcceptThread"+ mSocketType); BluetoothSocket socket = null; Log.d(TAG, "run: 開始監聽"); while (true){ try{ socket = mmServerSocket.accept(); Log.d("acceptThread", "run: 連接成功"); connected(socket,socket.getRemoteDevice(),mSocketType); } catch (IOException e) { Log.e(TAG, "Socket Type: " + mSocketType + "accept() failed", e); break; } } Log.i(TAG, "END mAcceptThread, socket Type: " + mSocketType); } public void cancel() { Log.d(TAG, "Socket Type" + mSocketType + "cancel " + this); try { mmServerSocket.close(); } catch (IOException e) { Log.e(TAG, "Socket Type" + mSocketType + "close() of server failed", e); } } }
上面的secure和Insecure隻是使用瞭不同的UUID而已。
客戶端
遠程設備開啟監聽後,我們就發起向此設備的連接,首先必須先獲得遠程設備的BluetoothDevice對象,然後獲取BluetoothSocket發起連接。
基本步驟如下
1、使用BluetoothDevice
通過調用createRfcommSocketToServiceRecord(UUID)
獲取 BluetoothSocket
。
2、通過connect發起連接
private class ConnectThread extends Thread{ private final BluetoothSocket mmSocket; private final BluetoothDevice mmDevice; private String mSocketType; public ConnectThread(BluetoothDevice device, boolean secure){ mmDevice = device; BluetoothSocket tmp = null; mSocketType = secure ? "Secure" : "Insecure"; try { if (secure){ tmp = device.createRfcommSocketToServiceRecord(MY_UUID_SECURE); }else { tmp = device.createRfcommSocketToServiceRecord(MY_UUID_INSECURE); } } catch (IOException e) { Log.e(TAG, "Socket Type: " + mSocketType + "create() failed", e); } mmSocket = tmp; } @Override public void run() { Log.i(TAG, "BEGIN mConnectThread SocketType:" + mSocketType); setName("ConnectThred"+mSocketType); // 總是取消發現,因為它會減慢連接 bluetoothAdapter.cancelDiscovery(); // connect // Make a connection to the BluetoothSocket try { // This is a blocking call and will only return on a // successful connection or an exception mmSocket.connect(); Log.d(TAG, "run: socket連接成功"); } catch (IOException e) { // Close the socket Log.d(TAG, "run: 關閉socket"); try { mmSocket.close(); } catch (IOException e2) { Log.e(TAG, "unable to close() " + mSocketType + " socket during connection failure", e2); } return; } connected(mmSocket,mmDevice,mSocketType); } public void cancel(){ try{ mmSocket.close(); } catch (IOException e) { Log.e(TAG, "close() of connect " + mSocketType + " socket failed", e); } } }
發送數據
連接成功後,我們就可以通過socket發送數據瞭,客戶端的Socket對象是BluetoothSocket
, 服務端的socket是BluetoothServerSocket
,特別註意不要混淆瞭。使用getInputStream
和getOutputStream
分別獲取通過套接字處理數據傳輸的InputStream
和OutputStream
。寫數據比較簡單,但是讀數據就需要一個單獨的線程一直監聽才行。
private class ConnectedThread extends Thread{ private final BluetoothSocket mmSocket; private InputStream mmInStream; private OutputStream mmOutStream; public ConnectedThread(BluetoothSocket socket, String socketType) throws IOException { Log.d(TAG, "create ConnectedThread: " + socketType); mmSocket = socket; InputStream tmpIn = null; OutputStream tmpOut = null; try{ tmpIn = socket.getInputStream(); tmpOut = socket.getOutputStream(); if (socket != null){ tmpOut.write(new String("hello").getBytes()); Log.d(TAG, "ConnectedThread: socket不是null"); } } catch (IOException e) { Log.e(TAG,"temp socket not created", e); } mmInStream = tmpIn; mmOutStream = tmpOut; // mmOutStream.write(new String("hello").getBytes()); } @RequiresApi(api = Build.VERSION_CODES.KITKAT) @Override public void run() { Log.i(TAG, "BEGIN mConnectedThread"); byte[] buffer = new byte[1024]; int bytes; while (true){ try{ bytes = mmInStream.read(buffer); // send the bytes to the ui Activity String text = encodeByteToString(buffer,bytes); Log.d(TAG, "run: 收到消息:"+ text); chatItems.add(text); mHandler.sendMessage(mHandler.obtainMessage()); } catch (IOException e) { Log.d(TAG, "run: 沒有收到消息"); e.printStackTrace(); break; } } } public String encodeByteToString(byte[] data,int length) { byte[] temp = new byte[length]; for (int i = 0; i < length; i++) { temp[i] = data[i]; } try { return new String(temp,"utf-8"); } catch (UnsupportedEncodingException e) { return ""; } } public void write(byte[] buffer){ try{ mmOutStream.write(buffer); // mHandler.obtainMessage(Constants.MESSAGE_WRITE,-1,-1,buffer).sendToTarget(); } catch (IOException e) { e.printStackTrace(); } } public void cancel(){ try{ mmSocket.close(); Log.d(TAG, "cancel: connectedThread"); } catch (IOException e) { Log.e(TAG, "close() of connect socket failed", e); } } }
上面的例子我主要是學習官網上的藍牙聊天項目寫的代碼,大傢也可以直接看官網項目。從上面的例子中可知,接受到的數據流都是一些二進制,要用到實際的項目中還需要進行一定的編碼和轉換。也就是自己編寫一些協議,學過socket編程的同學一定都懂,其實藍牙已經有很多的好用的協議瞭,就比如AVRCP(Audio Video Remote Control Profile),定義瞭藍牙設備和audio/video控制功能通信的特點和過程, 結合MediaSession 可以很容易的實現設備音視頻控制。
到此這篇關於android藍牙簡單開發示例教程的文章就介紹到這瞭,更多相關android藍牙開發內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!
推薦閱讀:
- Android實現藍牙串口通訊
- Android 8.0實現藍牙遙控器自動配對
- 詳解Android的四大應用程序組件
- Android實現一鍵鎖屏功能
- Android四大組件之廣播BroadcastReceiver詳解