概述
传统蓝牙是通过建立REFCCOM sockect来进行通信的,类似于socket通信,一台设备需要开放服务器套接字并处于listen状态,而另一台设备使用服务器的MAC地址发起连接。连接建立后,服务器和客户端就都通过对BluetoothSocket进行读写操作来进行通信。
实现步骤
要通过蓝牙来传输数据整体流程如下:
首次, 未配对状态
已配对状态
| 关键代码
蓝牙权限
<uses-permission android:name="android.permission.BLUETOOTH" /><uses-permission android:name="android.permission.BLUETOOTH_CONNECT" /><uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /><uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
打开或关闭蓝牙
BluetoothAdapter ba = BluetoothAdapter.getDefaultAdapter(); ba.enable(); ba.disable();
扫描设备
BluetoothAdapter.getDefaultAdapter().startDiscovery();
监听设备扫描
//Device stae IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED); //for discovery . filter.addAction(BluetoothDevice.ACTION_FOUND); filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_STARTED); filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
根据ACTION_FOUND
实时获取扫描到的设备, 常规会获取设备的名称和MAC
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
String name = device.getName();
String mac = device.getAddress();
| 发起设备配对
| 配对动作大部分情况下, 连接的双方设备都会有对应的弹出窗口, 让用户选择是否配对设备.
public static boolean startPair(BluetoothDevice dev){Logger.d(TAG, "startPair(" + dev.getName() + ")");if(dev != null){try {Method createBond = BluetoothDevice.class.getDeclaredMethod("createBond");if(createBond != null){Object r = createBond.invoke(dev);return (Boolean)r;}} catch (NoSuchMethodException e) {e.printStackTrace();} catch (InvocationTargetException e) {e.printStackTrace();} catch (IllegalAccessException e) {e.printStackTrace();}}return false;
}
| 连接并发送数据
//获取已绑定的设备
Set<BluetoothDevice> devs = BluetoothAdapter.getDefaultAdapter().getBondedDevices();
BluetoothDevice target = null;//通过名称查找目标设备
for(BluetoothDevice d : devs){if(StringTools.isNotEmpty(d.getName()) && d.getName().startsWith("TARGET-")){target = d;break;}
}if(target != null) {final UUID MY_UUID = UUID.fromString("00001101-0000-1000-8000-00XXXXXXXXXX");try {BluetoothSocket socket = target.createRfcommSocketToServiceRecord(MY_UUID);socket.connect();OutputStream outputStream = socket.getOutputStream();outputStream.write(msg.getBytes());Logger.d(TAG, "sendMsgByBluetooth " + msg);outputStream.flush();InputStream is = socket.getInputStream();byte[] cache = new byte[512];int r = is.read(cache);Logger.d(TAG, "receive response: " + new String(cache));sleepx(500);is.close();outputStream.close();} catch (IOException e) {e.printStackTrace();}
}
基于系统源码的服务端
PS: 源码端可以通过获取system
权限, 完成设备配对流程
监听广播并执行配对
public static final String BLUETOOTH_PARING_QUEST = "android.bluetooth.device.action.PAIRING_REQUEST";@Overridepublic void onReceive(Context context, Intent intent) {BluetoothDevice device = BluetoothTools.onPairRequest(intent);int mType = intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT, BluetoothDevice.ERROR);int mPasskey = intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_KEY, BluetoothDevice.ERROR);BluetoothTools.pair(device, mType, mPasskeyFormatted);}
BluetoothTools.java (反射系统蓝牙部分接口)
private static void setRemoteOutOfBandData(BluetoothDevice dev){try {Method setRemoteOutOfBandData = BluetoothDevice.class.getDeclaredMethod("setRemoteOutOfBandData");if(setRemoteOutOfBandData != null){setRemoteOutOfBandData.invoke(dev);}} catch (NoSuchMethodException e) {e.printStackTrace();} catch (InvocationTargetException e) {e.printStackTrace();} catch (IllegalAccessException e) {e.printStackTrace();}}private static void setPairingConfirmation(BluetoothDevice dev, boolean bool){try {Method setPairingConfirmation = BluetoothDevice.class.getDeclaredMethod("setPairingConfirmation", Boolean.TYPE);if(setPairingConfirmation != null){setPairingConfirmation.invoke(dev, bool);}} catch (NoSuchMethodException e) {e.printStackTrace();} catch (InvocationTargetException e) {e.printStackTrace();} catch (IllegalAccessException e) {e.printStackTrace();}}private static void setPasskey(BluetoothDevice dev, int passKey){try {Method setPasskey = BluetoothDevice.class.getDeclaredMethod("setPasskey", Integer.TYPE);if(setPasskey != null){setPasskey.invoke(dev, passKey);}} catch (NoSuchMethodException e) {e.printStackTrace();} catch (InvocationTargetException e) {e.printStackTrace();} catch (IllegalAccessException e) {e.printStackTrace();}}private static byte[] convertPinToBytes(String val){try {Method convertPinToBytes = BluetoothDevice.class.getDeclaredMethod("convertPinToBytes", String.class);if(convertPinToBytes != null){return (byte[])convertPinToBytes.invoke(null, val);}} catch (NoSuchMethodException e) {e.printStackTrace();} catch (InvocationTargetException e) {e.printStackTrace();} catch (IllegalAccessException e) {e.printStackTrace();}return null;}private static void setPin(BluetoothDevice mDevice, byte[] bytes){try {Method setPin = BluetoothDevice.class.getDeclaredMethod("setPin", byte[].class);if(setPin != null){setPin.invoke(mDevice, (Object)bytes);}} catch (NoSuchMethodException e) {e.printStackTrace();} catch (InvocationTargetException e) {e.printStackTrace();} catch (IllegalAccessException e) {e.printStackTrace();}}public static void pair(BluetoothDevice dev, int type, String password){onPair(type, password, dev);}private static void onPair(int mType, String value, BluetoothDevice mDevice) {switch (mType) {case BluetoothDevice.PAIRING_VARIANT_PIN:byte[] pinBytes = convertPinToBytes(value);if (pinBytes == null) {return;}setPin(mDevice, pinBytes);break;case PAIRING_VARIANT_PASSKEY:int passkey = Integer.parseInt(value);setPasskey(mDevice, passkey);break;case BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION:case PAIRING_VARIANT_CONSENT:setPairingConfirmation(mDevice, true);break;case PAIRING_VARIANT_DISPLAY_PASSKEY:case PAIRING_VARIANT_DISPLAY_PIN:// Do nothing.break;case PAIRING_VARIANT_OOB_CONSENT:setRemoteOutOfBandData(mDevice);break;default:Logger.e(TAG, "Incorrect pairing type received");}}
同样, 在完成配对后, 可以通过BluetoothServerSocket 来接收来自客户端的连接实现通讯:
final UUID MY_UUID = UUID.fromString("00001101-0000-1000-8000-00XXXXXXXXXX");
BluetoothServerSocket serverSocket = BluetoothAdapter.listenUsingRfcommWithServiceRecord("MyApp", MY_UUID);
Logger.d(TAG, "listen: waiting for new connect...");
BluetoothSocket clientSocket = serverSocket.accept();
InputStream inputStream = mSocket.getInputStream();
byte[] cache = new byte[512];
inputStream.read(cache);
//省略读写和关闭代码...
-
关于UUID
UUID.fromString
方法用于将字符串转换为UUID
对象。UUID
(通用唯一标识符)是一个 128 位的值,通常表示为 32 个十六进制数字,分为五组,形式为 8-4-4-4-12 的字符串。字符串格式要求如下:
- 长度必须是 36 个字符。
- 字符串必须以连字符(-)分隔为五组,每组字符数分别为 8、4、4、4 和 12。
- 所有字符都必须是十六进制数字(0-9 和 a-f 或 A-F)。
示例:
import java.util.UUID;public class Main {public static void main(String[] args) {String uuidString = "123e4567-e89b-12d3-a456-426614174000";UUID uuid = UUID.fromString(uuidString);System.out.println("UUID: " + uuid);} }
如果你尝试使用不符合格式的字符串,
UUID.fromString
方法将抛出IllegalArgumentException
。例如:import java.util.UUID;public class Main {public static void main(String[] args) {String invalidUuidString = "123e4567-e89b-12d3-a456-4266141740"; // 缺少一个字符try {UUID uuid = UUID.fromString(invalidUuidString);System.out.println("UUID: " + uuid);} catch (IllegalArgumentException e) {System.out.println("Invalid UUID string: " + invalidUuidString);}} }
在这个例子中,
invalidUuidString
不符合 UUID 字符串格式要求,因此UUID.fromString
方法将抛出IllegalArgumentException
。
PS:UUID对字符大小写不敏感
参考系统源码 packages/apps/Settings
配对窗口: AndroidManifest.xml<activity android:name=".bluetooth.BluetoothPairingDialog"android:excludeFromRecents="true"android:windowSoftInputMode="stateVisible|adjustResize"android:theme="@*android:style/Theme.DeviceDefault.Settings.Dialog.NoActionBar"><intent-filter android:priority="1"><action android:name="android.bluetooth.device.action.PAIRING_REQUEST" /><category android:name="android.intent.category.DEFAULT" /></intent-filter></activity>
src/com/android/settings/bluetooth/BluetoothParingController.java
src/com/android/settings/bluetooth/BluetoothParingDialogFragment.java
参考
android蓝牙开发 蓝牙设备的查找和连接
Android蓝牙通信机制详解