概述
中心角色和外围角色
在BLE中存在两个角色,一个是中心角色(Central),一个是外围角色(Peripheral),蓝牙设备或手机都可以单独作为Central或Peripheral角色。外设角色的作用是为中心角色提供各种数据,中心角色可以扫描并接收多个外设角色数据( 外围角色中的设备进行广播,中心角色的设备扫描寻找广播),数据以服务(Service)和特征(Characteristic)的形式呈现。其中Ble中心角色的API在Android 4.3得到支持,而外围角色的API在Android 5.0才得到支持
协议、服务、特征、描述符
一份协议(BluetoothGatt)由一个或多个服务(BluetoothGattService)构成,一个服务由零个或多个特征(BluetoothGattCharacteristic)构成,一个特征可以包含零个或多个描述符(BluetoothGattDescriptor)。每一个服务、特征、描述符都有一个UUID作为唯一识别符,识别符有通用的,也可以自定义,也可以随机生成,固定格式00000000-0000-0000-0000-000000000000(8-4-4-4-12),一般来说自定义的UUID只有前8位有变化,后面的基本是固定的0000-1000-8000-00805f9b34fb,所以一个自定义的UUID一般看起来就像这样 “0000????-0000-1000-8000-00805f9b34fb” 通配符就表示4个16进制数。每一个特征都有其属性和权限(Read | Write | Notify | Indicate),特征根据属性可读可写。在每个Ble蓝牙设备中,都会有两个默认的服务如下:
//Generic Access(Generic Attribute Profile 通用属性规范GATT)service:00001801-0000-1000-8000-00805f9b34fbcharacteristic:00002a05-0000-1000-8000-00805f9b34fb//Generic Attribute (Generic Access Profile 通用接入规范GAP)service:00001800-0000-1000-8000-00805f9b34fbcharacteristic:00002a00-0000-1000-8000-00805f9b34fbcharacteristic:00002a01-0000-1000-8000-00805f9b34fbcharacteristic:00002aa6-0000-1000-8000-00805f9b34fb
适配器,扫描器:
每一台支持蓝牙的手机中都会有一个蓝牙适配器,由蓝牙管理器管理着,从其中获得蓝牙适配器。适配器中自带扫描器,使用扫描器可以扫描周边的蓝牙设备。
Ble中常用类
BluetoothDeivce:蓝牙设备,代表一个具体的蓝牙外设
BluetoothManager:蓝牙管理器,主要用于获取蓝牙适配器和管理所有和蓝牙相关的东西
BluetoothAdapter:蓝牙适配器,每一台支持蓝牙功能的手机都有一个蓝牙适配器,一般来说,只有一个,可以通过BluetoothManager获取
BluetoothLeScanner:蓝牙适配器里面的扫描器,用于扫描BLE外设,可以通过BluetoothAdapter获取
BluetoothGatt:通用属性协议,定义了BLE通讯的基本规则,就是通过把数据包装成服务和特征的约定过程,可以通过建立连接获取
BluetoothGattService:服务,描述了一个BLE设备的一项基本功能,由零或多个特征组构成,可以通过BluetoothGatt获取并自定义(现有的约定服务可以在bluetooth.org上找到)
BluetoothGattCallback:作为中央的回调类,用于回调GATT通信的各种状态和结果
BluetoothGattServerCallback:作为周边的回调类,用于回调GATT通信的各种状态和结果
BluetoothGattCharacteristic:特征,里面包含了一组或多组数据,是GATT通信中的最小数据单元。
BluetoothGattDescriptor:特征描述符,对特征的额外描述,包括但不仅限于特征的单位,属性等。
BluetoothProfile:一个通用的规范,按照这个规范来收发数据
使用流程
中心设备:判断蓝牙是否可用->打开蓝牙->开始扫描->获取被扫描到的设备->连接设备->发现服务->获取到指定特征->写入特征值
外围设备:判断蓝牙是否可用->打开蓝牙->创建广播数据->发送广播->添加服务至广播->根据监听获取写入的数据
下图是中心设备的使用流程图来源
image.png
权限声明
<uses-permission android:name="android.permission.BLUETOOTH"/><uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>//获取扫描结果权限,Android6.0以上需要动态权限(这两个权限是在经典蓝牙的扫描中需要声明的)<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>//或者<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>//当android:required为true的时候,app只能强制运行在支持BLE功能的设备商,为false的时候,可以运行在所有设备上,但某些方法需要手动检测,否则可能存在隐性BUG<uses-feature android:name="android.hardware.bluetooth_le"android:required="false"/>
Ble可用性判断
不是任何设备都支持BLE,最开始要确定设备是否支持,还要确定蓝牙已经打开。
Android系统从4.3以后才支持,这是先决条件
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2){ ... ...}
判断蓝牙是否可用
//小于等于API17时直接使用BluetoothAdapter.getDefaultAdapter()来获取AdapterprivateBluetoothAdaptergetAdapter(){if(android.os.Build.VERSION.SDK_INT>=android.os.Build.VERSION_CODES.JELLY_BEAN_MR2){mBluetoothManager=(BluetoothManager)getSystemService(Context.BLUETOOTH_SERVICE);mBluetoothAdapter=mBluetoothManager.getAdapter();}else{mBluetoothAdapter=BluetoothAdapter.getDefaultAdapter();}returnmBluetoothAdapter;}if(mBluetoothAdapter==null){//蓝牙不可用}//Ble不可用privatebooleancheckIfSupportBle(){returngetPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE);}
蓝牙开启
//开启蓝牙功能需要一小段时间,所以不能执行开启蓝牙立即执行其他操作,这时蓝牙实际还没有开启,回出现异常,所以后续操作应该在蓝牙状态广播中处理privatevoidenableBluetooth(){if(mBluetoothAdapter==null&&!mBluetoothAdapter.isEnabled()){//使用系统弹框来启动蓝牙,REQUEST_ENABLE_BT为自定义的开启蓝牙请求码IntentenableBtIntent=newIntent(BluetoothAdapter.ACTION_REQUEST_ENABLE);startActivityForResult(enableBtIntent,REQUEST_ENABLE_BT);//或者静默打开bluetoothAdapter.enable();}}//静默关闭bluetoothAdapter.disable();
蓝牙开关广播更多蓝牙相关广播
//广播的ActionBluetoothAdapter.ACTION_STATE_CHANGED//四种状态值 BluetoothAdapter.STATE_ON//已开启BluetoothAdapter.STATE_OFF//已关闭BluetoothAdapter.STATE_TURNING_ON//正在开启BluetoothAdapter.STATE_TURNING_OFF//正在关闭//当前状态值的获取intcurState=intent.getIntExtra(BluetoothAdapter.EXTRA_STATE,BluetoothAdapter.STATE_ON);
扫描具体使用流程
经典通用方式
//此过程大概持续10秒,当扫描到蓝牙设备后,会发出广播,只要在需要的地方注册接收广播,就可以获得扫描结果。这种方法可以扫描出所有蓝牙设备,包括BLE,但貌似不同手机有不同体验。privatevoidstartDiscover(){mBluetoothAdapter.startDiscover();}//注册此广播,监听BluetoothDevice.ACTION_FOUND,以接收系统消息取得扫描结果 privateclassDeviceReceiverextendsBroadcastReceiver{@OverridepublicvoidonReceive(Contextcontext,Intentintent){Stringaction=intent.getAction();if(BluetoothDevice.ACTION_FOUND.equals(action)){//这个就是所获得的蓝牙设备。BluetoothDevicedevice=intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);mDevices.add(device);}}}
Ble扫描方式
//Ble蓝牙扫描方式在5.0之后发生了变更if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.LOLLIPOP){BluetoothLeScannerbluetoothLeScanner=bluetoothAdapter.getBluetoothLeScanner();//过滤器设置ArrayList<ScanFilter>filters=newArrayList<>();ScanFilter.BuilderfilterBuilder=newScanFilter.Builder();filters.add(filterBuilder);//扫描结果回调设置ScanCallbackscanCallback=newScanCallback(){publicvoidonScanResult(intcallbackType,ScanResultresult){//scanResult.getDevice()取得device}};//扫描参数设置ScanSettings.BuildersettingsBuilder=newScanSettings.Builder();//这种扫描方式占用资源比较高,建议当应用处于前台时使用该模式settingsBuilder.setScanMode(SCAN_MODE_LOW_LATENCY);// 开启扫描,第三项参数见下面,该操作为异步操作的,但是会消耗大量资源,一般扫描时长为12秒,建议找到需要的设备后,执行取消扫描bluetoothLeScanner.startScan(scanCallback,filter,settingsBuilder.build());}else{//扫描结果回调设置 BluetoothAdapter.LeScanCallbackleScanCallback=newBluetoothAdapter.LeScanCallback(){publicvoidonLeScan(BluetoothDevicedevice,intrssi,byte[]scanRecord){}};//该方法已被声明为过时方法 adapter.startLeScan(leScanCallback)}
停止扫描
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP){ bluetoothLeScanner.stopScan(mScanCallback);}else{ bluetoothAdapter.stopLeScan(mLeScanCallback);}
设备信息获取
根据蓝牙地址获取蓝牙设备
BluetoothDevice bluetoothDevice = bluetoothAdapter.getRemoteDevice(address);
已绑定设备获取
privatevoidgetBoundDevices(){Set<BluetoothDevice>boundDevices=mBluetoothAdapter.getBondedDevices();for(BluetoothDevice device:boundDevices){//对device进行其他操作,比如连接等。}}
设备详细信息获取
privatevoidshowDetailOfDevice(){//获得设备名称,多个设备可以有同一个名称。String deviceName=bluetoothDevice.getName();//获取设备物理地址,一个设备只能有一个物理地址,每个设备都有每个设备的物理地址,无法相同。String deviceMacAddress=bluetoothDevice.getAddress();//绑定设备bluetoothDevice.createBond();}
连接
建立连接
//扫描回调的BluetoothDevice用于建立GATT连接,参数2是控制是否自动链接,为true的时候,当设备进入中心范围,会进行自动连接,为false时是主动连接,一般推荐使用主动连接,因为这样连接速度更快。BluetoothGatt bluetoothGatt = bluetoothDevice.connectGatt(context, false, bluetoothGattCallback);//如果已经连接后,但是又断开了连接,需要重新连接时bluetoothGatt.connect();
连接状态的获取
//连接状态改变会在BluetoothGattCallback的onConnectionStateChange方法中回调,同时也可以随时主动去查询intstate=bluetoothManager.getConnectionState(bluetoothDevice,BluetoothGatt.GATT);
发现服务
//在建立连接成功后进行发现服务,onServicesDiscovered为发现服务的回调bluetoothGatt.discoverServer();
特征改变
//该方法一般是在发现服务后,进行设置的,设置该方法的目的是让硬件在特征数据改变的时候,发送数据给app,app则通过onCharacteristicChanged方法回调给用户,从参数中可获取到回调回来的数据bluetoothGatt.setCharacteristicNotification(characteristic,true);
连接数据回调(在非UI线程)
//mBluetoothGattCallback 为所有蓝牙数据回调的处理者,也是整个蓝牙操作当中最为核心的一部分。它里面有很多方法,但并非所有都需要在开发当中用到,这里列出来只是作为部分解析,需要哪个方法,就重写哪个方法,不需要的,直接去掉privateBluetoothGattCallbackmBluetoothGattCallback=newBluetoothGattCallback(){@OverridepublicvoidonConnectionStateChange(BluetoothGattgatt,intstatus,intnewState){super.onConnectionStateChange(gatt,status,newState);//当设备与中心连接状态发生改变时,下面是已连接的判断if(status==BluetoothGatt.GATT_SUCCESS&&newState==BluetoothProfile.STATE_CONNECTED){...}}@OverridepublicvoidonServicesDiscovered(BluetoothGattgatt,intstatus){super.onServicesDiscovered(gatt,status);//当发现设备服务时,会回调到此处,下面是遍历所有发现的服务if(status==BluetoothGatt.GATT_SUCCESS){//gatt.getServices()可以获得外设的所有服务for(BluetoothGattServiceservice:gatt.getServices()){//每发现一个服务,我们再次遍历服务当中所包含的特征,service.getCharacteristics()可以获得当前服务所包含的所有特征for(BluetoothGattCharacteristiccharacteristic:service.getCharacteristics()){//通常可以把所发现的特征放进一个列表当中以便后续操作,如果你想知道每个特征都包含哪些描述符,很简单,再用一个循环去遍历每一个特征的getDescriptor()方法。mCharacteristics.add(characteristic);//UUID获取,或者根据UUID判断是否是自己需要的服务/特征Log.i("",characteristic.getUuid().toString());//打印特征的UUID。}}}}@OverridepublicvoidonCharacteristicRead(BluetoothGattgatt,BluetoothGattCharacteristiccharacteristic,intstatus){//读取特征后回调到此处。}@OverridepublicvoidonCharacteristicWrite(BluetoothGattgatt,BluetoothGattCharacteristiccharacteristic,intstatus){//写入特征后回调到此处,status == BluetoothGatt.GATT_SUCCESS代表写入成功}@OverridepublicvoidonCharacteristicChanged(BluetoothGattgatt,BluetoothGattCharacteristiccharacteristic){//当特征(值)发生变法时回调到此处。}@OverridepublicvoidonDescriptorRead(BluetoothGattgatt,BluetoothGattDescriptordescriptor,intstatus){//读取描述符后回调到此处。}@OverridepublicvoidonDescriptorWrite(BluetoothGattgatt,BluetoothGattDescriptordescriptor,intstatus){//写入描述符后回调到此处}@OverridepublicvoidonReliableWriteCompleted(BluetoothGattgatt,intstatus){//暂时没有用过。}@OverridepublicvoidonReadRemoteRssi(BluetoothGattgatt,intrssi,intstatus){//Rssi表示设备与中心的信号强度,发生变化时回调到此处。}@OverridepublicvoidonMtuChanged(BluetoothGattgatt,intmtu,intstatus){//暂时没有用过。}};
数据传输
写入特征值,会回调onCharacteristicWrite
BluetoothGattService service=bluetoothGatt.getService(SERVICE_UUID);BluetoothGattCharacteristic characteristic=service.getCharacteristic(CHARACTERISTIC_UUID);characteristic.setValue(writeString);//设置回复方式bluetoothGattCharacteristic.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE);bluetoothGatt.writeCharacteristic(characteristic);
写入描述符
bluetoothGattDescriptor.setValue(writeContent.getBytes());bluetoothGatt.writeDescriptor(bluetoothGattDescriptor);
读取特征值
bluetoothGatt.readCharacteristic(characteristic);
外围设备实现(被连接者/服务端 )
广播的设定
privateAdvertiseSettingsbuildAdvertiseSettings(){AdvertiseSettings.Builder builder=newAdvertiseSettings.Builder()//设置广播模式:低功耗、平衡、低延时,广播间隔时间依次越来越短.setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_BALANCED)//设置是否可以连接,一般不可连接广播应用在iBeacon设备上.setConnectable(true)//设置广播的最长时间,最大时长为LIMITED_ADVERTISING_MAX_MILLIS(180秒).setTimeout(10*1000)//设置广播的信号强度 ADVERTISE_TX_POWER_ULTRA_LOW, ADVERTISE_TX_POWER_LOW,//ADVERTISE_TX_POWER_MEDIUM, ADVERTISE_TX_POWER_HIGH 信号强度依次增强.setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_HIGH);returnbuilder.build();}
对外广播的数据(数据限制 31 Bytes)
privateAdvertiseDatabuildAdvertiseData(){AdvertiseData.BuilderdataBuilder=newAdvertiseData.Builder();//添加厂家信息,第一个参数为厂家ID(不足两个字节会自动补0,例如这里为0x34,实际数据则为34,00)//一般情况下无需设置,否则会出现无法被其他设备扫描到的情况.addManufacturerData(0x34,newbyte[]{0x56})//添加服务进行广播,即对外广播本设备拥有的服务.addServiceData(...);//是否广播信号强度.setIncludeTxPowerLevel(true)//是否广播设备名称.setIncludeDeviceName(true);returndataBuilder.build();}
对外广播(即允许被扫描到)
privatevoidadvertise(){BluetoothManagerbluetoothManager=(BluetoothManager)getSystemService(BLUETOOTH_SERVICE);BluetoothAdaptermAdapter=bluetoothManager.getAdapter();AdvertiseCallbackadvertiseCallback=newAdvertiseCallback(){@OverridepublicvoidonStartSuccess(AdvertiseSettingssettingsInEffect){//广播成功,建议在这里开启服务}@OverridepublicvoidonStartFailure(interrorCode){//广播失败}};BluetoothLeAdvertisermAdvertiser=mAdapter.getBluetoothLeAdvertiser();mAdvertiser.startAdvertising(buildAdvertiseSettings(),buildAdvertiseData(),mAdvertiseCallback);}
开启服务
//声明需要广播的服务的UUID和特征的UUID,注意不要占用蓝牙设备默认的UUIDUUID UUID_SERVICE=UUID.fromString("00001354-0000-1000-8000-00805f9b34fb");UUID UUID_CHARACTERISTIC=UUID.fromString("00001355-0000-1000-8000-00805f9b34fb");UUID UUID_DESCRIPTOR=UUID.fromString("00001356-0000-1000-8000-00805f9b34fb");//外围设备状态、数据回调,详情见后面BluetoothGattServerCallback serverCallback=newBluetoothGattServerCallback(){...};//GATT协议服务BluetoothGattServer server=bluetoothManager.openGattServer(this,serverCallback);//创建一个服务BluetoothGattService service=newBluetoothGattService(UUID_SERVICE,BluetoothGattService.SERVICE_TYPE_PRIMARY);//创建一个特征,首先此characteristic属性满足BluetoothGattCharacteristic.PROPERTY_WRITY或BluetoothGattCharacteristic.PROPERTY_WRITY_NO_RESPONSE,//如果其property都不包含这两个,写特征值writeCharacteristic()函数直接返回false,什么都不做处理。其次此characteristic权限应满足//BluetoothGattCharacteristic.PERMISSION_WRITE,否则onCharacteristicWrite()回调收到GATT_WRITE_NOT_PERMITTED回应//如果需要既能读,又能写,则可以参考如下写法BluetoothGattCharacteristic characteristic=newBluetoothGattCharacteristic(Constants.UUID_CHARACTERISTIC,BluetoothGattCharacteristic.PROPERTY_READ|BluetoothGattCharacteristic.PROPERTY_WRITE,BluetoothGattCharacteristic.PERMISSION_WRITE|BluetoothGattCharacteristic.PERMISSION_READ);//创建一个描述符BluetoothGattDescriptor descriptor=newBluetoothGattDescriptor(UUID_DESCRIPTOR,BluetoothGattDescriptor.PERMISSION_READ);//将描述符添加到特征中,一个特征可以包含0至多个描述符characteristic.addDescriptor(descriptor);//将特征添加到服务中,一个服务可以包含1到多个特征service.addCharacteristic(characteristic);server.addService(service);
BluetoothGattServerCallback
publicabstractclassBluetoothGattServerCallback{publicBluetoothGattServerCallback(){}publicvoidonConnectionStateChange(BluetoothDevicedevice,intstatus,intnewState){//连接状态被改变}publicvoidonServiceAdded(intstatus,BluetoothGattServiceservice){//添加服务}publicvoidonCharacteristicReadRequest(BluetoothDevicedevice,intrequestId,intoffset,BluetoothGattCharacteristiccharacteristic){//被读取特征}publicvoidonCharacteristicWriteRequest(BluetoothDevicedevice,intrequestId,BluetoothGattCharacteristiccharacteristic,booleanpreparedWrite,booleanresponseNeeded,intoffset,byte[]value){//被写入数据,其中device是写入的设备,value是写入的值,responseNeeded指是否需要恢复,如果需要恢复则调用gattServer.sendResponse()方法回复}publicvoidonDescriptorReadRequest(BluetoothDevicedevice,intrequestId,intoffset,BluetoothGattDescriptordescriptor){//被读取描述符}publicvoidonDescriptorWriteRequest(BluetoothDevicedevice,intrequestId,BluetoothGattDescriptordescriptor,booleanpreparedWrite,booleanresponseNeeded,intoffset,byte[]value){//被写入描述符}publicvoidonExecuteWrite(BluetoothDevicedevice,intrequestId,booleanexecute){}@OverridepublicvoidonNotificationSent(BluetoothDevicedevice,intstatus){}@OverridepublicvoidonMtuChanged(BluetoothDevicedevice,intmtu){}@OverridepublicvoidonPhyUpdate(BluetoothDevicedevice,inttxPhy,intrxPhy,intstatus){}@OverridepublicvoidonPhyRead(BluetoothDevicedevice,inttxPhy,intrxPhy,intstatus){}}
广播数据格式
广播数据(或者扫描应答数据)由一个一个的AD Structure组成,对于未满31bytes的其它数据,则填充为0;每个AD Structure由两部分组成:1byte的长度信息(Data的长度),和剩余的Data信息;
Data信息又由两部分组成:AD Type(长度不定)指示该AD Structure的类型,以及具体的AD Data。
image.png
例如:
02 01 06 03 03 aa fe 17 16 aa fe 00 -10 00 01 02 03 04 05 06 07 08 09 0a 0b 0e 0f 00 00 00 00
02 01 06是一个AD Structure:Data的长度是02;Data是01 06;AD Type是01(Flags);AD Data是06,表明支持General Discoverable Mode、不支持BR/EDR。
03 03 aa fe是一个AD Structure:Data的长度是03;Data是03 aa fe;AD Type是03(16 bits的Service UUID);AD Data是aa fe,是Eddystone profile的Service UUID。AD Type查询
注意
外围设备广播的数据最多31 byte
写特征一次最多写入20 byte
单次扫描时间不宜过长(建议10~15秒)
BluetoothGattCallback的回调是在非UI线程
每个设备所能使用的Gatt连接是有限个数的,所以应该断开连接时也关闭GattbluetoothGatt.close(),然后重新连接
外围设备蓝牙关闭后,中心设备的BluetoothGattCallback的onConnectionStateChange会回调状态码status = 19,newState = 0(断开连接)
外围设备蓝牙关闭后,中心设备再进行连接,BluetoothGattCallback的onConnectionStateChange会回调状态码133,并且当外围设备蓝牙开启后再调用中心设备的bluetoothGatt.connect()不能重新连接,仍然是133错误,所以建议针对133错误进行关闭gatt并释放资源