当前位置: 首页 > article >正文

Android 蓝牙开发-传输数据

在这里插入图片描述

概述

    传统蓝牙是通过建立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蓝牙通信机制详解


http://www.kler.cn/a/450499.html

相关文章:

  • 帧缓存的分配
  • 谷歌浏览器的网络连接问题解决方案
  • Selenium 和 Playwright两大框架的不同之处
  • 有没有检测吸烟的软件 ai视频检测分析厂区抽烟报警#Python
  • windows11家庭版安装docker无法识别基于wsl2的Ubuntu
  • Vue.js 响应接口
  • AIDD - 探索语言模型在药物分子生成方面的应用
  • iptables交叉编译(Hisiav300平台)
  • [cisco 模拟器] ftp服务器配置
  • 一个简单封装的的nodejs缓存对象
  • 828考研资料汇总
  • C++ QT chip layout tool开发浅思
  • 【python】银行客户流失预测预处理部分,独热编码·标签编码·数据离散化处理·数据筛选·数据分割
  • PTA数据结构题目:链表操作集合
  • 近实时”(NRT)搜索、倒排索引
  • Unity3d 基于UGUI和VideoPlayer 实现一个多功能视频播放器功能(含源码)
  • GitLab 停止为中国区用户提供 GitLab.com 账号服务
  • kong网关使用pre-function插件,改写接口的返回数据
  • 隧道可视化技术开拓智能建设新航道
  • 基于Spring Boot的摄影器材租赁回收系统
  • 神经网络图像隐写术:用AI隐藏信息的艺术
  • 1小时放弃Rust(1): Hello-World
  • *【每日一题 基础题】 [蓝桥杯 2024 省 B] 好数
  • 逻辑的诗:类与对象(下)
  • JavaWeb(一) | 基本概念(web服务器、Tomcat、HTTP、Maven)、Servlet 简介
  • Hydra配置文件的书写语法