Android 蓝牙开发实践笔记

本文基于传统蓝牙开发。

先来梳理一下蓝牙开发的逻辑,基本分为以下几步:搜索设备,配对设备,连接设备,传输数据。前三步主要借助Android提供的蓝牙相关的API实现,最后一步是基于Socket的,类似于TCP协议传输数据,不过用的并不是普通Java类中的Socket,而是蓝牙专用的BluetoothServerSocket和BluetoothSocket,类的模型和TCP协议中的ServerSocket和Socket类似。

首先要清楚,蓝牙设备传输之前要进行设备的发现和配对,这两步我们可以调用系统设置界面完成或者自己实现,调用系统设置代码如下:

Intent intent = new Intent(Settings.ACTION_BLUETOOTH_SETTINGS);
startActivity(intent);

为了加深对开发流程的了解,我们这里自己手动实现以下开启蓝牙并搜索配对的逻辑。

第一步要有相关权限:

<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

前两个是蓝牙相关的,不需要动态申请,后面4个都需要动态申请。第3和第4个权限如果不申请的话是搜索不到周围设备的,最后两个是为了传输文件用的。

蓝牙几乎所有操作都要用到BluetoothAdapter这个类,获取方法:

bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();

1.打开关闭蓝牙与状态更新

获取到之后,我们第一步可以判断蓝牙状态并在需要时打开,蓝牙打开关闭的代码:

bluetoothAdapter.enable();  //打开蓝牙
bluetoothAdapter.disable();  //关闭蓝牙

有一点需要注意的时,使用enable()虽然可以打开蓝牙,但并不能保证在所有系统上都有效,因为这种方法不带任何提示,不太友好。所以还有另外一种带提示的打开方法:

Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivity(intent);

由于打开关闭蓝牙都是需要时间的,所以蓝牙的状态和wifi类似一共有四种:

BluetoothAdapter.STATE_ON  //已打开
BluetoothAdapter.STATE_TURNING_ON //打开中
BluetoothAdapter.STATE_OFF //已关闭
BluetoothAdapter.STATE_TURNING_OFF //关闭中

通过下面方法可以随时获取到蓝牙的状态:

bluetoothAdapter.getState()

这个方法一般用于界面的初始化,如要动态的更新,需要注册广播接受者实现,首先注册下面的广播:

BluetoothAdapter.ACTION_STATE_CHANGED

在广播接受者中获取状态并更新

case BluetoothAdapter.ACTION_STATE_CHANGED:
      int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE,BluetoothAdapter.STATE_OFF);
      updateState(state);
      break;

2.发现周围设备

在确保蓝牙开启后,我们可以搜索周围的蓝牙设备,开启搜索代码如下:

bluetoothAdapter.startDiscovery();

另外我们可以判断是否处于搜索状态,并决定是开启搜索或者是结束搜索

if (bluetoothAdapter.isDiscovering())
    bluetoothAdapter.cancelDiscovery();
else
    bluetoothAdapter.startDiscovery();

为了不断获得已被搜索到的设备我们需要注册下面的广播:

BluetoothDevice.ACTION_FOUND

注册之后,如果有ACCESS_FINE_LOCATION权限,每搜索到一个设备,就会收到一个广播,这样我们就能更新我们的列表

BluetoothDevice deviceFound = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
if (!listNearby.contains(deviceFound)) {
       listNearby.add(deviceFound);
       adapterNearby.notifyDataSetChanged();
}

由于搜索过程比较耗电,也会减慢蓝牙传输速度,所以我们并不能决定一次搜索的时长,不同的设备一次搜索的时间也不一样,所以我们需要知道搜索是否结束,可以注册下面的广播,实现动态更新:

BluetoothAdapter.ACTION_DISCOVERY_FINISHED
BluetoothAdapter.ACTION_DISCOVERY_STARTED

两个广播分别在搜索开始和结束时发出。

3.设备配对及取消配对

配对实现比较简单,先拿到要配对设备的BluetoothDevice实例,然后一行代码

device.createBond()

由于配对是异步操作,我们若需要及时获得配对状态,需要注册下面广播:

BluetoothDevice.ACTION_BOND_STATE_CHANGED

当设备配对状态发送改变时会发出次广播,共有三种状态

BluetoothDevice.BOND_NONE  //未配对
BluetoothDevice.BOND_BONDING  //配对中
BluetoothDevice.BOND_BONDED  //已配对

此外我们还可以获得已配对的设备列表:

bluetoothAdapter.getBondedDevices()

已配对的设备不必经过搜索,可以直接连接。

若想取消配对,暂时没有提供可用的方法调用,不过我们可以通过反射实现:

try {
      Method method = BluetoothDevice.class.getMethod("removeBond");
      method.invoke(device);
} catch (Exception e) {
      e.printStackTrace();
}

4.设备可见

蓝牙设备有可见和不可见两种状态,不可见的设备能搜索周围的设备,但不能被周围设备发现,一般只启动搜索时,设备为不可见状态,若要被其他设备搜索到,我们要更改设备可见性:

Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
intent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION,3000);
startActivity(intent);

其中BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION指的是可见时间,单位为秒,最大为300秒。

5.设备连接与数据传输

二者其实是一回事,要传输数据就要先建立连接,设备配对只是建立连接的前提,配对类似于记住该设备而已,连接是为了传输数据。

蓝牙传输虽然用到了BluetoothServerSocket和BluetoothSocket,看着像是基于Socket,但是并没有什么关系,只是开发者为了便于我们开发,将模型做的和TCP类似而已。蓝牙是用MAC地址和UUID作为标志建立连接的,类似于IP地址和端口号。深层次的蓝牙传输协议我们这里并不深究,在Android上提供的接口已经极大地屏蔽的底层的细节,是我们开发变的非常方便。

既然连接与传输的模型类似于TCP,那么BluetoothServerSocket就代表一个服务器实例,BluetoothSocket代表一个传输实例。简单来说就是服务端BluetoothServerSocket接受一个BluetoothSocket进行数据接受,客户端向服务端发送一个BluetoothSocket进行数据传递。

一般可以在应用启动时就建立一个服务端,开启一个死循环,不断监听客户端的请求,不过要注意阻塞的处理。在发送数据时,利用子线程建立一个BluetoothSocket发送即可。

在服务端,BluetoothServerSocket通过BluetoothAdapter的下面方法获取:

public BluetoothServerSocket listenUsingRfcommWithServiceRecord(String name, UUID uuid)

name代表服务的可识别名称,可以任意指定,系统会将其写入数据库,UUID是一个标识符,这个比较重要,只有两端的UUID一样,连接才能建立,一般我们可以通过下面静态方法生成一个:

public static UUID fromString(String name)

在客户端BluetoothSocket可以通过BluetoothDevice的下面方法获取:

public BluetoothSocket createRfcommSocketToServiceRecord(UUID uuid)

可见这里只需传入对应的UUID 即可。

在服务端,BluetoothServerSocket有一个阻塞方法accept()来监听连接请求,BluetoothSocket有一个阻塞方法connect()来建立连接,这里就和TCP模型类似了,接下来BluetoothSocket还有getInputStream()和getOutputStream()方法进行数据收发,基本和TCP实现一样,这里就不贴出代码了,详细可见我的一个简单demo

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

推荐阅读更多精彩内容