概述
传统蓝牙是通过建立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"; @Override public 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蓝牙通信机制详解