安卓低功耗蓝牙开发
来源:互联网 发布:淘宝代金券怎么使用 编辑:程序博客网 时间:2024/06/04 18:56
近几日做了些安卓低功耗蓝牙的项目,主要是用了北欧半导体公司的板子。不过对于安卓上位机来说,是哪家公司的板子,差别并不是很大。
刚开始对蓝牙不是很了解,找了NordicSemiconductor的Android-nRF-Toolbox和谷歌自己的sample 的代码研究了一番。
Nordic的代码较为庞大,内容也很丰富,包括了dfu升级服务,体温服务,串口服务等等。而谷歌的相对要简单些,仅仅让使用者能够看出蓝牙发上来的数据,对于心率的服务还做了解析。两者的共同点是将大部分的蓝牙操作置于Service之中,而acticivty只需要绑定服务,并且注册相应的广播来接收各种信息即可。
因此,我对蓝牙的操作进行了一些小小的总结。只需要基于以下几个基本功能,就可以完成一个简单的蓝牙操作过程。
一、蓝牙的初始化
1、判断安卓设备是否支持ble
如果安卓设备不支持ble的话,基本就没得玩了。不过现在的安卓手机普遍都支持,按照官方文档的说法,如图。
只要是安卓4.3以上,即api18以上的设备基本都支持ble。但是不排除有些安卓设备是从4.3以下升级上来的,这些设备可能就不能使用低功耗蓝牙了,但是这种情况还是比较少的。尽管如此,我们还是可以用
public boolean isBLESupported() { boolean flag = true; if(!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) { flag = false; } return flag; }
来判断以下。
2、获取本地蓝牙适配器
mBluetoothManager = (BluetoothManager)mContext.getSystemService(Context.BLUETOOTH_SERVICE); mBluetoothAdapter = mBluetoothManager.getAdapter();
只有在获取本地蓝牙适配器之后才能进行相应的蓝牙操作。
3、判断蓝牙是否已经打开
尽管这不是必须的,但是有时候可能会使用到
private boolean isBLEEnabled() { return mBluetoothAdapter != null && mBluetoothAdapter.isEnabled(); }
4、开启蓝牙
开启蓝牙有两种方式,一种是需要通过用户同意才打开,另一种是不经过用户,直接打开。这两种方式各有利弊。要根据情况来稍作判断。
public void enableBle(boolean bShowDialog) { if (isBLESupported()){ if (bShowDialog){ //这种是需要弹出对话框让用户选择是否打开的 final Intent enableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); mContext.startActivity(enableIntent); }else{ //这种是不经过用户,直接打开 mBluetoothAdapter.enable(); } }else { Log.v(TAG,"ble is not support"); } }
4、关闭蓝牙
mBluetoothAdapter.disable();
关闭的代码较为简单。
二、扫描设备
扫描设备是相当耗能的,通常情况下,在扫描设备的时候不会进行其他有关蓝牙的操作。按照官方文档,我们扫描的时候还应当设定一个扫描时间,免得手机一直再扫描,消耗电量。
扫描设备又有几种方式,不同安卓系统之间的函数又有不同,5.0以上(含)的系统和5.0以下的系统,用的是不同的扫描函数,但是本质上差的不是很多,这里只提供5.0以下的扫描函数,因为,即使是5.0以上的系统也还可以用5.0以下的函数进行扫描。
1、准备一个扫描回调
为什么要弄一个这种东西呢?这里涉及到了java里的一种回调机制,简单的说
比方说:有一天,我去面包店买面包,可是店员告诉我面包卖完了,我留了个电话给他,让他有面包的时候打电话通知我,我再过来买。这样做的好处是,我可以先去干别的事情而不用为了这点面包苦苦等待。
运用到蓝牙中就是,我开启了扫描,在扫描的这段时间里我可以先处理一些和蓝牙有关或无关的事情。不会让扫描这件事一直阻塞着我的进程。
private BluetoothAdapter.LeScanCallback mLeScanCallback = new BluetoothAdapter.LeScanCallback() { @Override public void onLeScan(final BluetoothDevice device, int rssi, byte[] scanRecord) { //在这里可以处理扫描到设备的时候想要做的事情 //你可以发送一个扫描到设备的广播,或者发个消息,等等 } };
2、扫描设备
扫描呢,就得注意一个东西,就是扫描时间,我们可以弄一个延时函数Handler类的postDelayed并用上Runnable就可以达到延时效果,开启扫描后延时一定的毫秒数停止扫描。
另外呢,扫描的时候可以指定相应的服务的uuid进行扫描,也可以不管三七二十一,只要是低功耗蓝牙设备都给扫了。只需要调用不同的startLeScan就可以。
public void scanLeDevice(UUID[] serviceUuids, long scanPeriod){ // Stops scanning after a pre-defined scan period. if (isBLEEnabled()){ mHandler.postDelayed(new Runnable() { @Override public void run() { mScanning = false; mBluetoothAdapter.stopLeScan(mLeScanCallback); } }, scanPeriod);//scanPeriod是毫秒,也就是1秒 = 1000 //mBluetoothAdapter.stopLeScan(mLeScanCallback);若调用这条函数,即可扫描全部ble设备 mBluetoothAdapter.startLeScan(serviceUuids, mLeScanCallback); //这里我发送了个开始扫描的广播,可发可不发 broadcastUpdate(BleBroadcastAction.ACTION_DEVICE_DISCOVERING); } }
3、停止扫描
停止扫描也比较简单
mBluetoothAdapter.stopLeScan(mLeScanCallback);
三、连接设备
1、准备一个BluetoothGattCallback——一个基于gatt服务的回调
有是一个回调,java中因为没有函数指针,所以回调通常会借助接口或者抽象类来完成。这个回调包含了几个功能:
1、接收手机与ble设备连接状态改变的信号,对应函数onConnectionStateChange(BluetoothGatt gatt, int status,
int newState)
2、发现所连接的设备具有的服务。对应函数onServicesDiscovered(BluetoothGatt gatt, int status)
3、读取所连接服务对应的数据,对应函数onCharacteristicRead(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic,
int status)
4、得到数据改变的通知,并获取改变的结果,对应函数onCharacteristicChanged(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic)
注意:这里的涉及到一个蓝牙连接后的顺序问题,必须要先连接设备,才能发现设备相应的服务。发现服务后,必须要先连接服务,才能获取服务对应的数据,和发送给服务对应的数据。
private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() { @Override public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { String intentAction; if (newState == BluetoothProfile.STATE_CONNECTED) { intentAction = BleBroadcastAction.ACTION_GATT_CONNECTED; mConnectionState = STATE_CONNECTED; broadcastUpdate(intentAction); Log.i(TAG, "Connected to GATT server."); // Attempts to discover services after successful connection. Log.i(TAG, "Attempting to start service discovery:" + mBluetoothGatt.discoverServices()); } else if (newState == BluetoothProfile.STATE_DISCONNECTED) { intentAction = BleBroadcastAction.ACTION_GATT_DISCONNECTED; mConnectionState = STATE_DISCONNECTED; Log.i(TAG, "Disconnected from GATT server."); broadcastUpdate(intentAction); close(); } } @Override public void onServicesDiscovered(BluetoothGatt gatt, int status) { if (status == BluetoothGatt.GATT_SUCCESS) { broadcastUpdate(BleBroadcastAction.ACTION_GATT_SERVICES_DISCOVERED); connectCharacteristic(mCharacteristicUuidArr); } else { Log.w(TAG, "onServicesDiscovered received: " + status); } } @Override public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { if (status == BluetoothGatt.GATT_SUCCESS) { broadcastUpdate(characteristic.getUuid().toString(), characteristic); } } @Override public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { broadcastUpdate(characteristic.getUuid().toString(), characteristic); } };
这上面是谷歌的代码,我只做了略微修改。总的来说,只要调用了哪个回调函数都可以发个广播或者消息,让其他activity或者service去处理。
2、连接
连接ble设备,第一次连接是比较简单的,调用
final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address);//address是mac字符串mBluetoothGatt = device.connectGatt(mContext, false, mGattCallback);
即可开始连接。
但是这里又涉及到一个比较隐晦坑爹的点,就再次连接同一个设备的情况。我一开始在做这个部分的时候没有注意到连接同一个设备的情况。结果导致我待会断开连接之后,设备还是一直保持通信。原因是安卓手机无法识别他们是同一个设备,建立了新的连接,把设备当做新设备连接来处理。我后来断开的设备只是断掉了安卓认为的新设备。
这里呢,可以先判断是否连接过,如果连接过,可以调用connect();方法重新连接。
if (mBluetoothDeviceAddress != null && address.equals(mBluetoothDeviceAddress) && mBluetoothGatt != null) { Log.d(TAG, "Trying to use an existing mBluetoothGatt for connection."); if (mBluetoothGatt.connect()) { mConnectionState = STATE_CONNECTING; return true; } else { return false; } }
但是,这么做呢,经过试验是有些缺陷的,连接时间会大大拉长。可以采取nordic的做法。
if (mConnected) return; if (mBluetoothGatt != null) { Logger.d(mLogSession, "gatt.close()"); mBluetoothGatt.close(); mBluetoothGatt = null; } final boolean autoConnect = shouldAutoConnect(); mUserDisconnected = !autoConnect; // We will receive Linkloss events only when the device is connected with autoConnect=true Logger.v(mLogSession, "Connecting..."); Logger.d(mLogSession, "gatt = device.connectGatt(autoConnect = " + autoConnect + ")"); mBluetoothGatt = device.connectGatt(mContext, autoConnect, getGattCallback());
这样做,可以加快连接速度。
3、获取服务
连接完成后,需调用BluetoothGatt的discoverServices()函数去查询当前连接的设备所具有的服务,当安卓设备发现所连接的设备具有服务时,会调用我们刚才重写的回调函数onServicesDiscovered(BluetoothGatt gatt, int status)。我们便可调用mBluetoothGatt.getServices()去获取设备拥有的服务。并进行操作,获取他们的characteristic。
4、断开连接
有连接,则有断开连接。
public void disconnect() { if (mBluetoothAdapter == null || mBluetoothGatt == null) { Log.w(TAG, "BluetoothAdapter not initialized"); return; } mConnectionState = STATE_DISCONNECTING; broadcastUpdate(BleBroadcastAction.ACTION_GATT_DISCONNECTING); mBluetoothGatt.disconnect(); }
程序结束后呢,还得释放资源,怎么说呢像c++的delete或者c语言free那样吧。调用BluetoothGatt的close函数,关闭一波
public void close() { if (mBluetoothGatt == null) { return; } mBluetoothGatt.close(); mBluetoothGatt = null;}
四、数据收发
1、接收数据
收数据呢,安卓也是采取了回调的方式,一收到数据,就立马通知用户进行处理,这样做的好处呢,我们可以及时处理收到的数据,不会像stm32 串口处理数据一样,即有可能因为定时器来不及处理,数据就被覆盖。笔者写单片机的时候还是被坑了一波爹的。
当然,设备那么多个服务,我们也不是要全部接受数据,安卓也没有那么勤快,什么数据都自动帮我们接收,想要接受什么数据,我们得先通知他一声。
private BluetoothGatt mBluetoothGatt;BluetoothGattCharacteristic characteristic;boolean enabled;...mBluetoothGatt.setCharacteristicNotification(characteristic, enabled);...
调用setCharacteristicNotification(characteristic,enabled)通知安卓我要接收那个服务的数据,要接收就true,不接收就false。
这里又涉及到了一个东西,就是characteristic。
characteristic可能更贴近服务的概念吧
Service是characteristic的集合,也就是说Service更像是一个组名,不直接提供数据交换,而Descriptor是用来描述characteristic的,比方说描述某个characteristic是否可读,是否可写。(这里有待考证,我还没有亲自试验过)。所以用户使用最多应该还是characteristic。基本上光使用characteristic就可以换成数据交互。
调用readCharacteristic可以读取数据,并进入onCharacteristicRead的回调中去,解析读到的数据。
2、发送数据
发数据呢,这是一个比较奇怪的方式,不像串口一样,怼着哪个通道就把数据给丢出去,方便快捷。按说是需要获取到你要写数据的characteristic,然后调用setValue函数,把你想要发的数据写进characteristic,然后再发出去。
private boolean writeCharacteristic(final BluetoothGattCharacteristic characteristic) { final BluetoothGatt gatt = mBluetoothGatt; if (gatt == null || characteristic == null) return false; // Check characteristic property final int properties = characteristic.getProperties(); if ((properties & (BluetoothGattCharacteristic.PROPERTY_WRITE | BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE)) == 0) return false; return gatt.writeCharacteristic(characteristic); } public boolean writeBle(byte[] data, UUID uuid) { BluetoothGattCharacteristic targetCharacteristic = null; for (BluetoothGattService bluetoothGattService : getSupportedGattServices()) { for (BluetoothGattCharacteristic bluetoothGattCharacteristic : bluetoothGattService.getCharacteristics()) { if (bluetoothGattCharacteristic.getUuid().toString().equals(uuid.toString())) { targetCharacteristic = bluetoothGattCharacteristic; } } } if (targetCharacteristic == null){ return false; }else { targetCharacteristic.setValue(data); return writeCharacteristic(targetCharacteristic); }// return writeCharacteristic() }
大体就先说这么多东西吧,笔者也只是了解点皮毛,欢迎讨论。
参考链接:https://developer.android.com/guide/topics/connectivity/bluetooth-le.html
代码资源:
https://github.com/will4906/AndroidBleDemo
- 安卓低功耗蓝牙开发
- 安卓低功耗蓝牙(BLE)开发教程
- 安卓低功耗蓝牙
- 安卓低功耗蓝牙
- Android蓝牙低功耗开发
- BLE低功耗蓝牙设备开发
- Android ble低功耗蓝牙开发
- 三步走--低功耗蓝牙BLE开发实战
- Android BLE低功耗蓝牙开发
- 学习笔记之低功耗蓝牙开发
- Android-BLE低功耗蓝牙开发
- 低功耗蓝牙开发学习 0523
- 【android 蓝牙开发——BLE(低功耗)蓝牙】
- Android蓝牙开发(二) BLE4.0低功耗蓝牙
- 【Android应用开发】Android 蓝牙低功耗 (BLE) ( 第一篇 . 概述 . 蓝牙低功耗文档 翻译)
- 开发android蓝牙4.0 BLE低功耗应用的感受
- Android低功耗蓝牙应用开发获取的服务UUID
- 低功耗蓝牙4.0BLE编程-nrf51822开发(1)
- HDU-3172-Virtual Friends
- Chrome,FireFox开发者工具无法找到js?
- postman post/get 方法
- C#中datagridview使用tooltip控件显示单元格内容与datagridview自带的tooltip显示单元格内容的方法
- CodeForces 606B Testing Robots【读题题QAQ】
- 安卓低功耗蓝牙开发
- Easy 191题 Number of 1 Bits
- This app has been built with an incorrect configuration. Please configure your build for VectorDrawa
- HTML 18.3 表单元素input的输入属性type
- masterJ2EE篇001——启动tomcat报错Server Tomcat v7.0 Server at localhost failed to start
- 高效MacBook工作环境配置
- Caffe代码解读(四):solver_param
- strStr
- last-position-of-target