1. android-Ble蓝牙开发Demo示例–扫描,连接,发送和接收数据,分包解包(附源码)
万物互联的物联网时代的已经来临,ble蓝牙开发在其中扮演着举重若轻的角色。最近刚好闲一点,抽时间梳理下这块的知识点。
涉及ble蓝牙通讯的客户端(开启、扫描、连接、发送和接收数据、分包解包)和服务端(初始化广播数据、开始广播、配置Services、Server回调操作)整个环节以及一些常见的问题即踩过的一些坑。
比如
1、在Android不同版本或不同手机的适配问题,扫描不到蓝牙设备
2、如何避免ble蓝牙连接出现133错误?
3、单次写的数据大小有20字节限制,如何发送长数据
蓝牙有传统(经典)蓝牙和低功耗蓝牙BLE(Bluetooth Low Energy)之分,两者的开发的API不一样,本文主讲Ble蓝牙开发,传统蓝牙不展开,有需要的可以自行了解。
相对传统蓝牙,BLE低功耗蓝牙,主要特点是快速搜索,快速连接,超低功耗保持连接和数据传输。
客户端
服务端
Android4.3(API Level 18)开始引入BLE的核心功能并提供了相应的 API。应用程序通过这些 API 扫描蓝牙设备、查询 services、读写设备的 characteristics(属性特征)等操作。
BLE蓝牙协议是GATT协议, BLE相关类不多, 全都位于android.bluetooth包和android.bluetooth.le包的几个类:
android.bluetooth.
.BluetoothGattService 包含多个Characteristic(属性特征值), 含有唯一的UUID作为标识
.BluetoothGattCharacteristic 包含单个值和多个Descriptor, 含有唯一的UUID作为标识
.BluetoothGattDescriptor 对Characteristic进行描述, 含有唯一的UUID作为标识
.BluetoothGatt 客户端相关
.BluetoothGattCallback 客户端连接回调
.BluetoothGattServer 服务端相关
.BluetoothGattServerCallback 服务端连接回调
android.bluetooth.le.
.AdvertiseCallback 服务端的广播回调
.AdvertiseData 服务端的广播数据
.AdvertiseSettings 服务端的广播设置
.BluetoothLeAdvertiser 服务端的广播
.BluetoothLeScanner 客户端扫描相关(Android5.0新增)
.ScanCallback 客户端扫描回调
.ScanFilter 客户端扫描过滤
.ScanRecord 客户端扫描结果的广播数据
.ScanResult 客户端扫描结果
.ScanSettings 客户端扫描设置
BLE设备分为两种设备: 客户端(也叫主机/中心设备/Central), 服务端(也叫从机/外围设备/peripheral)
客户端的核心类是 BluetoothGatt
服务端的核心类是 BluetoothGattServer 和 BluetoothLeAdvertiser
BLE数据的核心类是 BluetoothGattCharacteristic 和 BluetoothGattDescriptor
下面详细讲解下客户端和服务端的开发步骤流程
安卓手机涉及蓝牙权限问题,蓝牙开发需要在AndroidManifest.xml文件中添加权限声明:
在搜索设备之前需要询问打开手机蓝牙:
注意: BLE设备地址是动态变化(每隔一段时间都会变化),而经典蓝牙设备是出厂就固定不变了!
通过扫描BLE设备,根据设备名称区分出目标设备targetDevice,下一步实现与目标设备的连接,在连接设备之前要停止搜索蓝牙;停止搜索一般需要一定的时间来完成,最好调用停止搜索函数之后加以100ms的延时,保证系统能够完全停止搜索蓝牙设备。停止搜索之后启动连接过程;
BLE蓝牙的连接方法相对简单只需调用connectGatt方法;
参数说明
与设备建立连接之后与设备通信,整个通信过程都是在BluetoothGattCallback的异步回调函数中完成;
BluetoothGattCallback中主要回调函数如下:
上述几个回调函数是BLE开发中不可缺少的;
当调用targetdDevice.connectGatt(context, false, gattCallback)后系统会主动发起与BLE蓝牙设备的连接,若成功连接到设备将回调onConnectionStateChange方法,其处理过程如下:
判断newState == BluetoothGatt.STATE_CONNECTED表明此时已经成功连接到设备;
mBluetoothGatt.discoverServices();
扫描BLE设备服务是安卓系统中关于BLE蓝牙开发的重要一步,一般在设备连接成功后调用,扫描到设备服务后回调onServicesDiscovered()函数,函数原型如下:
BLE蓝牙开发主要有负责通信的BluetoothGattService完成的。当且称为通信服务。通信服务通过硬件工程师提供的UUID获取。获取方式如下:
具体操作方式如下:
开启监听,即建立与设备的通信的首发数据通道,BLE开发中只有当客户端成功开启监听后才能与服务端收发数据。开启监听的方式如下:
BLE单次写的数据量大小是有限制的, 通常是20字节 ,可以尝试通过requestMTU增大,但不保证能成功。分包写是一种解决方案,需要定义分包协议,假设每个包大小20字节,分两种包,数据包和非数据包。对于数据包,头两个字节表示包的序号,剩下的都填充数据。对于非数据包,主要是发送一些控制信息。
监听成功后通过向 writeCharacteristic写入数据实现与服务端的通信。写入方式如下:
其中:value一般为Hex格式指令,其内容由设备通信的蓝牙通信协议规定;
若写入指令成功则回调BluetoothGattCallback中的onCharacteristicWrite()方法,说明将数据已经发送给下位机;
若发送的数据符合通信协议,则服务端会向客户端回复相应的数据。发送的数据通过回调onCharacteristicChanged()方法获取,其处理方式如下:
通过向服务端发送指令获取服务端的回复数据,即可完成与设备的通信过程;
当与设备完成通信之后之后一定要断开与设备的连接。调用以下方法断开与设备的连接:
源码上传在CSDN上了,有需要的可以借鉴。
=====> Android蓝牙Ble通讯Demo示例源码–扫描,连接,发送和接收数据,分包解包
BLE单次写的数据量大小是有限制的,通常是20字节,可以尝试通过requestMTU增大,但不保证能成功。分包写是一种解决方案,需要定义分包协议,假设每个包大小20字节,分两种包,数据包和非数据包。对于数据包,头两个字节表示包的序号,剩下的都填充数据。对于非数据包,主要是发送一些控制信息。
总体流程如下:
1、定义通讯协议,如下(这里只是个举例,可以根据项目需求扩展)
2、封装通用发送数据接口(拆包)
该接口根据会发送数据内容按最大字节数拆分(一般20字节)放入队列,拆分完后,依次从队列里取出发送
3、封装通用接收数据接口(组包)
该接口根据从接收的数据按协议里的定义解析数据长度判读是否完整包,不是的话把每条消息累加起来
4、解析完整的数据包,进行业务逻辑处理
5、协议还可以引入加密解密,需要注意的选算法参数的时候,加密后的长度最好跟原数据长度一致,这样不会影响拆包组包
一般都是Android版本适配以及不同ROM机型(小米/红米、华为/荣耀等)(EMUI、MIUI、ColorOS等)的权限问题
蓝牙开发中有很多问题,要静下心分析问题,肯定可以解决的,一起加油;
2. Android蓝牙开发——实现蓝牙聊天
与蓝牙开发主要的相关类是以下四个
知道对应API后就可以进行对应的蓝牙开发,这里以获取蓝牙设备为例子
}
搜索设备的回调则需要通过注册广播的形式来获取
定义广播
之后就可以进行个人的一些操作
要实现蓝牙聊天则涉及到蓝牙之间的传输通信,前面也说到了,这里肯定就是用到BluetoothServerSocket以及BluetoothSocket。
蓝牙传输通信相当于服务器端与客户端之间的通信,只不过不同是这里每一个蓝牙设备本身自己 既充当服务器端也充当客户端 ,大致的关系就是
注意,这些连接都是阻塞式的,都要放在线程里去执行。
可以看到,当BluetoothServerSocket监听到有设备连接的时候,就会调用dataTransfer开启一个数据传输。
需要一个ConnectThread来发起
之后建立连接之后就会调用dataTransfer来进行数据传输,同样也需要一个线程来维护数据传输
蓝牙聊天则是基于上面三个线程来进行实现,同样,对于蓝牙文件间的传输也是同个道理,通过输入输出流来进行处理。之后的操作就比较容易处理了
蓝牙聊天
Android 蓝牙开发基本流程
3. android蓝牙BLE(三) —— 广播
在蓝牙开发中,有些情况是不需要连接的,只要外设广播自己的数据即可,例如苹果的 ibeacon 。自 Android 5.0 更新蓝牙API后,手机可以作为外设广播数据。
广播包有两种:
其中 广播包是每个外设都必须广播的,而响应包是可选的 。每个广播包的长度必须是 31个字节 ,如果不到 31个字节 ,则剩下的全用 0 填充 补全,这部分的数据是无效的
广播包中包含若干个广播数据单元,广播数据单元也称为 AD Structure 。
广播数据单元 = 长度值Length + AD type + AD Data。
长度值 Length 只占 一个字节 ,并且位于广播数据单元的 第一个字节 。
概念的东西有些抽象,先看看下面的广播报文:
0x代表这串字符串是十六进制的字符串。 两位十六进制数代表一个字节 。因为两个字符组成的十六进制字符串最大为 FF ,即255,而java中byte类型的取值范围是-128到127,刚好可以表示一个255的大小。所以两个十六进制的字符串表示一个字节。
继续查看报文内容,开始读取第一个广播数据单元。读取 第一个 字节: 0x07 ,转换为十进制就是7,即表示后面的7个字节是这个广播数据单元的数据内容。超过这7个字节的数据内容后,表示是一个新的广播数据单元。
而第二个广播数据单元,第一个字节的值是 0x16 ,转换为十进制就是22,表示后面22个字节为第二个广播数据单元。
在广播数据单元的 数据部分 中, 第一个字节 代表 数据类型 (AD type),决定数据部分表示的是什么数据。(即广播数据单元第二个字节为AD type)
AD Type 的类型如下:
这bit 1~7分别代表着发送该广播的蓝牙芯片的物理连接状态。当bit的值为1时,表示支持该功能。
例:
蓝牙广播的数据格式大致讲了一下,有助于下面的广播操作的理解。
先看看广播设置( AdvertiseSettings )如何定义:
(1)、通过 AdvertiseSettings.Builder#setAdvertiseMode() 设置广播模式。其中有3种模式:
(2)、通过 AdvertiseSettings.Builder#setAdvertiseMode() 设置广播发射功率。共有4种功率模式:
(3)、通过 AdvertiseSettings.Builder#setTimeout() 设置持续广播的时间,单位为毫秒。最多180000毫秒。当值为0则无时间限制,持续广播,除非调用 BluetoothLeAdvertiser#stopAdvertising() 停止广播。
(4)、通过 AdvertiseSettings.Builder#setConnectable() 设置该广播是否可以连接的。
之前说过,外设必须广播广播包,扫描包是可选。但添加扫描包也意味着广播更多得数据,即可广播62个字节。
可见无论是广播包还是扫描包,其广播的内容都是用 AdvertiseData 类封装的。
(1)、 AdvertiseData.Builder#setIncludeDeviceName() 方法,可以设置广播包中是否包含蓝牙的名称。
(2)、 AdvertiseData.Builder#setIncludeTxPowerLevel() 方法,可以设置广播包中是否包含蓝牙的发射功率。
(3)、 AdvertiseData.Builder#addService UUID (Parcel UUID ) 方法,可以设置特定的 UUID 在广播包中。
(4)、 AdvertiseData.Builder#addServiceData(Parcel UUID ,byte[]) 方法,可以设置特定的 UUID 和其数据在广播包中。
(5)、 AdvertiseData.Builder#addManufacturerData(int,byte[]) 方法,可以设置特定厂商Id和其数据在广播包中。
从 AdvertiseData.Builder 的设置中可以看出,如果一个外设需要在不连接的情况下对外广播数据,其数据可以存储在 UUID 对应的数据中,也可以存储在厂商数据中。但由于厂商ID是需要由Bluetooth SIG进行分配的,厂商间一般都将数据设置在厂商数据。
另外可以通过 BluetoothAdapter#setName() 设置广播的名称
先看一个例子,我们分别在 广播包 和 扫描包 中设置 AdvertiseData.Builder 的 每一种广播报文参数 ,得到一下报文内容:
(1)、Type = 0x01 表示设备LE物理连接。
(2)、Type = 0x09 表示设备的全名
(3)、Type = 0x03 表示完整的16bit UUID 。其值为0xFFF7。
(4)、Type = 0xFF 表示厂商数据。前两个字节表示厂商ID,即厂商ID为0x11。后面的为厂商数据,具体由用户自行定义。
(5)、Type = 0x16 表示16 bit UUID 的数据,所以前两个字节为 UUID ,即 UUID 为0xF117,后续为 UUID 对应的数据,具体由用户自行定义。
最后继承 AdvertiseCallback 自定义广播回调。
初始化完毕上面的对象后,就可以进行广播:
广播主要是通过 BluetoothLeAdvertiser#startAdvertising() 方法实现,但在之前需要先获取 BluetoothLeAdvertiser 对象。
BluetoothLeAdvertiser 对象存在两个情况获取为Null:
所以在调用 BluetoothAdapter#getBluetoothLeAdvertiser() 前,需要先调用判断蓝牙已开启,并判断在 BluetoothAdapter 中获取的 BluetoothLeAdvertiser 是否为空(测试过某些华为手机 mBluetoothAdapter.() 为 false , 但是能发送ble广播)。
与广播成对出现就是 BluetoothLeAdvertiser.stopAdvertising() 停止广播了,传入开启广播时传递的广播回调对象,即可关闭广播:
虽然通过广播告知外边自身拥有这些Service,但手机自身并没有初始化Gattd的Service。导致外部的中心设备连接手机后,并不能找到对应的 GATT Service 和 获取对应的数据。
Service类型有两个级别:
创建 BluetoothGattService 时,传入两个参数: UUID 和Service类型:
我们都知道Gatt中, Service 的下一级是 Characteristic , Characteristic 是最小的通信单元,通过对 Characteristic 进行读写操作来进行通信。
特征属性表示该 BluetoothGattCharacteristic 拥有什么功能,即能对 BluetoothGattCharacteristic 进行什么操作。其中主要有3种:
权限属性用于配置该特征值所具有的功能。主要两种:
Characteristic 下还有 Descriptor ,初始化 BluetoothGattDescriptor 时传入: Descriptor UUID 和 权限属性
为 Service 添加 Characteristic ,为 Characteristic 添加 Descriptor :
通过蓝牙管理器 mBluetoothManager 获取 Gatt Server ,用来添加 Gatt Service 。添加完 Gatt Service 后,外部中心设备连接手机时,将能获取到对应的 GATT Service 和 获取对应的数据
定义 Gatt Server 回调。当中心设备连接该手机外设、修改特征值、读取特征值等情况时,会得到相应情况的回调。
最后开启广播后,用nRF连接后看到的特征值信息如下图所示:(加多了一个只能都的特征值)
android蓝牙BLE(一) —— 扫描
android蓝牙BLE(二) —— 通信
android蓝牙BLE(三) —— 广播
android蓝牙BLE(四) —— 实战
4. 如何实现android蓝牙开发 自动配对连接,并不弹出提示框
我就开始查找怎么关闭这个蓝牙配对提示框,后面还是伟大的android源码帮助了我。
在源码 BluetoothDevice 类中还有两个隐藏方法
cancelBondProcess()和cancelPairingUserInput()
这两个方法一个是取消配对进程一个是取消用户输入
下面是自动配对的代码
Mainfest,xml注册
<receiverandroid:name=".">
<intent-filter>
<actionandroid:name="android.bluetooth.device.action.PAIRING_REQUEST"/>
</intent-filter>
</receiver>
自己在收到广播时处理并将预先输入的密码设置进去
{
StringstrPsw="0";
@Override
publicvoidonReceive(Contextcontext,Intentintent)
{
//TODOAuto-generatedmethodstub
if(intent.getAction().equals(
"android.bluetooth.device.action.PAIRING_REQUEST"))
{
BluetoothDevicebtDevice=intent
.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
//byte[]pinBytes=BluetoothDevice.convertPinToBytes("1234");
//device.setPin(pinBytes);
Log.i("tag11111","ddd");
try
{
ClsUtils.setPin(btDevice.getClass(),btDevice,strPsw);//手机和蓝牙采集器配对
ClsUtils.createBond(btDevice.getClass(),btDevice);
ClsUtils.cancelPairingUserInput(btDevice.getClass(),btDevice);
}
catch(Exceptione)
{
//TODOAuto-generatedcatchblock
e.printStackTrace();
}
}
}
}
<b>/************************************蓝牙配对函数***************/
importjava.lang.reflect.Field;
importjava.lang.reflect.Method;
importandroid.bluetooth.BluetoothDevice;
importandroid.util.Log;
publicclassClsUtils
{
/**
*与设备配对参考源码:platform/packages/apps/Settings.git
*/Settings/src/com/android/settings/bluetooth/CachedBluetoothDevice.java
*/
staticpublicbooleancreateBond(ClassbtClass,BluetoothDevicebtDevice)
throwsException
{
MethodcreateBondMethod=btClass.getMethod("createBond");
BooleanreturnValue=(Boolean)createBondMethod.invoke(btDevice);
returnreturnValue.booleanValue();
}
/**
*与设备解除配对参考源码:platform/packages/apps/Settings.git
*/Settings/src/com/android/settings/bluetooth/CachedBluetoothDevice.java
*/
staticpublicbooleanremoveBond(ClassbtClass,BluetoothDevicebtDevice)
throwsException
{
MethodremoveBondMethod=btClass.getMethod("removeBond");
BooleanreturnValue=(Boolean)removeBondMethod.invoke(btDevice);
returnreturnValue.booleanValue();
}
staticpublicbooleansetPin(ClassbtClass,BluetoothDevicebtDevice,
Stringstr)throwsException
{
try
{
MethodremoveBondMethod=btClass.getDeclaredMethod("setPin",
newClass[]
{byte[].class});
BooleanreturnValue=(Boolean)removeBondMethod.invoke(btDevice,
newObject[]
{str.getBytes()});
Log.e("returnValue",""+returnValue);
}
catch(SecurityExceptione)
{
//thrownewRuntimeException(e.getMessage());
e.printStackTrace();
}
catch(IllegalArgumentExceptione)
{
//thrownewRuntimeException(e.getMessage());
e.printStackTrace();
}
catch(Exceptione)
{
//TODOAuto-generatedcatchblock
e.printStackTrace();
}
returntrue;
}
//取消用户输入
(ClassbtClass,
BluetoothDevicedevice)
throwsException
{
MethodcreateBondMethod=btClass.getMethod("cancelPairingUserInput");
//cancelBondProcess()
BooleanreturnValue=(Boolean)createBondMethod.invoke(device);
returnreturnValue.booleanValue();
}
//取消配对
(ClassbtClass,
BluetoothDevicedevice)
throwsException
{
MethodcreateBondMethod=btClass.getMethod("cancelBondProcess");
BooleanreturnValue=(Boolean)createBondMethod.invoke(device);
returnreturnValue.booleanValue();
}
/**
*
*@paramclsShow
*/
(ClassclsShow)
{
try
{
//取得所有方法
Method[]hideMethod=clsShow.getMethods();
inti=0;
for(;i<hideMethod.length;i++)
{
Log.e("methodname",hideMethod[i].getName()+";andtheiis:"
+i);
}
//取得所有常量
Field[]allFields=clsShow.getFields();
for(i=0;i<allFields.length;i++)
{
Log.e("Fieldname",allFields[i].getName());
}
}
catch(SecurityExceptione)
{
//thrownewRuntimeException(e.getMessage());
e.printStackTrace();
}
catch(IllegalArgumentExceptione)
{
//thrownewRuntimeException(e.getMessage());
e.printStackTrace();
}
catch(Exceptione)
{
//TODOAuto-generatedcatchblock
e.printStackTrace();
}
}
}</b>
执行时直接使用:
<b>publicstaticbooleanpair(StringstrAddr,StringstrPsw)
{
booleanresult=false;
=BluetoothAdapter
.getDefaultAdapter();
bluetoothAdapter.cancelDiscovery();
if(!bluetoothAdapter.isEnabled())
{
bluetoothAdapter.enable();
}
if(!BluetoothAdapter.checkBluetoothAddress(strAddr))
{//检查蓝牙地址是否有效
Log.d("mylog","devAdneffient!");
}
BluetoothDevicedevice=bluetoothAdapter.getRemoteDevice(strAddr);
if(device.getBondState()!=BluetoothDevice.BOND_BONDED)
{
try
{
Log.d("mylog","NOTBOND_BONDED");
ClsUtils.setPin(device.getClass(),device,strPsw);//手机和蓝牙采集器配对
ClsUtils.createBond(device.getClass(),device);
remoteDevice=device;//配对完毕就把这个设备对象传给全局的remoteDevice
result=true;
}
catch(Exceptione)
{
//TODOAuto-generatedcatchblock
Log.d("mylog","setPiNfailed!");
e.printStackTrace();
}//
}
else
{
Log.d("mylog","HASBOND_BONDED");
try
{
ClsUtils.createBond(device.getClass(),device);
ClsUtils.setPin(device.getClass(),device,strPsw);//手机和蓝牙采集器配对
ClsUtils.createBond(device.getClass(),device);
remoteDevice=device;//如果绑定成功,就直接把这个设备对象传给全局的remoteDevice
result=true;
}
catch(Exceptione)
{
//TODOAuto-generatedcatchblock
Log.d("mylog","setPiNfailed!");
e.printStackTrace();
}
}
returnresult;
}</b>
5. Android 蓝牙开发(一)
普通蓝牙设备官方文档
Android 平台包含蓝牙网络堆栈支持 ,凭借此支持,设备能以无线方式与其他蓝牙设备交换数据。应用框架提供了通过 Android Bluetooth API 访问蓝牙功能的途径。使用 Bluetooth API Android 应用可以执行下面的操作:
传统蓝牙适用于电池使用强度较大的操作,例如 Android 设备之间的流传输和通信等。针对具有低功耗要求的蓝牙设备,Android 4.3(API 18)中引入了面向低功耗蓝牙的 API 支持。
使用 Android Bluetooth API 来完成使用蓝牙进行通信的四项主要任务: 设置蓝牙 、 查找局部区域内的配对设备或可用设备 、 连接设备 ,以及在 设备之间传输数据 。
关于蓝牙的 API 在 android.bluetooth 包中,下面介绍一下和蓝牙相关的主要类:
在 BluetoothProfile IPC 客户端连接到服务(即,运行特定配置文件的内部服务)或断开服务连接时向其发送通知的接口。
使用蓝牙必须声明权限 BLUETOOTH 才可以执行蓝牙通信。
1、获取蓝牙适配器
例如:我们可以查询所有已配对的设备,然后使用 ArrayAdapter 向用户显示每台设备的名称:
要发起连接仅需要知道目标蓝牙设备的 Mac 地址就可以了。
注意 执行 discovery 对于蓝牙适配器来说是一个非常繁重的过程,并且会消耗大量资源。在找到要连接的设备后, 要确保使用 cancelDiscovery() 来停止发现,然后尝试连接 。如果您已经和某台设备进行连接,那么这个时候执行发现操作会大幅度的减少此连接可用的带宽!因此不应该在处于连接状态的时候执行发现操作!
例如:
在连接之前如果两个设备没有配对,则系统会自动发出配对请求。
服务器套接字接受连接的基本过程
放在子线程中去执行。
例子:
客户端连接的基本过程
调用 connect() 的时候要确保客户端没有执行发现操作。如果执行了会大幅度降低连接的速度,增加失败的可能。
例子
在连接之前调用 cancleDiscovery() 在进行连接之前应该始终调用这个方法,而且调用的时候无需检测是否正在扫描。
过程:
从 Android 3.0 开始, Bluetooth API 便支持使用蓝牙配置文件。蓝牙配置文件是适用于设备间蓝牙通信的无线接口规范。
1、蓝牙配置文件就是设备间通信(蓝牙设备)的一种规范
免提配置文件便是一个示例,对于连接到无线耳机的手机,两台设备都必须支持免提配置文件。我们也可以通过实现接口 BluetoothProfile 来写入自己的类来支持特定的蓝牙配置文件。Android API 提供了以下的几种蓝牙配置文件的实现:
2、使用配置文件的基本步骤
创建 HDP 应用:
关于普通蓝牙设备和普通蓝牙设备之间的连接通信
关于蓝牙设备和蓝牙仪器(蓝牙耳机、电子秤等等类似产品)
这种之间的通信是通过配置文件代理来实现的。
都有一个对应的配置文件代理类。具体的操作是通过这个对象来完成。
参考: https://mp.weixin.qq.com/s?__biz=MzU5NzA2NjQzMg==&mid=2247484128&idx=1&sn=&scene=21#wechat_redirect
6. Android Bluetooth相关操作
C_BluetoothUtils
不同配置文件有不同广播,可以在 android.bluetooth.BluetoothProfile 的子类中看到对应的广播
需要等做到什么再完善
有三种方式
需要像套接字一样作为客户端或服务器与其他设备连接
如果两个设备之前没有配对,那么系统会自动发起配对
配对成功后即可像TCP一样进行数据传输
需要先通过扫描找到附近的 BluetoothDevice
Android 3.0开始 Bluetooth API 就支持使用蓝牙配置文件。 蓝牙配置文件是适用于设备间蓝牙通信的无线接口规范。
Android 提供了以下几种蓝牙配置文件的实现
使用蓝牙耳机音频传输由两种方式
打开SCO有两种方式
没设备,待补充,待验证
蓝牙低功耗概览
经典蓝牙与低功耗蓝牙的区别
Android 蓝牙开发(2)——低功耗蓝牙
Android-BLE蓝牙原理
7. 如何使用Android蓝牙开发
转载 Android平台支持蓝牙网络协议栈,实现蓝牙设备之间数据的无线传输。本文档描述了怎样利用android平台提供的蓝牙API去实现蓝压设备之间的通信。蓝牙具有point-to-point 和 multipoint两种连接功能。
使用蓝牙API,可以做到:
* 搜索蓝牙设备
* 从本地的Bluetooth adapter中查询已经配对的设备
* 建立RFCOMM通道
* 通过service discovery连接到其它设备
* 在设备之间传输数据
* 管理多个连接
基础知识
本文档介绍了如何使用Android的蓝牙API来完成的四个必要的主要任务,使用蓝牙进行设备通信,主要包含四个部分:蓝牙设置、搜索设备(配对的或可见的)、连接、传输数据。
所有的蓝牙API在android.bluetooth包中。实现这些功能主要需要下面这几个类和接口:
BluetoothAdapter
代表本地蓝牙适配器(蓝牙发射器),是所有蓝牙交互的入口。通过它可以搜索其它蓝牙设备,查询已经配对的设备列表,通过已知的MAC地址创建BluetoothDevice,创建BluetoothServerSocket监听来自其它设备的通信。
BluetoothDevice
代表了一个远端的蓝牙设备, 使用它请求远端蓝牙设备连接或者获取 远端蓝牙设备的名称、地址、种类和绑定状态。 (其信息是封装在 bluetoothsocket 中) 。
BluetoothSocket
代表了一个蓝牙套接字的接口(类似于 tcp 中的套接字) ,他是应用程 序通过输入、输出流与其他蓝牙设备通信的连接点。
BluetoothServerSocket
代表打开服务连接来监听可能到来的连接请求 (属于 server 端) , 为了连接两个蓝牙设备必须有一个设备作为服务器打开一个服务套接字。 当远端设备发起连 接连接请求的时候,并且已经连接到了的时候,Blueboothserversocket 类将会返回一个 bluetoothsocket。
BluetoothClass
描述了一个设备的特性(profile)或该设备上的蓝牙大致可以提供哪些服务(service),但不可信。比如,设备是一个电话、计算机或手持设备;设备可以提供audio/telephony服务等。可以用它来进行一些UI上的提示。
BluetoothProfile
BluetoothHeadset
提供手机使用蓝牙耳机的支持。这既包括蓝牙耳机和免提(V1.5)模式。
BluetoothA2dp
定义高品质的音频,可以从一个设备传输到另一个蓝牙连接。 “A2DP的”代表高级音频分配模式。
BluetoothHealth
代表了医疗设备配置代理控制的蓝牙服务
BluetoothHealthCallback
一个抽象类,使用实现BluetoothHealth回调。你必须扩展这个类并实现回调方法接收更新应用程序的注册状态和蓝牙通道状态的变化。
代表一个应用程序的配置,蓝牙医疗第三方应用注册与远程蓝牙医疗设备交流。
BluetoothProfile.ServiceListener
当他们已经连接到或从服务断开时通知BluetoothProfile IPX的客户时一个接口(即运行一个特定的配置文件,内部服务)。
蓝牙权限
为了在你的应用中使用蓝牙功能,至少要在AndroidManifest.xml中声明两个权限:BLUETOOTH(任何蓝牙相关API都要使用这个权限) 和 BLUETOOTH_ADMIN(设备搜索、蓝牙设置等)。
为了执行蓝牙通信,例如连接请求,接收连接和传送数据都必须有BLUETOOTH权限。
必须要求BLUETOOTH_ADMIN的权限来启动设备发现或操纵蓝牙设置。大多数应用程序都需要这个权限能力,发现当地的蓝牙设备。此权限授予其他的能力不应该使用,除非应用程序是一个“电源管理”,将根据用户要求修改的蓝牙设置
注释:要请求BLUETOOTH_ADMIN的话,必须要先有BLUETOOTH。
在你的应用manifest 文件中声明蓝牙权限。例如:
<manifest ... >
<uses-permission android:name="android.permission.BLUETOOTH" />
...
</manifest>
通过查看<uses-permission>资料来声明应用权限获取更多的信息。
蓝牙设置
在你的应用通过蓝牙进行通信之前,你需要确认设备是否支持蓝牙,如果支持,确信它被打开。
如果不支持,则不能使用蓝牙功能。如果支持蓝牙,但不能够使用,你刚要在你的应用中请求使用蓝牙。这个要两步完成,使用BluetoothAdapter。
1.获取BluetoothAdapter
所有的蓝牙活动请求BluetoothAdapter,为了获取BluetoothAdapter,呼叫静态方法getDefaultAdapter() 。这个会返回一个BluetoothAdapter,代表设备自己的蓝牙适配器(蓝牙无线电)。这个蓝牙适配器应用于整个系统中,你的应用可以通过这个对象进行交互。如果getDefaultAdapter()返回null,则这个设备不支持蓝牙。例如:
BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if (mBluetoothAdapter == null) {
// Device does not support Bluetooth
}
2.打开蓝牙
其次。你需要确定蓝牙能够使用。通过isEnabled()来检查蓝牙当前是否可用。如果这个方法返回false,则蓝牙不能够使用。为了请求蓝牙使用,呼叫startActivityForResult()与的ACTION_REQUEST_ENABLE动作意图。通过系统设置中启用蓝牙将发出一个请求(不停止蓝牙应用)。例如:
if (mBluetoothAdapter.isEnabled()) {
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
}
![http://developer.android.com/images/bt_enable_request.png]
对话框中显示请求使用蓝牙权限。如果响应"Yes",这个进程完成(或失败)后你的应用将能够使用蓝牙。
REQUEST_ENABLE_BT常量作为一个整型传到startActivityForResult()中(值必须大于0),该系统传回给你,在你onActivityResult()作为实现的requestCode参数。
如果调用蓝牙成功,你的Activity就会在onActivityResult()中收到RESULT_OK结果,如果蓝牙不能使用由于错误(或用户响应“NO”那么结果返回RESULT_CANCELED。
除了通过onActivityResult(),还可以通过监听ACTION_STATE_CHANGED这个broadcast Intent来知道蓝牙状态是否改变。这个Intent包含EXTRA_STATE,EXTRA_PREVIOUS_STATE两个字段,分别代表新旧状态。可能的值是STATE_TURNING_ON, STATE_ON, STATE_TURNING_OFF, 还有STATE_OFF。
小贴: Enabling discoverability 将自动启用蓝牙。如果您计划执行蓝牙活动之前,始终使设备可发现,你可以跳过上面的步骤2。参阅enabling discoverability。
搜索设备
使用BluetoothAdapter可以通过设备搜索或查询配对设备找到远程Bluetooth设备。
Device discovery(设备搜索)是一个扫描搜索本地已使能Bluetooth设备并且从搜索到的设备请求一些信息的过程(有时候会收到类似“discovering”,“inquiring”或“scanning”)。但是,搜索到的本地Bluetooth设备只有在打开被发现功能后才会响应一个discovery请求,响应的信息包括设备名,类,唯一的MAC地址。发起搜寻的设备可以使用这些信息来初始化跟被发现的设备的连接。
一旦与远程设备的第一次连接被建立,一个pairing请求就会自动提交给用户。如果设备已配对,配对设备的基本信息(名称,类,MAC地址)就被保存下来了,能够使用Bluetooth API来读取这些信息。使用已知的远程设备的MAC地址,连接可以在任何时候初始化而不必先完成搜索(当然这是假设远程设备是在可连接的空间范围内)。
需要记住,配对和连接是两个不同的概念:
配对意思是两个设备相互意识到对方的存在,共享一个用来鉴别身份的链路键(link-key),能够与对方建立一个加密的连接。
连接意思是两个设备现在共享一个RFCOMM信道,能够相互传输数据。
目前Android Bluetooth API's要求设备在建立RFCOMM信道前必须配对(配对是在使用Bluetooth API初始化一个加密连接时自动完成的)。
下面描述如何查询已配对设备,搜索新设备。
注意:Android的电源设备默认是不能被发现的。用户可以通过系统设置让它在有限的时间内可以被发现,或者可以在应用程序中要求用户使能被发现功能。
查找匹配设备
在搜索设备前,查询配对设备看需要的设备是否已经是已经存在是很值得的,可以调用getBondedDevices()来做到,该函数会返回一个描述配对设备BluetoothDevice的结果集。例如,可以使用ArrayAdapter查询所有配对设备然后显示所有设备名给用户:
Set<BluetoothDevice> pairedDevices = mBluetoothAdapter.getBondedDevices();
// If there are paired devices
if (pairedDevices.size() > 0) {
// Loop through paired devices
for (BluetoothDevice device : pairedDevices) {
// Add the name and address to an array adapter to show in a ListView
mArrayAdapter.add(device.getName() + "n" + device.getAddress());
}
};
BluetoothDevice对象中需要用来初始化一个连接唯一需要用到的信息就是MAC地址。
8. Android开发之蓝牙(Bluetooth)
在上一篇中有介绍了Wifi与网络连接处理
Android开发之WiFi与网络连接处理
下面,来继续说说Android中蓝牙的基本使用。
Bluetooth是目前使用的最广泛的无线通讯协议之一,主要针对短距离设备通讯(10米),常用于连接耳机、鼠标和移动通讯设备等。
值得一提的是:
android4.2新增了部分新功能,但是对于Bluetooth熟悉的人或许开始头疼了,那就是Android4.2引入了一个新的蓝牙协议栈针BLE。谷歌和Broadcom之间的合作,开发新的蓝牙协议栈,取代了基于堆栈的Bluez。因此市场上出现了老设备的兼容问题,很多蓝牙设备在android4.2手机上不能正常使用。
BluetoothAdapter简单点来说就是代表了本设备(手机、电脑等)的蓝牙适配器对象。
first:we need permission
要操作蓝牙,先要在AndroidManifest.xml里加入权限
**下面来看看如何使用蓝牙。 **↓↓↓****
Demo已就绪:
返回值:如果设备具备蓝牙功能,返回BluetoothAdapter 实例;否则,返回null对象。
打开蓝牙设备的方式:
1.直接调用函数enable()去打开蓝牙设备 ;
2.系统API去打开蓝牙设备,该方式会弹出一个对话框样式的Activity供用户选择是否打开蓝牙设备。
注意: 1.如果蓝牙已经开启,不会弹出该Activity界面。2.在目前大多数Android手机中,是不支持在飞行模式下开启蓝牙的。如果蓝牙已经开启,那么蓝牙的开关 ,状态会随着飞行模式的状态而发生改变。
1. 搜索蓝牙设备
使用BluetoothAdapter的startDiscovery()方法来搜索蓝牙设备
startDiscovery()方法是一个异步方法,调用后会立即返回。该方法会进行对其他蓝牙设备的搜索,该过程会持续12秒。该方法调用后,搜索过程实际上是在一个System Service中进行的,所以可以调用cancelDiscovery()方法来停止搜索(该方法可以在未执行discovery请求时调用)。
系统开始搜索蓝牙设备
^( *  ̄(oo) ̄ ) ^ 系统会发送以下三个广播:
2.扫描设备
3.定义广播接收器接收搜索结果
4.注册广播
获取附近的蓝牙设备
第一步建立连接:首先Android sdk(2.0以上版本)支持的蓝牙连接是通过BluetoothSocket建立连接,服务端BluetoothServerSocket和客户端(BluetoothSocket)需指定同样的UUID,才能建立连接,因为建立连接的方法会阻塞线程,所以服务器端和客户端都应启动新线程连接。
(这里的服务端和客户端是相对来说的)
两个蓝牙设备之间的连接,则必须实现服务端与客户端的机制。
当两个设备在同一个RFCOMM channel下分别拥有一个连接的BluetoothSocket,这两个设备才可以说是建立了连接。
服务端设备与客户端设备获取BluetoothSocket的途径是不同的。
1,服务端设备是通过accepted一个incoming connection来获取的,
2,客户端设备则是通过打开一个到服务端的RFCOMM channel来获取的。
服务端
通过调用BluetoothAdapter的(String, UUID)方法来获取BluetoothServerSocket(UUID用于客户端与服务端之间的配对)
客户端
调用BluetoothService的(UUID)方法获取BluetoothSocket(该UUID应该同于服务端的UUID)。
调用BluetoothSocket的connect()方法(该方法为block方法),如果UUID同服务端的UUID匹配,并且连接被服务端accept,则connect()方法返回。
数据传递,通过以上操作,就已经建立的BluetoothSocket连接了,数据传递无非是通过流的形式
获取流
该类就是关于远程蓝牙设备的一个描述。通过它可以和本地蓝牙设备---BluetoothAdapter连接通信。
好多东西我也不知道怎么描述,下面给出Demo:
刚好有刚学习的小伙伴问我ListView怎么用,那我就用ListView。
源码:
RairDemo
GitHub: https://github.com/Rairmmd/android-demo
Coding: https://coding.net/u/Rair/p/RairDemo/git