Android Bluetooth 与 Headset 通信

Android Bluetooth

本文主要记录 Android 与蓝牙设备通信过程的整个流程,并对流程中的一些坑给出相应的解决思路。
本文中的通信设备是蓝牙耳机,其他蓝牙设备整体思路及流程类似,视具体情况稍加调整

最近手上有个项目是基于移动端 App 与蓝牙耳机通信的,死磕一番发现蓝牙真的是...

for (int i = 0; i < 10000; i ++) {
    fuck("Android Bluetooth");
}

下图是项目完成后整理的一份流程表,希望对大家有帮助

Android 蓝牙连接流程

通过图中所示流程相信大部分开发者都能清楚的了解到蓝牙的整个连接过程,但是为什么要画这张图呢?是因为在图中星标的这些位置需要引起大家注意

获取蓝牙适配器

这是所有蓝牙开发的第一步

在 Android API 17 及之前的版本中,需要通过 BluetoothAdapter 的 getDefaultAdapter() 函数进行获取

在 Android API 18 开始可以通过 BluetoothManager 这个类的 getAdapter() 函数获取到 BluetoothAdapter 对象

判断蓝牙开关

既然要用蓝牙,你得保证它开着啊

开关检测可以通过 BluetoothAdapter 的 isEnabled() 函数进行检测,返回 true 表示已开启,false 未开启,如果 BluetoothAdapter 为 null 则表示设备不支持蓝牙

如果蓝牙未开启,这个时候就需要申请开启蓝牙

这里特别说明,不要使用 mBluetoothAdapter.enable() 这句代码来开启蓝牙,在 Android 动态授权的机制出来后,这句代码在某些高版本的系统中无效,这对用户体验是致命的,就好像你一拳打出去打在空气中一样?当然,如果你觉得用户体验没个屌用就请原谅在下这顿逼逼叨

为了保证通过较好的用户体验来打开蓝牙,可以使用如下代码申请

// REQUEST_ENABLE_BT 是定义的局部变量,在 onActivityResult 会通过 requestCode 变量返回该值,用于识别操作类型
Intent enableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableIntent, REQUEST_ENABLE_BT);

然后在 Activity 的 onActivityResult() 函数中处理用户的决定

扫描设备

扫描设备可以通过 BluetoothAdapter 的 startDiscovery() 开启扫描设备,千万要记得在发现目标设备之后立即调用 cancelDiscovery() 关闭。Google 官方也强调过扫描蓝牙设备是一个比较耗的过程,所以能省则省

扫描到的设备会以广播的形式回调给设备,所以我们需要注册 BluetoothDevice.ACTION_FOUND 的监听器,在这里便可以获取到目标设备的 BluetoothDevice 类

设备校验

这里的设备校验我采用的是通过名称匹配的方式,我们的蓝牙耳机出厂的设备名称是有一定规律的,如:HeadsetHK_38913、HeadsetHK_81390、HeadsetHK_34698

那我只要找到以 HeadsetHK_ 开头的设备就尝试进入下面的流程

可能有些同学会觉得这里的处理不够严谨,那是得看使用场景来的

如果用户正常想要使用我们的蓝牙耳机的场景下,他周围刚好有一个名字同样以 HeadsetHK_ 开头的设备并且又不是我们的产品,这种概率大家自己掂量

为什么会选用这个不够严谨的方式来进行校验,是因为在此之前,我也是希望能够蓝牙设备蓝牙设备能够给我一个反馈,告诉 app 我是自己人,快拉我上车

但是通过 BluetoothSocket 去连接设备的时候发现一个问题,蓝牙不稳定,而且部分手机有些通信堵塞的感觉

在蓝牙设备开机的情况下,BluetoothSocket 连接可能 3~5s 便可以进行通信,但是如果设备关机了,BluetoothSocket 连接的过程可能需要等上15~30s 才能给出反馈

当然针对不同的使用场景需要采用不同的方案,这里只是将我的思路给出来分享给大家

设备配对

在 API 19 中,BluetoothDevice 类新增了 createBond() 函数,可用于主动配对设备,但是在此之前的版本中,我们需要通过反射的机制来进行主动配对。代码如下

// 传入的 device 就是希望配对的 BluetoothDevice 类
Method bond = BluetoothDevice.class.getMethod("createBond");
bond.invoke(device);

连接设备

再看看上面的流程图,为什么我要将扫描设备、设备校验、连接设备和数据通信用星号标出?

扫描设备是因为比较耗,需要强调;设备校验是为了提升能够成功建立连接的准确度;

那连接设备和数据通信为什么要单独拧出来说明?而且还分为两个步骤?

按照我没有了解之前,自以为连接成功就可以进行通信了,然并卵

大家记得 Android 系统自带的蓝牙设置界面中,为什么有些系统在点击扫描到的设备列表之后,点击目标设备第一次是设备配对,而第二次点击才是连接设备?

因为每个蓝牙设备都是由0或者多个组件构成的,这些组件有音视频、拍照、定位等各个功能,而且每个设备都有一个主要和次要的组件

像蓝牙耳机主要组件肯定就是音频,但是你不能肯定的说市面上所有蓝牙耳机都没有定位组件

说这些是为了告诉大家,既然蓝牙模块其实是N个功能组件的集合,那我们是可以单独与蓝牙设备中的某一个功能组件建立连接的

同样的,如果希望发起与蓝牙设备的连接,也要使用反射

Method connect = btHeadsetCls.getMethod("connect", BluetoothDevice.class);
connect.setAccessible(true);
connect.invoke(bluetoothHeadset, device);

不论是配对还是连接的反射返回,都会返回一个 boolean 值告诉我们成功或者失败,但是我可以确切的告诉大家,这两个值是不可靠的,那这个时候我们怎么才能知道蓝牙设备与我们的 App 连接成功呢?

需要监听 BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED,该广播会反馈蓝牙连接的当前状态

我们只需要在收到广播之后,作出对应的 UI 改变即可

数据通信

确保设备连接成功之后,就需要进行数据通信了

具体的通信方式这里不再赘述,主要使用到的是 BluetoothDevice 类中的 createRfcommSocketToServiceRecord() 创建连接然后进行读取

这里直接分享给大家一个网上找到工具类

<a href='https://www.devtool.top/article/13' target='_blank'>BluetoothChatUtil 蓝牙通信连接工具类下载</a>


后话

其实文中还有很多没有提及到的细节。例如对蓝牙广播的监听是否应该放到 service 中?当用户在下拉菜单或者是转到系统设置界面中进行蓝牙设置的时候,我们 app 是接收不到广播的。

本文没有对蓝牙权限获取的步骤进行说明,直接从获取蓝牙适配器开始。后面会陆续整理出相应的文章希望大家关注

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,013评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,205评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,370评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,168评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,153评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,954评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,271评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,916评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,382评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,877评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,989评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,624评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,209评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,199评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,418评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,401评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,700评论 2 345

推荐阅读更多精彩内容