Android 蓝牙开发实践笔记

2018-04-23  本文已影响0人  留给时光吧

本文基于传统蓝牙开发。

先来梳理一下蓝牙开发的逻辑,基本分为以下几步:搜索设备,配对设备,连接设备,传输数据。前三步主要借助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

上一篇下一篇

猜你喜欢

热点阅读