蓝牙是一种无线技术标准,可实现固定设备、移动设备和楼宇个域网之间的短距离数据交换,传输距离近、低功耗
蓝牙学习笔记
基础知识
蓝牙是一种无线技术标准,可实现固定设备、移动设备和楼宇个域网之间的短距离数据交换(使用2.4—2.485GHz的ISM波段的UHF无线电波)。蓝牙技术最初由电信巨头爱立信公司1994年创制,当时是作为RS232数据线的替代方案。蓝牙可连接多个设备,克服了数据同步的难题。蓝牙是一个标准的无线通讯协议,基于设备低成本的收发器芯片,传输距离近、低功耗
蓝牙使用跳频技术,将传输的数据分割成数据包,通过79个指定的蓝牙频道分别传输数据包。每个频道的频宽为1 MHz。蓝牙4.0使用2 MHz 间距,可容纳40个频道
最初,高斯频移键控GFSK调制是唯一可用的调制方案。然而蓝牙2.0+EDR 使得 π/4-DQPSK和8DPSK 调制在兼容设备中的使用变为可能
蓝牙是基于数据包、有着主从架构的协议。一个主设备至多可和同一微微网中的七个从设备通讯。所有设备共享主设备的时钟
蓝牙Profile
Bluetooth的一个很重要特性,就是所有的Bluetooth产品都无须实现全部的Bluetooth规范。为了更容易的保持Bluetooth设备之间的兼容,Bluetooth规范中定义了Profile。Profile定义了设备如何实现一种连接或者应用,你可以把Profile理解为连接层或者应用层协议
在所有的Profile中,有四种是基本的Profile,这些Profile会被其它的Profile使用:
GAP
Generic Access Profile,该Profile保证不同的设备可以互相发现对方并建立连接,它具有强制性,并作为所有其它蓝牙应用规范的基础SDAP
Service Discovery Application Profile,通过该Profile,一个设备可以找到其它设备提供的服务,以及查询相关的信息SPP
Serial Port Profile,定义了如何在两台设备之间建立虚拟串口并进行连接GOEP
Generic Object Exchange Profile,通用对象交换。这个Profile的名字有些费解,它定义的是数据的传输,包括同步,文件传输,或者推送其它的数据。可以理解为与内容无关的传输层协议,可以被任何应用用来传输自己定义的数据对象A2DP
Advenced Audio Distribution Profile,蓝牙音频传输模型协定。A2DP 规定了使用蓝牙非同步传输信道方式,传输高质量音乐文件数据的协议堆栈软件和使用方法,基于该协议就能通过以蓝牙方式传输高品质的音乐了。这个技术可以利用立体声蓝牙耳机来收听手机中的音乐了DUN
Dial-up Networking Profile,实现一台蓝牙设备通过另外一个带无线功能的蓝牙设备共享上网GATT
Generic Attribute Profile,是一个在蓝牙连接之上的发送和接收很短的数据段的通用规范,用于低功耗蓝牙BLE
BLE
BLE(Bluetooth Low Energy),蓝牙4.0核心特性,Android4.3以上才支持,主要特点是快速搜索,快速连接,超低功耗保持连接和数据传输,缺点:数据传输速率低,由于其具有低功耗特点,所以经常用在可穿戴设备之中
- Profile:可以理解为一种规范,一个标准的通信协议,其存在于手机中,蓝牙组织规定了一些标准的Profile,每个Profile中包含了多个Service
- Service:可以理解为一个服务,在BLE从机中有多个服务,电量信息,系统服务信息等,每一个Service中包含了多个Characteristic特征值,每一个具体的Characteristic特征值才是BLE通信的主题
- Characteristic特征值:BLE主机从机通信均是通过Characteristic进行,可以将其理解为一个标签,通过该标签可以读取或写入相关信息
- UUID:Service和Characteristic均需要这个唯一的UUID进行标识,识别符有通用的,也可以自定义,也可以随机生成,每一个特征都有其属性和权限
READ
/WRITE
/NOTIFY
/INDICATE
,特征根据属性可读可写
任何设备都可以单独作为中心或外设角色。一个没有被链接的外设角色,会向外界发出广播,这个时候可以被多个中心角色发现,一旦外设角色被某个中心角色链接后,外设角色就会停止广播,其他中心角色就无法在链接到这个外设角色。中心角色可以扫描外设角色,可以监听接收广播或主动链接,一个中心角色可以与多个外设同时链接。
Android蓝牙开发
首先在AndroidManifest.xml中加入权限
1 | <uses-permissionandroid:name="android.permission.BLUETOOTH_ADMIN" /> |
常用类
BluetoothAdapter
蓝牙适配器,可以通过它进行基础的蓝牙任务- 初始化设备发现
- 获得配对的设备列表
- 使用MAC初始化蓝牙设备
- 初始化Socket监听蓝牙连接
- 扫描BLE设备等
BluetoothDevice
蓝牙设备BluetoothServerSocket
蓝牙服务端Socket,类似TCP,服务端使用一个BluetoothServerSocket监听连接请求,接受连接后返回一个新的BluetoothSocket用于通讯,客户端使用一个单独的BluetoothSocket进行连接,最常见的蓝牙Socket是RFCOMM
,是Android支持的,RFCOMM
是一个蓝牙上面向连接的流传输,也被称为SPP
BluetoothSocket
蓝牙客户端Socket
基础开发流程
获得蓝牙适配器
API17
及以下
1 | BluetoothAdapter mAdapter = BluetoothAdapter.getDefaultAdapter(); |
API17
以上
1 | BluetoothManager bm = (BluetoothManager) mContext.getSystemService(Context.BLUETOOTH_SERVICE); |
打开蓝牙
1 | if (!mBluetoothAdapter.isEnabled()) { |
获取配对设备
1 | Set<BluetoothDevice> devices = mBluetoothAdapter.getBondedDevices(); |
搜索设备
搜索设备得到的结果会通过广播通知,所以先注册广播接收器,然后开始搜索
1 | IntentFilter filter = new IntentFilter(); |
1 | mBluetoothAdapter.startDiscovery(); |
配对
1 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { |
同样在广播中检测配对状态
建立连接
建立连接会阻塞线程,需要在新线程中进行
服务端和客户端需要指定相同的UUID才能连接
服务端
1 | BluetoothServerSocket serverSocket = mAdapter.listenUsingRfcommWithServiceRecord(serverSocketName, UUID); |
客户端
1 | BluetoothSocket clienSocket = dcvice.createRfcommSocketToServiceRecord(UUID); |
数据传输
获取输入输出流
1 | inputStream = socket.getInputStream(); |
其他
1 | if (mAdapter.getProfileConnectionState(BluetoothProfile.A2DP) == BluetoothProfile.STATE_CONNECTED) { |
getProfileConnectionState()
获得某种特定协议的连接状态,比如判断是不是有蓝牙音箱设备连接
getProfileProxy()
获得协议的代理对象
BluetoothProfile.getConnectedDevices()
获得该协议连接的设备
BLE开发
检查是否支持BLE
1 | getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE); |
对于6.0
以上,需要动态申请一个定位权限
1 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { |
扫描设备
通用的搜索方法可以搜索所有的蓝牙设备,这里只扫描BLE设备
1 | private BluetoothAdapter.LeScanCallback mLeScanCallback= new BluetoothAdapter.LeScanCallback(){ |
这是个过时的方法,但仍可用
1 | private ScanCallback mScanCallback = new ScanCallback() { |
所有搜索都会对流畅性有影响,都需要在子线程进行Android4.3
开始支持BLE,Android5.0
开始支持手机端作为从设备
连接BLE设备
1 | private BluetoothGattCallback mBluetoothGattCallback = new BluetoothGattCallback() { |
数据交互
1 | //读取特征,相当简单,读取结果会回调到mGattCallback中的onCharacteristicRead。 |
系统回调BluetoothGattCallback.onCharacteristicWrite()
方法通知数据已经完成写入。此时,我们需要执行BluetoothGattCharactristic.getValue()
方法检查一下写入的数据是否我们需要发送的数据,如果不是按照项目的需要判断是否需要重发
蓝牙的写入操作,取操作必须序列化进行。写入数据和读取数据是不能同时进行的,如果调用了写入数据的方法,马上调用写入数据或者读取数据的方法,第二次调用的方法会立即返回 false
, 代表当前无法进行操作. 断开连接
BLE蓝牙连接断开非常重要,因为主设备支持的设备数量有限,如果没有释放,后面连接可能总是失败
当蓝牙成功连接之后,通过BluetoothGatt.disconnect()
断开蓝牙的连接,紧接着在 BluetoothGattCallback.onConnectionStateChange()
执行BluetoothGatt.close()
方法释放资源