【Android】Bluetooth(蓝牙)连接与数据传输(一)

2023-11-10

简介

蓝牙技术是一种无线数据和语音通信开放的全球规范,它是基于低成本的近距离无线连接,为固定和移动设备建立通信环境的一种特殊的近距离无线技术(使用2.4~2.485GHz的ISM波段的UHF无线电波)连接。

蓝牙类型 描述
经典蓝牙(Classic Bluetooth) 功耗高,传输数据量大,传输距离短(10米)
低功耗蓝牙(Bluetooth Low Energy) 功耗低,传输数据量小,传输距离较经典蓝牙远
蓝牙模块 描述
单模蓝牙 有一种蓝牙版本,运行一种蓝牙协议栈的模块,常用在低功耗蓝牙(Bluetooth Low Energy),如手环。
双模蓝牙 内置两个蓝牙版本,运行两套协议栈的蓝牙模块,支持经典蓝牙与低功耗蓝牙,如手机。

权限声明

Android 11(API 30)及以下的Android版本,只需声明android.permission.BLUETOOTHandroid.permission.ACCESS_FINE_LOCATION,如果是Android 9(API 28)及以下的Android版本,则需要将android.permission.ACCESS_FINE_LOCATION更换为android.permission.ACCESS_COARSE_LOCATION。以下权限则是Android 12(API 31)及以上你可能会使用到的权限。

<!--  允许应用程序连接到配对的蓝牙设备  -->
<uses-permission android:name="android.permission.BLUETOOTH"/>
<!--  需要能够发现和配对附近的蓝牙设备  -->
<uses-permission android:name="android.permission.BLUETOOTH_SCAN"/>
<!--  允许应用程序发现和配对蓝牙设备  -->
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
<!--  需要能够连接到配对的蓝牙设备  -->
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT"/>
<!--  允许应用程序在没有用户交互的情况下配对蓝牙设备,并允许或禁止电话簿访问或消息访问  -->
<uses-permission android:name="android.permission.BLUETOOTH_PRIVILEGED"
    tools:ignore="ProtectedPermissions" />
<!--  需要能够向附近的蓝牙设备做广告 Android 12 -->
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE"/>

<uses-permission android:name="com.google.android.things.permission.MANAGE_BLUETOOTH" />
<!--  获得定位  -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>

这里展示了七个与蓝牙相关的权限,其中的android.permission.BLUETOOTH_SCANandroid.permission.BLUETOOTH_CONNECTandroid.permission.BLUETOOTH_ADVERTISEandroid.permission.ACCESS_FINE_LOCATION权限属于危险权限,需要另外写代码去动态申请。

蓝牙扫描

开始扫描

蓝牙扫描接收扫描结果使用BroadcastReceiver进行接收,而BroadcastReceiver需要写代码去触发,执行startDiscovery()扫描蓝牙前应加上registerReceiver(BroadcastReceiver receiver,IntentFilter filter)注册一个广播接收者,同时加上IntentFilter过滤掉那些用不到的intent,只取我们用得到的Intent

val filter = IntentFilter()
// 找到设备
filter.addAction(BluetoothDevice.ACTION_FOUND)
// 远程设备的绑定状态发生变化
filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED)
// 第一次检索远程设备的友好名称,或自上次检索后更改
filter.addAction(BluetoothDevice.ACTION_NAME_CHANGED)
// 远程设备的蓝牙类已更改
filter.addAction(BluetoothDevice.ACTION_CLASS_CHANGED)
// 用于在获取远程设备后将其UUID 作为ParcelUuid远程设备的包装进行广播
filter.addAction(BluetoothDevice.ACTION_UUID)
// 操作配对请求
filter.addAction(BluetoothDevice.ACTION_PAIRING_REQUEST)
// 蓝牙状态改变
filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED)
// 开始搜索蓝牙
filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_STARTED)
// 蓝牙搜索完成
filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED)
// 本地Adapter的蓝牙扫描模式发生了变化
filter.addAction(BluetoothAdapter.ACTION_SCAN_MODE_CHANGED)
// 本地蓝牙适配器更改蓝牙名称
filter.addAction(BluetoothAdapter.ACTION_LOCAL_NAME_CHANGED)
// 请求本地蓝牙可被其它蓝牙扫描到
filter.addAction(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE)
// 显示请求可发现模式的系统活动
filter.addAction(BluetoothAdapter.ACTION_REQUEST_ENABLE)
// 连接状态改变
filter.addAction(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED)

继承了BroadcastReceiver的类,实现其onReceive(Context context, Intent intent)方法,就可以在onReceive(Context context, Intent intent)里加上if语句去判断当前BroadcastReceiver是否接收到了上述的Action,当接收到IntentAction与上述IntentFilter.addAction(String action)相符,意味着是时候该执行与对应Action相关的操作了。

当识别到设备需要将其返回到Activity时,则可使用EventBus来进行传输。

执行上述操作后,当前手机开启了蓝牙,其它的蓝牙设备却扫描不到当前手机蓝牙时,也许是因为没有启用允许其它蓝牙发现当前手机蓝牙的功能(Android系统默认情况下无法扫描到当前手机蓝牙),关于这一点,应在扫描蓝牙前就加上允许其它蓝牙发现当前手机蓝牙的代码,如下:

val enabler = Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE)
startActivity(enabler)

执行代码,弹出申请是否允许其它设备发现本设备功能弹框,点击允许后,在接下来的120秒内(上限为300秒),其它的蓝牙可在规定时间内扫描到当前手机蓝牙。
在这里插入图片描述

取消扫描

取消蓝牙扫描时应先判断是否为null、是否正在扫描蓝牙后再取消扫描,同时注销掉广播。

if (bluetoothAdapter != null && bluetoothAdapter!!.isDiscovering) {
    bluetoothAdapter!!.cancelDiscovery()
    context.unregisterReceiver(bluetoothReceiver)
}

获取蓝牙信息

找到蓝牙设备后可得到一个任意类型T,将其转换为BluetoothDevice,就可以通过这一个类来获取蓝牙的相关信息,下图中使用到的方法如下:

Methods Describe
getName() 获取蓝牙名称
getType() 获取蓝牙类型
getBondState() 获取蓝牙的绑定状态
getAddress() 获取蓝牙设备的硬件地址
getUuids() 返回远程设备支持的功能 (UUID)

其效果如下:
在这里插入图片描述

其它获取蓝牙相关信息方法见:BluetoothDevice

蓝牙配对

配对

蓝牙的配对使用BluetoothDevice.createBond()进行配对,createBond()有一个返回值,但千万不要以为他返回true意味着蓝牙配对成功,返回true只是意味着它开始申请配对,届时双方的蓝牙都会弹出如下对话框提示是否配对,此刻点击取消才是真正的取消配对,即配对失败。

在这里插入图片描述
部分蓝牙,如蓝牙耳机、部分手机蓝牙,配对成功后就直接连接上了,不需要再执行连接的操作。关于连接、数据传输的操作会在下一章进行讲解,博主暂时还缺乏能够进行数据传输的蓝牙设备,还请见谅。

取消配对

蓝牙配对与取消配对的方法createBond()removeBond()都在BluetoothDevice里面,但调用方式却有着天壤之别。

createBond()可通过BluetoothDevice类的实例直接进行调用,removeBond()通过BluetoothDevice实例来进行调用是行不通的,这一切的始作俑者,都是因为removeBond()@SystemApi注解导致的。

在这里插入图片描述
但这并不意味无法调用removeBond()方法,针对这种情况,可以使用反射来强制调用removeBond()方法,如下所示:

try {
     val method = BluetoothDevice::class.java.getMethod("removeBond")
     val b = method.invoke(device) as Boolean
 } catch (e: Exception) {
     e.printStackTrace()
 }

获取已配对蓝牙

获取已经配对的蓝牙设备,得拿到BluetoothAdapter类的实例,调用getBondedDevices()即可获得一个Set<BluetoothDevice>类型的已配对蓝牙数据。

val bondedDevices: Set<BluetoothDevice> get() = getBluetoothAdapter()!!.bondedDevices

在这里插入图片描述

最终效果

由于


注意事项:
1、低功耗蓝牙不能兼容(连接)经典蓝牙,只能兼容双模蓝牙、低功耗蓝牙。
2、BluetoothAdapter.getDefaultAdapter()方法在API 31 中已废弃,可使用BluetoothManager.getAdapter()代替。
3、本项目目前只做到蓝牙扫描与配对,数据的传输因缺少对应的蓝牙设备而暂时停滞,后期有相关设备后将会补上数据传输相关的文章。

点击前往下载源码

参考文档:
1、Android Bluetooth 连接
2、Android Developers —— Bluetooth overview(蓝牙概述)
3、Android Developers —— Bluetooth permissions(蓝牙权限)
4、Android Developers —— Manifest.permission(蓝牙清单权限)
5、Android Developers —— Find Bluetooth devices(查找蓝牙设备)
6、Android Developers —— Connect Bluetooth devices(连接蓝牙设备)

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【Android】Bluetooth(蓝牙)连接与数据传输(一) 的相关文章

  • for循环中更新JLabel的问题

    我的程序的想法是从之前在其他 JFrame 中保存的列表中选择一个名称 我想在标签中一个接一个地打印所有名称 它们之间有很小的延迟 然后停在其中一个名称上 问题是lbl setText String 如果有多个则不起作用setText co
  • Java 中如何验证字符串的格式是否正确

    我目前正在用 Java 编写一个验证方法来检查字符串是否是要更改为日期的几种不同格式之一 我希望它接受的格式如下 MM DD YY M DD YY MM D YY 和 M D YY 我正在测试第一种格式 每次它都告诉我它无效 即使我输入了有
  • Time.valueOf 方法返回错误值

    我使用 Time valueOf 方法将字符串 09 00 00 转换为 Time 对象 如下所示 Time valueOf LocalTime parse 09 00 00 当我调用 getTime 来显示我得到的值时 28800000
  • ActiveMQ JNDI 查找问题

    尝试使用 JNDI 运行以下 ActiveMQ http activemq apache org jndi support html http ActiveMQ 20JNDI 并且我的 jboss server node lib 文件夹中有
  • 错误膨胀类 android.support.design.widget.NavigationView [启动时崩溃]

    该应用程序应该有一个导航抽屉 可以从左侧拉出并显示各种活动 但是一旦将导航栏添加到 XML Activity homescreen 文档中 应用程序一启动就会崩溃 主屏幕 java package com t99sdevelopment c
  • Eclipse 在 Android SDK 内容加载器处挂起

    我已经在 OS X 10 8 2 上使用 Eclipse 4 2 Juno 版本 20120920 0800 几个星期了 为 Android 3 0 及更高版本构建应用程序 我有一台带 SSD 的四核 i7 MacBook Pro 因此性能
  • Janusgraph 0.3.2 + HBase 1.4.9 - 无法设置 graph.timestamps

    我在 Docker 容器中运行 Janusgraph 0 3 2 并尝试使用运行 HBase 1 4 9 的 AWS EMR 集群作为存储后端 我可以运行 gremlin server sh 但如果我尝试保存某些内容 我会得到粘贴在下面的堆
  • 获取 Future 对象的进度的能力

    参考 java util concurrent 包和 Future 接口 我注意到 除非我弄错了 只有 SwingWorker 实现类才能启动冗长的任务并能够查询进度 这就引出了以下问题 有没有办法在非 GUI 非 Swing 应用程序 映
  • Android 导航组件 - 从“任何地方”/基本片段导航?

    我正在开发一个应用程序 它有一个奇怪的花招 可以在设备旋转时打开特定的片段 在实现 android 的导航组件之前 所需要的只是对当前活动的引用 并且可以在特定时刻向用户显示的任何内容之上执行手动片段事务 但是在转移到导航组件之后 我发现很
  • Spring - 如何在不匹配列名的情况下使用 BeanPropertyRowMapper

    我正在开发一个应用程序 该应用程序已使用行映射器从纯 JDBC 转换为 Spring 模板 我遇到的问题是数据库中的列与属性名称不匹配 这阻止我使用BeanPropertyRowMapper容易地 我看到一些关于在查询中使用别名的帖子 这会
  • 如何在 LazyColumn 底部添加空白区域?

    我想添加 LazyColumn 的空白底部 并且我想允许用户调出底部元素 我怎样才能实现这个 Example LazyColumn modifier Modifier fillMaxWidth height 300 dp border 2
  • Android Studio - 无法解析符号“firebase”

    我目前正在将应用程序升级到新的 Firebase 版本 我按照指南进行操作 包括classpath com google gms google services 3 0 0 在我的项目 build gradle 的依赖项中以及compile
  • 通过 Google 帐户从 Google Play 安装的应用程序列表

    以下是我的问题 是否可以通过使用任何 api 以编程方式通过 Google Play 获取用户已安装的应用程序列表 请注意 我并不是询问设备中当前安装的应用程序列表 而是询问在某个时刻已安装的应用程序列表 我需要解决上述问题 因为我正在考虑
  • Dart/Flutter 如何编译到 Android?

    我找不到任何具体的资源 Dart 是否被编译到 JVM 或者 Google 的团队是否编译了 Dart VM 以在 JVM 上运行 然后在 JVM 内的 Dart VM 中运行 Dart 前者更有意义 并且符合 无桥 的口号 但后者似乎更符
  • Firebase:用户注册后如何进行电话号码验证?

    所以我知道我可以使用电子邮件验证或电话号码验证 但我想做的是在用户注册或登录后进行电话号码验证 如何连接这两种身份验证方法 最后 Firebase中是否有一个函数可以检查用户是否通过电话号码验证 谢谢 即使用户已通过身份验证 您仍然可以使用
  • java Web应用程序中的日期转换

    String date1 13 03 2014 16 56 46 AEDT SimpleDateFormat sdf new SimpleDateFormat dd MM yyyy HH mm ss z sdf setTimeZone Ti
  • 具有隐式授权的 OAuth 应用程序中的客户端模拟

    来自 OAuth 草案 隐式section https datatracker ietf org doc html draft ietf oauth v2 31 section 1 3 2 在隐式授权流程期间发出访问令牌时 授权服务器不对客
  • 如何在android中将文本放在单选按钮的左侧

    我想将单选按钮的文本放在左侧而不是右侧 我找到了这个解决方案
  • Recyclerview项目点击涟漪效果[重复]

    这个问题在这里已经有答案了 我正在尝试添加Ripple影响到RecyclerView的项目 我在网上查了一下 但找不到我需要的东西 我努力了android background归因于RecyclerView本身并将其设置为 android
  • 从 Dropbox 下载文件并将其保存到 SDCARD

    现在我真的很沮丧 我想从 Dropbox 下载一个文件并将该文件保存到 SD 卡中 我得到的代码为 private boolean downloadDropboxFile String dbPath File localFile throw

随机推荐

  • 保持工作稳定情绪与心理健康的八大秘诀

    近期发生的新闻热点再度引发公众对稳定情绪和心理健康的关注 有时候我们遇到的最大的敌人 不是运气也不是能力 而是失控的情绪和口无遮拦的自己 如何在工作中保持稳定的情绪 谈谈你的看法 在工作中保持稳定的情绪对于个人的心理健康和工作效率都至关重要
  • 并行单边jacobi算法 奇偶序列

    单边jacobi算法大家都非常熟悉 就是不停地计算旋转矩阵 简单说就是计算c和s 然后旋转 然而其中做一轮旋转 任何两列都需要旋转一次 需要n n 1 2次单独的旋转 这样的旋转其实是可以并行来实现的 这也就是为何jacobi算法最近比较热
  • SpringDataRedis 使用

    1 SpringDataRedis 特点 2 使用 SpringDataRedis 步骤 3 自定义 RedisTemplate 序列化 4 SpringDataRedis 操作对象 1 SpringDataRedis 特点 提供了对不同
  • C#开发学习~~~Console.WriteLine()

    前言 奥利给 冲冲冲 概述 Console WriteLine 是system名称空间中Console类中的一个方法 用于向控制台写入字符串并换行 其格式项采用如下形式 index alignment formatString index
  • 关于返回值RESULT

    关于返回值RESULT HRESULT Here s the RESULT 值分成32位值 HRESULT值中16到30这15个比特位包含的是设备代码 设备代码标识的是可以返回HRESULT返回代码的操作系统部分 由于Windows操作系统
  • P1089 津津的储蓄计划

    题目描述 津津的零花钱一直都是自己管理 每个月的月初妈妈给津津300300元钱 津津会预算这个月的花销 并且总能做到实际花销和预算的相同 为了让津津学习如何储蓄 妈妈提出 津津可以随时把整百的钱存在她那里 到了年末她会加上20 20 还给津
  • Spring 事件发布

    前言 事件发布是 Spring 框架中最容易被忽视的功能之一 但实际上它是一个很有用的功能 使用事件机制可以将同一个应用系统内互相耦合的代码进行解耦 并且可以将事件与 Spring 事务结合起来 实现我们工作中的一些业务需求 和 Sprin
  • 各大知名游戏引擎分析报告

    游戏引擎之争就像编程语言之争一样 在游戏开发圈永远是一个火爆的话题 目前市面上主流的一些游戏引擎 我们来给他们做一些比较 了解他们的历史 特点 为了严谨 备注一下写这个文章的时间编写时间是2021年4月20日 目前国内主流在用的游戏引擎有
  • git未在指定分支修改,保存并切换到正确分支(解决未在指定分支修改问题)

    我们在开发一个需求的时候 可能会忘记切换到开发分支上面 下面是切换到正确分支并保存修改的操作 1 将修改的代码暂存到stash git stash 2 切换到正确的分支 git checkout 正确的分支 3 从stash中取出暂存的代码
  • oracle修改临时表出现已使用的事务正在处理临时表问题

    错误提示 ORA 14450 试图访问已经在使用的事务处理临时表 解决方法 通过第一句sql来查找临时表的object id 然后代入第二局sql来生成第三句sql语句 最后再执行第三句sql语句即可kill session 执行修改表的操
  • 查看Quartz 调度任务 job 的状态

    首先 明确一点什么是 jobkey JobKey jobkey new JobKey name group jobkey相当于一把钥匙连接 所有从 schedule 中 获取 信息的钥匙 如果想获取 初始化信息 则 scheduler ge
  • 使用git rebase压缩提交(commits)

    我使用 git 有一段时间了 但老实说 我很少关注凌乱的提交历史 最近在学习 git rebase 想分享一下如何使用这个命令来压缩整理提交 Commits 五步完成 简而言之 总共有五个步骤 运行git rebase i head x x
  • 【具有路由 WSN 模拟器的随机方式移动】具有路由 WSN 模拟器的随机方式移动(Matlab代码实现)

    欢迎来到本博客 博主优势 博客内容尽量做到思维缜密 逻辑清晰 为了方便读者 座右铭 行百里者 半于九十 本文目录如下 目录 1 概述 2 运行结果 3 参考文献 4 Matlab代码实现 1 概述 经过我们的研究和实践 我们提出了一种具有路
  • vim: 未找到命令

    解决 一键配置所有 yum y install vim 检查是否安装 rpm qa grep vim 出现以下的的命令即安装完毕
  • 如何提高链表的查询效率

    使用跳表 1 链表的变形 跳表 跳表是一种我们不常见的数据结构 但由于其优秀的特性 在工业中 常常被用来代替红黑树 进行查找 插入和删除 Redis的有序集合就是用跳表来实现的 跳表本质上是采用 二分 的思路来改造链表 所以这要求链表必须是
  • HCIP-H12-221练习题

    HCIP H12 221练习题 习题1 由于属性AS PATH不能在AS内起作用 所以规定BGP路由器不会宣告任何从IBGP对等体来的更新信息给其IBGP对等体 A 正确 B 错误 答案 A 习题2 通过重发布命令注入BGP的路由 其Ori
  • Unity3D中的JavaScript语言基础

    Unity中的JS 也称UnityScript 和基于浏览器的JS有比较大的区别 因为UnityScript是基于Mono的 net 的IL语言规范 CLR运行环境 Mono虚拟机 上设计的语言 0 基本概念 Unity3d中的脚本可以与游
  • 使用Matlab对传递函数进行z变换

    一 建立传递函数 在这里插入代码片 s tf s Gc 100 s s 100 二 进行z变换 在这里插入代码片 c2d Gc 0 01 z dsys c2d sys ts method 传函离散 这里面的method有好多种 zoh 零阶
  • (二)Docker部署Tomcat及Web应用

    Docker部署Tomcat及Web应用 这里只拉起一个Tomcat容器 运行一个简单的web项目 确保整个docker可以正常运行 查看Tomcat镜像 docker search tomcat 下载下来官方的镜像Starts最高的那个
  • 【Android】Bluetooth(蓝牙)连接与数据传输(一)

    目录 简介 权限声明 蓝牙扫描 开始扫描 取消扫描 获取蓝牙信息 蓝牙配对 配对 取消配对 获取已配对蓝牙 最终效果 简介 蓝牙技术是一种无线数据和语音通信开放的全球规范 它是基于低成本的近距离无线连接 为固定和移动设备建立通信环境的一种特