Android 蓝牙开发-传输数据

发布于:2025-02-11 ⋅ 阅读:(99) ⋅ 点赞:(0)

在这里插入图片描述

概述

    传统蓝牙是通过建立REFCCOM sockect来进行通信的,类似于socket通信,一台设备需要开放服务器套接字并处于listen状态,而另一台设备使用服务器的MAC地址发起连接。连接建立后,服务器和客户端就都通过对BluetoothSocket进行读写操作来进行通信。

实现步骤

      要通过蓝牙来传输数据整体流程如下:

首次, 未配对状态

Created with Raphaël 2.3.0 开始 打开蓝牙 蓝牙已打开? 扫描 找到设备? 配对 配对成功? 连接 发送/读取数据 结束 yes no yes no yes no

已配对状态

Created with Raphaël 2.3.0 开始 打开蓝牙 蓝牙已打开? 获取配对设备 连接 发送/读取数据 结束 yes no

| 关键代码

蓝牙权限

    <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 的字符串。

    字符串格式要求如下:

    1. 长度必须是 36 个字符。
    2. 字符串必须以连字符(-)分隔为五组,每组字符数分别为 8、4、4、4 和 12。
    3. 所有字符都必须是十六进制数字(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蓝牙通信机制详解