
最近总是有用户反馈说APP扫描不到设备,让我很费解了一段时间,尤其是华为和OPPO,公司还专门买了这款手机,然后测试没问题,直到一个偶然,我把手机定位给关了,才发现这个问题,Android 60 扫描设备需开启位置权限,用户突然一天把定位给关了,我们在扫描之前又没检测,唉,一个逻辑不严谨就会出现各种问题,现在记录一下
权限获取
<uses-permission android:name="androidpermissionBLUETOOTH"/> 使用蓝牙所需要的权限
<uses-permission android:name="androidpermissionBLUETOOTH_ADMIN"/> 使用扫描和设置蓝牙的权限(申明这一个权限必须申明上面一个权限)
在Android50之前,是默认申请GPS硬件功能的。而在Android 50 之后,需要在manifest 中申明GPS硬件模块功能的使用。
<!-- Needed only if your app targets Android 50 (API level 21) or higher -->
<uses-feature android:name="androidhardwarelocationgps" />
在 Android 60 及以上,还需要打开位置权限。如果应用没有位置权限,蓝牙扫描功能不能使用(其它蓝牙 *** 作例如连接蓝牙设备和写入数据不受影响)
<uses-permission android:name="androidpermissionACCESS_COARSE_LOCATION"/>
除了上面的设置之外,如果想设置设备只支持 BLE,可以加上下面这句话
<uses-feature android:name="androidhardwarebluetooth_le" android:required="true"/>
同样,如果不想添加 BLE 的支持,那么可以设置 required="false"
然后可以在运行时判断设备是否支持 BLE,
// Use this check to determine whether BLE is supported on the device Then
// you can selectively disable BLE-related features
if (!getPackageManager()hasSystemFeature(PackageManagerFEATURE_BLUETOOTH_LE)) {
ToastmakeText(this, Rstringble_not_supported, ToastLENGTH_SHORT)show();
finish();
}
打开定位 (Location)
首先检查定位是否打开,可以像下面这样 *** 作:
/
Location service if enable
@param context
@return location is enable if return true, otherwise disable
/
public static final boolean isLocationEnable(Context context) {
LocationManager locationManager = (LocationManager) contextgetSystemService(ContextLOCATION_SERVICE);
boolean networkProvider = locationManagerisProviderEnabled(LocationManagerNETWORK_PROVIDER);
boolean gpsProvider = locationManagerisProviderEnabled(LocationManagerGPS_PROVIDER);
if (networkProvider || gpsProvider) return true;
return false;
}
如果定位已经打开,可以搜索到 ble 设备;如果定位没有打开,则需要用户去打开,像下面这样:
private static final int REQUEST_CODE_LOCATION_SETTINGS = 2;
private void setLocationService() {
Intent locationIntent = new Intent(SettingsACTION_LOCATION_SOURCE_SETTINGS);
thisstartActivityForResult(locationIntent, REQUEST_CODE_LOCATION_SETTINGS);
}
进入定位设置界面,让用户自己选择是否打开定位。选择的结果获取:
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_CODE_LOCATION_SETTINGS) {
if (isLocationEnable(this)) {
//定位已打开的处理
} else {
//定位依然没有打开的处理
}
} else superonActivityResult(requestCode, resultCode, data);
}
没接触过低功耗蓝牙协议,也没实际开发过和低功耗蓝牙有关的东西,最近需要获取一款低功耗蓝牙产品的数据,听说有专门的低功耗蓝牙抓包工具和软件可供使用,刚好手里也有硬件(USB蓝牙适配器),就硬着头皮,准备尝试一下,因为还在尝试阶段,很多知识点还不甚明了,所以我会随时更改此文里面的内容。
在蓝牙开发中,有些情况是不需要连接的,只要外设广播自己的数据即可,例如苹果的 ibeacon 。自 Android 50 更新蓝牙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)、通过 AdvertiseSettingsBuilder#setAdvertiseMode() 设置广播模式。其中有3种模式:
(2)、通过 AdvertiseSettingsBuilder#setAdvertiseMode() 设置广播发射功率。共有4种功率模式:
(3)、通过 AdvertiseSettingsBuilder#setTimeout() 设置持续广播的时间,单位为毫秒。最多180000毫秒。当值为0则无时间限制,持续广播,除非调用 BluetoothLeAdvertiser#stopAdvertising() 停止广播。
(4)、通过 AdvertiseSettingsBuilder#setConnectable() 设置该广播是否可以连接的。
之前说过,外设必须广播广播包,扫描包是可选。但添加扫描包也意味着广播更多得数据,即可广播62个字节。
可见无论是广播包还是扫描包,其广播的内容都是用 AdvertiseData 类封装的。
(1)、 AdvertiseDataBuilder#setIncludeDeviceName() 方法,可以设置广播包中是否包含蓝牙的名称。
(2)、 AdvertiseDataBuilder#setIncludeTxPowerLevel() 方法,可以设置广播包中是否包含蓝牙的发射功率。
(3)、 AdvertiseDataBuilder#addService UUID (Parcel UUID ) 方法,可以设置特定的 UUID 在广播包中。
(4)、 AdvertiseDataBuilder#addServiceData(Parcel UUID ,byte[]) 方法,可以设置特定的 UUID 和其数据在广播包中。
(5)、 AdvertiseDataBuilder#addManufacturerData(int,byte[]) 方法,可以设置特定厂商Id和其数据在广播包中。
从 AdvertiseDataBuilder 的设置中可以看出,如果一个外设需要在不连接的情况下对外广播数据,其数据可以存储在 UUID 对应的数据中,也可以存储在厂商数据中。但由于厂商ID是需要由Bluetooth SIG进行分配的,厂商间一般都将数据设置在厂商数据。
另外可以通过 BluetoothAdapter#setName() 设置广播的名称
先看一个例子,我们分别在 广播包 和 扫描包 中设置 AdvertiseDataBuilder 的 每一种广播报文参数 ,得到一下报文内容:
(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 是否为空(测试过某些华为手机 mBluetoothAdapterisMultipleAdvertisementSupported() 为 false , 但是能发送ble广播)。
与广播成对出现就是 BluetoothLeAdvertiserstopAdvertising() 停止广播了,传入开启广播时传递的广播回调对象,即可关闭广播:
虽然通过广播告知外边自身拥有这些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(四) —— 实战
android蓝牙自动配对连接的具体代码如下:
1 获取蓝牙适配器BluetoothAdapter blueadapter=BluetoothAdaptergetDefaultAdapter();
如果BluetoothAdapter 为null,说明android手机没有蓝牙模块。
2 判断蓝牙模块是否开启,blueadapterisEnabled() true表示已经开启,false表示蓝牙并没启用。
3 启动配置蓝牙可见模式,即进入可配对模式Intent in=new Intent(BluetoothAdapterACTION_REQUEST_DISCOVERABLE);
inputExtra(BluetoothAdapterEXTRA_DISCOVERABLE_DURATION, 200);
startActivity(in); ,200就表示200秒。
4 获取蓝牙适配器中已经配对的设备Set<BluetoothDevice> device=blueadaptergetBondedDevices();
当然,还需要在androidManifestxml中声明蓝牙的权限
<uses-permission android:name="androidpermissionBLUETOOTH" />
<uses-permission android:name="androidpermissionBLUETOOTH_ADMIN" />
5自动配对设置Pin值
static public boolean autoBond(Class btClass, BluetoothDevice device, String strPin)
throws Exception {
Method autoBondMethod = btClassgetMethod("setPin", new Class[] { byte[]class });
Boolean result = (Boolean) autoBondMethod
invoke(device, new Object[] { strPingetBytes() });
return result;
}
6开始配对请求
static public boolean createBond(Class btClass, BluetoothDevice device) throws Exception {
Method createBondMethod = btClassgetMethod("createBond");
Boolean returnValue = (Boolean) createBondMethodinvoke(device);
return returnValuebooleanValue();
}
BLE蓝牙收发demo
串口收发助手
这个类主要是扫描蓝牙然后或获取蓝牙的地址:
通过蓝牙适配器就可以扫描蓝牙了
mBluetoothAdapterstopLeScan(mLeScanCallback);
可以看到上面回调当中有三个参数其中device为蓝牙设备,这里面包含蓝牙名称和蓝牙地址,rssi可以通过算出模糊的算出蓝牙直接的距离;
在这个类中包括启动服务和接收服务发送过来的广播,以及蓝牙的连接和对服务的什么周期管理;
启动服务
startService(new Intent(this, BluetoothLeServiceclass));
注册广播
registerReceiver(mGattUpdateReceiver, makeGattUpdateIntentFilter()); //注册广播
连接蓝牙
final boolean result = mBluetoothLeServiceconnect(mDeviceAddress); //连接蓝牙
可以看到连接蓝牙只需要蓝牙的地址就可以,通过调用服务中封装好的连接蓝牙方法就可以连接
蓝牙建立好连接,然后通过UUID的读写通道建立读写的关系就可以在广播处接受分发送数据到服务类中将数据发送或接收;
连接蓝牙
// 第二个参数: 如果为false,则直接立即连接。
// 如果为true,则等待远程设备可用时(在范围内,。。)连接。并不是断开后重新连接。
mBluetoothGatt = deviceconnectGatt(this, false, mGattCallback);
这里mGattCallback是蓝牙的BluetoothGattCallback的回调,这个回调中有几个重要方法,弄懂这几个方法那就弄懂了蓝牙。
最近遇到了一个BLE的项目,花时间恶补了下相关的知识,这里记录下来备忘。这篇笔记是纯协议的,先大概了解ble的协议和流程,能帮助我们更好的编码
Ble设备的发现实际上靠的是Advertising(广播)机制。广播也有人管它叫做Beacon,我没有在官方文档里面查找到这个词,但是从网络上的文章来看,它们差不多就是同一个东西。
基于广播发现Ble设备有两种方式:
由于这两种方式都基于广播,所以它们的数据格式是一样的。广播会自带一些信息,例如设备的名称、MAC地址等。除了自带的数据之外,我们还能携带一些额外的信息数据。根据 官方 的 文档 ,可以看到这个额外数据的具体格式如下:
可以看到广播数据里面包含多个AD Structure。每个AD Structure分为两个部分:数据段长度(1字节)+数据段(N字节)。数据段又分为头1个字节的AD Type标识类型和剩余的AD Data具体数据。
注意看最后的Non-significant part,有时候在安卓的回调里面会在byte数组的最后看到一堆的0x0,这个实际上也是定义在协议里面正常的无意义数据,我们直接忽略它们就好。
举个实际的例子,在手机上使用ble搜索应用搜索我司开发的蓝牙设备,查看其广播数据:
可以看到广播数据0x0319C703020104030312180C094D41584559455F353146300C16791300000002000000735C,实际有5个AD Structure。
AD Type如上图所说可以去蓝牙协议的 官方 查看[Generic Access Profile文档]( >
Android 从 43(API Level 18) 开始支持低功耗蓝牙,但是只支持作为中心设备(Central)模式,这就意味着 Android 设备只能主动扫描和链接其他外围设备(Peripheral)。从 Android 50(API Level 21) 开始两种模式都支持。
低功耗蓝牙开发算是较偏技术,实际开发中坑是比较多的,网上有很多文章介绍使用和经验总结,但是有些问题答案不好找,甚至有些误导人,比如 :获取已经连接的蓝牙,有的是通过反射,一大堆判断,然而并不是对所有手机有用,关于Ble传输速率问题的解决,都是默认Android每次只能发送20个字节,然而也并不是,,,下面进入正文。
这里用的是 Android50 新增的扫描API,
这里说一下,如果做蓝牙设备管理页面,可能区分是否是已连接的设备,网上又通过反射或其他挺麻烦的 *** 作,也不见得获取到,官方Api 就有提供
与外围设备交互经常每次发的数据大于 mtu的,需要做分包处理,接收数据也要判断数据的完整性最后才返回原数据做处理,所以一般交互最少包含包长度,和包校验码和原数据。当然也可以加包头,指令还有其他完整性校验。下面分享几个公用方法:
我自己封装的一个BleUtil ,因为涉及跟公司业务关联性太强(主要是传输包的协议不同)就先不开源出来了,如果这边文章对大家有帮助反馈不错,我会考虑上传个demo到github供大家使用,
在这先给大家推荐一个不错 Demo ,里面除了没有分包,协议,和传输速率。基本的功能都有,而且调试数据到打印到界面上了。最主要是它可以用两个个手机一个当中心设备一个当外围设备调试。
首先传输速率优化有两个方向,1 外围设备传输到Android 。2 Android传输到外围设备。
我在开发中首先先使用上面那位仁兄的demo调试,两个Android 设备调试不延时,上一个成功马上下一个,最多一秒发11个20字节的包。
后来和我们的蓝牙设备调试时发现发送特别快,但是数据不完整,他蓝牙模块接收成功了,但是透传数据到芯片处理时发现不完整,我们的硬件小伙伴说因为 波特率 限制(差不多每10字节透传要耗时1ms)和蓝牙模块的buff (打印时是最多100byte,100打印的)限制,就算蓝牙模块每包都告诉你接收成功,也是没透传完就又接收了。后来通过调试每次发20K数据,最后是 Android 发是 20字节/130ms 稳定。给Android 发是 20字节/ 8ms 。 (天杀的20字节,网上都是说20字节最多了)
后来看了国外一家物联网公司总结的 Ble 吞吐量的文章(上面有连接),知道Android 每个延时是可以连续接收6个包的。就改为 120字节/ 16ms (为啥是16ms,不是每次间隔要6个包吗,怎么像间隔两次,这时因为波特率影响,多了5个包100字节,差不多 我们的单片机透传到蓝牙模块要多耗时不到10ms )
而Android 发数据可以申请 我们设备的mtu 来得到最多每次能发多少字节。延时还是130ms,即:241字节/ 130ms 提高12倍,这个速度还可以。
根据蓝牙BLE协议, 物理层physical layer的传输速率是1Mbps,相当于每秒125K字节。事实上,其只是基准传输速率,协议规定BLE不能连续不断地传输数据包,否则就不能称为低功耗蓝牙了。连续传输自然会带来高功耗。所以,蓝牙的最高传输速率并不由物理层的工作频率决定的。
在实际的 *** 作过程中,如果主机连线不断地发送数据包,要么丢包严重要么连接出现异常而断开。
在BLE里面,传输速度受其连接参数所影响。连接参数定义如下:
1)连接间隔。蓝牙基带是跳频工作的,主机和从机会商定多长时间进行跳频连接,连接上才能进行数据传输。这个连接和广播状态和连接状态的连接不是一样的意思。主机在从机广播时进行连接是应用层的主动软件行为。而跳频过程中的连接是蓝牙基带协议的规定,完全由硬件控制,对应用层透明。明显,如果这个连接间隔时间越短,那么传输的速度就增大。连接上传完数据后,蓝牙基带即进入休眠状态,保证低功耗。其是125毫秒一个单位。
2)连接延迟。其是为了低功耗考虑,允许从机在跳频过程中不理会主机的跳频指令,继续睡眠一段时间。而主机不能因为从机睡眠而认为其断开连接了。其是125毫秒一个单位。明显,这个数值越小,传输速度也高。
蓝牙BLE协议规定连接参数最小是5,即725毫秒;而Android手机规定连接参数最小是8,即10毫秒。iOS规定是16,即20毫秒。
连接参数完全由主机决定,但从机可以发出更新参数申请,主机可以接受也可以拒绝。android手机一部接受,而ios比较严格,拒绝的概率比较高。
参考:
在iOS和Android上最大化BLE吞吐量
最大化BLE吞吐量第2部分:使用更大的ATT MTU
以上就是关于Android 6.0 扫描不到 Ble 设备需开启位置权限全部的内容,包括:Android 6.0 扫描不到 Ble 设备需开启位置权限、BLE蓝牙抓包工具使用尝试、android蓝牙BLE(三) —— 广播等相关内容解答,如果想了解更多相关内容,可以关注我们,你们的支持是我们更新的动力!
欢迎分享,转载请注明来源:内存溢出
微信扫一扫
支付宝扫一扫
评论列表(0条)