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

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

    字符串格式要求如下:

    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蓝牙通信机制详解

相关文章:

Android 蓝牙开发-传输数据

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

webrtc获取IceCandidate流程

在WebRTC(Web Real-Time Communication)中,ICECandidate是一个关键概念,它用于描述在建立点对点(P2P)连接时可以考虑的潜在通信端点。以下是关于WebRTC中ICECandidate的详细解释: 一、ICECandidate的定义 ICECandidate对象通常包含以下关键属性: foundation:用于唯一…...

每天40分玩转Django:Django静态文件

Django静态文件 一、今日学习内容概述 学习模块重要程度主要内容静态文件配置⭐⭐⭐⭐⭐基础设置、路径配置CDN集成⭐⭐⭐⭐⭐CDN配置、资源优化静态文件处理⭐⭐⭐⭐压缩、版本控制部署优化⭐⭐⭐⭐性能优化、缓存策略 二、基础配置 # settings.py import os# 静态文件配置…...

Linux 线程池

1.概念介绍 线程池是一种多线程处理形式&#xff0c;它维护着多个线程&#xff0c;这些线程处于等待状态&#xff0c;随时准备接受任务并执行。线程池的主要目的是为了提高系统的性能和资源利用率&#xff0c;避免在处理短时间任务时频繁创建和销毁线程所带来的开销。 线程池…...

windows使用zip包安装MySQL

windows通过zip包安装MySQL windows通过zip包安装MySQL下载MySQL的zip安装包创建安装目录和数据目录解压zip安装包创建配置目录 etc 和 配置文件 my.ini安装MySQL进入解压后的bin目录执行命令初始化执行命令安装 验证安装查看服务已安装 启动MySQL查看服务运行情况修改密码创建…...

深度学习实战之超分辨率算法(tensorflow)——ESPCN

espcn原理算法请参考上一篇论文&#xff0c;这里主要给实现。 数据集如下&#xff1a;尺寸相等即可 针对数据集&#xff0c;生成样本代码preeate_data.py import imageio from scipy import misc, ndimage import numpy as np import imghdr import shutil import os import…...

Android unitTest 单元测试用例编写(初始)

文章目录 了解测试相关库导入依赖库新建测试文件示例执行查看结果网页结果其他 本片讲解的重点是unitTest&#xff0c;而不是androidTest哦 了解测试相关库 androidx.compose.ui:ui-test-junit4: 用于Compose UI的JUnit 4测试库。 它提供了测试Compose UI组件的工具和API。 and…...

C++简明教程(10)(初识类)

类的教程 C 类的完整教程 C 中&#xff0c;类&#xff08;class&#xff09;是面向对象编程的核心概念&#xff0c;用于定义对象的属性&#xff08;数据成员&#xff09;和行为&#xff08;成员函数&#xff09;。本教程将带你从零开始&#xff0c;循序渐进地学习如何定义和使…...

光谱相机的工作原理

光谱相机的工作原理主要基于不同物质对不同波长光的吸收、反射和透射特性存在差异&#xff0c;以下是其具体工作过程&#xff1a; 一、光的收集 目标物体在光源照射下&#xff0c;其表面会对光产生吸收、反射和透射等相互作用。光谱相机的光学系统&#xff08;如透镜、反射镜…...

【Linux进程】基于管道实现进程池

目录 前言 1. 进程池 1.1 基本结构&#xff1a; 1.2. 池化技术 1.3. 思路分析 1.4. 代码实现 总结 前言 上篇文章介绍了管道及其使用&#xff0c;本文在管道的基础上&#xff0c;通过匿名管道来实现一个进程池&#xff1b; 1. 进程池 父进程创建一组子进程&#xff0c;子进…...

软件测试之单元测试

&#x1f345; 点击文末小卡片&#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 一、何为单测 测试有黑盒测试和白盒测试之分&#xff0c;黑盒测试顾名思义就是我们不了解盒子的内部结构&#xff0c;我们通过文档或者对该功能的理解&#xff0c…...

vscode+编程AI配置、使用说明

文章目录 [toc]1、概述2、github copilot2.1 配置2.2 使用文档2.3 使用说明 3、文心快码&#xff08;Baidu Comate&#xff09;3.1 配置3.2 使用文档3.3 使用说明 4、豆包&#xff08;MarsCode&#xff09;4.1 配置4.2 使用文档4.3 使用说明 5、通义灵码&#xff08;TONGYI Lin…...

007-spring-bean的相关配置(重要)

spring-bean的相关配置...

【唐叔学算法】第19天:交换排序-冒泡排序与快速排序的深度解析及Java实现

引言 排序算法是计算机科学中的基础问题&#xff0c;而交换排序作为其中一类经典的排序方法&#xff0c;因其简单直观的思想和易于实现的特点&#xff0c;在初学者中广受欢迎。交换排序的核心思想是通过不断交换相邻元素来达到排序的目的。本文将深入探讨两种典型的交换排序算…...

合并 Python 中的字典

合并 Python 中的字典 如何在 Python 中合并字典&#xff1f; 这取决于你对“合并”一词的具体定义。 在 Python 中使用 | 操作符合并字典 首先&#xff0c;让我们讨论合并字典的最简单方法&#xff0c;这通常已经足够满足你的需求。 以下是两个字典&#xff1a; >>…...

使用Python实现自动化文档生成工具:提升文档编写效率的利器

友友们好! 我的新专栏《Python进阶》正式启动啦!这是一个专为那些渴望提升Python技能的朋友们量身打造的专栏,无论你是已经有一定基础的开发者,还是希望深入挖掘Python潜力的爱好者,这里都将是你不可错过的宝藏。 在这个专栏中,你将会找到: ● 深入解析:每一篇文章都将…...

uniapp使用live-pusher实现模拟人脸识别效果

需求&#xff1a; 1、前端实现模拟用户人脸识别&#xff0c;识别成功后抓取视频流或认证的一张静态图给服务端。 2、服务端调用第三方活体认证接口&#xff0c;验证前端传递的人脸是否存在&#xff0c;把认证结果反馈给前端。 3、前端根据服务端返回的状态&#xff0c;显示在…...

【JavaSE】【网络原理】初识网络

目录 一、网络互联二、局域网与广域网三、网络通信基础3.1 IP地址3.2 端口号3.3 网络协议3.4 五元组 四、协议分层4.1 OSI七层网络模型4.2 TCP/IP五层(四层)网络模型4.3 网络设备 五、网络数据通信基本流程。5.1 封装和分用5.2 简述过程 一、网络互联 网络互联&#xff1a; 网…...

鸿蒙之路的坑

1、系统 Windows 10 家庭版不可用模拟器 对应的解决方案【坑】 升级系统版本 直接更改密钥可自动升级系统 密钥找对应系统的&#xff08;例&#xff1a;windows 10专业版&#xff09; 升级完之后要激活 坑1、升级完后事先创建好的模拟器还是无法启动 解决&#xff1a;删除模拟…...

Python生日祝福烟花

1. 实现效果 2. 素材加载 2个图片和3个音频 shoot_image pygame.image.load(shoot(已去底).jpg) # 加载拼接的发射图像 flower_image pygame.image.load(flower.jpg) # 加载拼接的烟花图 烟花不好去底 # 调整图像的像素为原图的1/2 因为图像相对于界面来说有些大 shoo…...

wordpress后台更新后 前端没变化的解决方法

使用siteground主机的wordpress网站&#xff0c;会出现更新了网站内容和修改了php模板文件、js文件、css文件、图片文件后&#xff0c;网站没有变化的情况。 不熟悉siteground主机的新手&#xff0c;遇到这个问题&#xff0c;就很抓狂&#xff0c;明明是哪都没操作错误&#x…...

在软件开发中正确使用MySQL日期时间类型的深度解析

在日常软件开发场景中&#xff0c;时间信息的存储是底层且核心的需求。从金融交易的精确记账时间、用户操作的行为日志&#xff0c;到供应链系统的物流节点时间戳&#xff0c;时间数据的准确性直接决定业务逻辑的可靠性。MySQL作为主流关系型数据库&#xff0c;其日期时间类型的…...

Linux 文件类型,目录与路径,文件与目录管理

文件类型 后面的字符表示文件类型标志 普通文件&#xff1a;-&#xff08;纯文本文件&#xff0c;二进制文件&#xff0c;数据格式文件&#xff09; 如文本文件、图片、程序文件等。 目录文件&#xff1a;d&#xff08;directory&#xff09; 用来存放其他文件或子目录。 设备…...

模型参数、模型存储精度、参数与显存

模型参数量衡量单位 M&#xff1a;百万&#xff08;Million&#xff09; B&#xff1a;十亿&#xff08;Billion&#xff09; 1 B 1000 M 1B 1000M 1B1000M 参数存储精度 模型参数是固定的&#xff0c;但是一个参数所表示多少字节不一定&#xff0c;需要看这个参数以什么…...

SciencePlots——绘制论文中的图片

文章目录 安装一、风格二、1 资源 安装 # 安装最新版 pip install githttps://github.com/garrettj403/SciencePlots.git# 安装稳定版 pip install SciencePlots一、风格 简单好用的深度学习论文绘图专用工具包–Science Plot 二、 1 资源 论文绘图神器来了&#xff1a;一行…...

Cesium1.95中高性能加载1500个点

一、基本方式&#xff1a; 图标使用.png比.svg性能要好 <template><div id"cesiumContainer"></div><div class"toolbar"><button id"resetButton">重新生成点</button><span id"countDisplay&qu…...

高频面试之3Zookeeper

高频面试之3Zookeeper 文章目录 高频面试之3Zookeeper3.1 常用命令3.2 选举机制3.3 Zookeeper符合法则中哪两个&#xff1f;3.4 Zookeeper脑裂3.5 Zookeeper用来干嘛了 3.1 常用命令 ls、get、create、delete、deleteall3.2 选举机制 半数机制&#xff08;过半机制&#xff0…...

质量体系的重要

质量体系是为确保产品、服务或过程质量满足规定要求&#xff0c;由相互关联的要素构成的有机整体。其核心内容可归纳为以下五个方面&#xff1a; &#x1f3db;️ 一、组织架构与职责 质量体系明确组织内各部门、岗位的职责与权限&#xff0c;形成层级清晰的管理网络&#xf…...

srs linux

下载编译运行 git clone https:///ossrs/srs.git ./configure --h265on make 编译完成后即可启动SRS # 启动 ./objs/srs -c conf/srs.conf # 查看日志 tail -n 30 -f ./objs/srs.log 开放端口 默认RTMP接收推流端口是1935&#xff0c;SRS管理页面端口是8080&#xff0c;可…...

Axios请求超时重发机制

Axios 超时重新请求实现方案 在 Axios 中实现超时重新请求可以通过以下几种方式&#xff1a; 1. 使用拦截器实现自动重试 import axios from axios;// 创建axios实例 const instance axios.create();// 设置超时时间 instance.defaults.timeout 5000;// 最大重试次数 cons…...