安卓低功耗蓝牙
来源:互联网 发布:一亿玉碎 知乎 编辑:程序博客网 时间:2024/06/06 07:05
谷歌官方文档:
https://developer.android.google.cn/guide/topics/connectivity/bluetooth-le.html
谷歌官方demo:
https://github.com/googlesamples/android-BluetoothLeGatt/
参考:
http://blog.csdn.net/chenfengdejuanlian/article/details/45787123
http://blog.csdn.net/z957250254/article/details/52411556
第一次接触蓝牙方面的知识,仅此记录,大家多多交流啊
先按照下图走一遍流程
检查、开启权限
开启蓝牙权限
<uses-permission android:name="android.permission.BLUETOOTH" /> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /> <!-- 声明此应用仅适用于具有低功耗蓝牙设备,若想适用于不支持BLE的设备,required设置为false --> <uses-feature android:name="android.hardware.bluetooth_le" android:required="true" />
检查设备是否支持低功耗蓝牙
if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) { Toast.makeText(this, "此设备不支持BLE(低功耗蓝牙)", Toast.LENGTH_LONG).show(); finish(); }
需要注意的是还要开启位置权限
在manifest添加
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
并在activity中动态申请权限(本例使用RxPermission请求权限)
/** * 请求定位权限 */ private void checkPermission() { new RxPermissions((Activity) mContext).request(Manifest.permission.ACCESS_COARSE_LOCATION) .subscribe(new Observer<Boolean>() { @Override public void onSubscribe(Disposable d) { } @Override public void onNext(Boolean granted) { if (granted) { initBlueTooth(); } else { // 未获取权限 Toast.makeText(mContext, "未获取定位权限", Toast.LENGTH_LONG).show(); } } @Override public void onError(Throwable e) { } @Override public void onComplete() { } }); }
搜索蓝牙设备
搜索蓝牙设备是需要一个BluetoothAdapter来扫描设备的,这里又要进行设备是否支持蓝牙功能的判断和是否开启蓝牙(若未开启,提示用户或强制开启蓝牙)
private void initBlueTooth() { //BluetoothAdapter代表设备自己的蓝牙适配器(蓝牙无线电) BluetoothManager bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE); mBluetoothAdapter = bluetoothManager.getAdapter(); //设备是否支持蓝牙 if (mBluetoothAdapter == null) { Toast.makeText(mContext, "该设备不支持蓝牙功能", Toast.LENGTH_LONG).show(); finish(); } //蓝牙是否启用,若未启用,请求用户启用 if (!mBluetoothAdapter.isEnabled()) { Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT); return; } //若开启蓝牙,直接扫描低功耗蓝牙设备;若未开启,提示用户开启,用户开启后在onActivityResult开启扫描 scanLeDevice(true); }
扫描或关闭扫描
注意:扫描不能一直进行,本例SCAN_PERIOD=10000毫秒后停止扫描
/** * 扫描低功耗蓝牙设备 * 您只能扫描蓝牙LE设备或扫描经典蓝牙设备,如蓝牙中所述 您无法同时扫描Bluetooth LE和传统设备。 * * @param enable */ private void scanLeDevice(final boolean enable) { if (enable) { //Because scanning is battery-intensive, you should observe the following guidelines: //As soon as you find the desired device, stop scanning.找到所需设备停止扫描 //Never scan on a loop, and set a time limit on your scan.切勿扫描循环,并在扫描上加上时间限制 mHandler.postDelayed(new Runnable() { @Override public void run() { mScanning = false; mBluetoothAdapter.stopLeScan(mLeScanCallback); mTextView.setText("扫描结束"); invalidateOptionsMenu(); } }, SCAN_PERIOD); mScanning = true; //如果只想扫描特定类型的外设,则可以改为调用startLeScan(UUID [],BluetoothAdapter.LeScanCallback) //提供指定您的应用程序支持的GATT服务的UUID对象数组 mBluetoothAdapter.startLeScan(mLeScanCallback); mTextView.setText("扫描中..."); } else { mScanning = false; mBluetoothAdapter.stopLeScan(mLeScanCallback); mTextView.setText("扫描结束"); } invalidateOptionsMenu(); }
mBluetoothAdapter.startLeScan()的参数mLeScanCallback是扫描结果的回调
mLeScanCallback = new BluetoothAdapter.LeScanCallback() { @Override public void onLeScan(final BluetoothDevice device, int rssi, byte[] scanRecord) { runOnUiThread(new Runnable() { @Override public void run() { if (device != null) { //过滤同一设备 if (!mDevices.contains(device)) { //将设备添加到列表中 mDeviceListAdapter.addData(device); } } } }); } };
其中mDeviceListAdapter为列表适配器,mDevices为列表适配器中的数据,若不进行同一设备的过滤,会返回很多相同设备
连接ble设备
本demo是基于谷歌的示例代码,就直接将谷歌连接设备的相关类拷过来了,这里直接对其分析
在activity中绑定一个BluetoothLeService服务,此服务是用于对指定蓝牙设备进行连接或数据通信的服务
Intent gattServiceIntent = new Intent(this, BluetoothLeService.class); bindService(gattServiceIntent, mServiceConnection, BIND_AUTO_CREATE);
其中mServiceConnection管理BluetoothLeService服务的生命周期
/** * 管理蓝牙服务的生命周期 */ private final ServiceConnection mServiceConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName componentName, IBinder service) { mBluetoothLeService = ((BluetoothLeService.LocalBinder) service).getService(); if (!mBluetoothLeService.initialize()) { Log.e(TAG, "Unable to initialize Bluetooth"); finish(); } //成功启动初始化后自动连接到设备 mBluetoothLeService.connect(mDeviceAddress); } @Override public void onServiceDisconnected(ComponentName componentName) { mBluetoothLeService = null; } };
在连接设备之前,要判断设备是否支持低功耗蓝牙
public boolean initialize() { // For API level 18 and above, get a reference to BluetoothAdapter through // BluetoothManager. if (mBluetoothManager == null) { mBluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE); if (mBluetoothManager == null) { Log.e(TAG, "Unable to initialize BluetoothManager."); return false; } } mBluetoothAdapter = mBluetoothManager.getAdapter(); if (mBluetoothAdapter == null) { Log.e(TAG, "Unable to obtain a BluetoothAdapter."); return false; } return true; }
BluetoothLeService绑定成功后,将要连接的设备地址传入,在service中连接设备
/** * 连接到Bluetooth LE设备上托管的GATT服务器 * * @param address 设备地址 * @return 返回是否连接是否成功启动(注意:是启动,并不是连接结果) * 连接结果是在BluetoothGattCallback onConnectionStateChange异步返回的 */ public boolean connect(final String address) { if (mBluetoothAdapter == null || address == null) { Log.w(TAG, "BluetoothAdapter not initialized or unspecified address."); return false; } // 以前连接的设备,尝试重连 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; } } //通过传入的设备地址获取设备 final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address); if (device == null) { Log.w(TAG, "Device not found. Unable to connect."); return false; } // 我们要直接连接到设备,所以我们正在设置autoConnect参数为false (此处autoConnect为true可能不是立即连接) mBluetoothGatt = device.connectGatt(this, false, mGattCallback); Log.d(TAG, "Trying to create a new connection."); mBluetoothDeviceAddress = address; mConnectionState = STATE_CONNECTING; return true; }
其中device.connectGatt()的第三个参数mGattCallback为蓝牙设备与GATT服务端连接和数据通信的监听,此方法的返回值mBluetoothGatt 稍后介绍
private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() { //回调指示何时GATT客户端连接到远程GATT服务器/从远程GATT服务器断开连接 @Override public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { String intentAction; //连接成功 if (newState == BluetoothProfile.STATE_CONNECTED) { intentAction = ACTION_GATT_CONNECTED; mConnectionState = STATE_CONNECTED; broadcastUpdate(intentAction); Log.i(TAG, "Connected to GATT server."); // 连接成功后尝试发现服务 Log.i(TAG, "Attempting to start service discovery:" + mBluetoothGatt.discoverServices()); } else if (newState == BluetoothProfile.STATE_DISCONNECTED) { intentAction = ACTION_GATT_DISCONNECTED; mConnectionState = STATE_DISCONNECTED; Log.i(TAG, "Disconnected from GATT server."); broadcastUpdate(intentAction); } } //发现新服务时调用回调 @Override public void onServicesDiscovered(BluetoothGatt gatt, int status) { if (status == BluetoothGatt.GATT_SUCCESS) { broadcastUpdate(ACTION_GATT_SERVICES_DISCOVERED); } else { Log.w(TAG, "onServicesDiscovered received: " + status); } } //回调报告特征性读取操作的结果 @Override public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { if (status == BluetoothGatt.GATT_SUCCESS) { broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic); } } //由于远程特征通知而触发的回调 @Override public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic); } };
其中,连接成功、断开或发现服务或数据通信的回调均发送了广播,在activity中接收广播进行管理,这里先不关注每一个方法,尽量从一个流程分析
看到onConnectionStateChange回调中,判断状态为连接成功,调用了如下代码发现服务
mBluetoothGatt.discoverServices()
注意:
上文中mBluetoothGatt相当于手机与设备通信的管道
通过BluetoothGatt
- 发现服务(discoverServices)
- 获取服务(getServices)
- 开启指定指定特征的通知(setCharacteristicNotification)
- 对指定特征写入数据(writeCharacteristic)
- 把相应地属性返回到BluetoothGattCallback
demo中发现服务后发送广播到activity中,在activity中接收广播调用service(此service为BluetoothLeService,是自己写的对指定蓝牙设备进行连接或数据通信的服务)的获取服务(此处的服务为**蓝牙设备所拥有的服务**BluetoothGattService)方法getServices,然后在activity中对服务进行遍历,并存储每个服务下的特征值
此处分析下流程:调用mBluetoothGatt.discoverServices方法后,发现服务后会回调给BluetoothGattCallback的onServicesDiscovered,在调用mBluetoothGatt.getServices()返回一个包含BluetoothGattService服务的集合,再对集合进行遍历,获取每一个BluetoothGattService服务的特征
注意:(这里有点绕)
- BluetoothLeService 自己写的对指定蓝牙设备进行连接或数据通信的服务
- BluetoothGattService 蓝牙设备所拥有的服务
- BluetoothGattCharacteristic 某一BluetoothGattService的某一特征
- BluetoothGattDescriptor 某一BluetoothGattCharacteristic下的属性,用来描述characteristic变量的属性
下面这张图为BluetoothGattService 和 BluetoothGattCharacteristic 和 BluetoothGattDescriptor的关系
遍历服务(这里尤其绕)使用了ExpandableListView展示服务和服务下的特征
private void displayGattServices(List<BluetoothGattService> gattServices) { if (gattServices == null) return; String uuid = null; String unknownServiceString = getResources().getString(R.string.unknown_service); String unknownCharaString = getResources().getString(R.string.unknown_characteristic); ArrayList<HashMap<String, String>> gattServiceData = new ArrayList<HashMap<String, String>>(); ArrayList<ArrayList<HashMap<String, String>>> gattCharacteristicData = new ArrayList<ArrayList<HashMap<String, String>>>(); mGattCharacteristics = new ArrayList<ArrayList<BluetoothGattCharacteristic>>(); //遍历所有的服务 for (BluetoothGattService gattService : gattServices) { //每一个服务的name 和 UUID保存在map中 HashMap<String, String> currentServiceData = new HashMap<String, String>(); uuid = gattService.getUuid().toString(); currentServiceData.put( LIST_NAME, SampleGattAttributes.lookup(uuid, unknownServiceString)); currentServiceData.put(LIST_UUID, uuid); //将 每一个 包含服务信息的map 都添加到集合中 gattServiceData.add(currentServiceData); ArrayList<HashMap<String, String>> gattCharacteristicGroupData = new ArrayList<HashMap<String, String>>(); List<BluetoothGattCharacteristic> gattCharacteristics = gattService.getCharacteristics(); ArrayList<BluetoothGattCharacteristic> charas = new ArrayList<BluetoothGattCharacteristic>(); //遍历服务中的特征 for (BluetoothGattCharacteristic gattCharacteristic : gattCharacteristics) { //将此服务中的特征添加到集合中 charas.add(gattCharacteristic); //将此特征的UUID和name保存在map中 HashMap<String, String> currentCharaData = new HashMap<String, String>(); uuid = gattCharacteristic.getUuid().toString(); currentCharaData.put( LIST_NAME, SampleGattAttributes.lookup(uuid, unknownCharaString)); currentCharaData.put(LIST_UUID, uuid); //将 每一个 包含特征信息的map 都添加到集合中 gattCharacteristicGroupData.add(currentCharaData); } //将 每个服务中包含的所有特征的集合 添加到总集合中 mGattCharacteristics.add(charas); //将 每个服务中包含的所有特征信息的集合 添加到总集合中 gattCharacteristicData.add(gattCharacteristicGroupData); } SimpleExpandableListAdapter gattServiceAdapter = new SimpleExpandableListAdapter( this, gattServiceData, android.R.layout.simple_expandable_list_item_2, new String[]{LIST_NAME, LIST_UUID}, new int[]{android.R.id.text1, android.R.id.text2}, gattCharacteristicData, android.R.layout.simple_expandable_list_item_2, new String[]{LIST_NAME, LIST_UUID}, new int[]{android.R.id.text1, android.R.id.text2} ); mGattServicesList.setAdapter(gattServiceAdapter); }
上面的代码如果不太明白可以慢慢看的,先看下面的
我们只需要知道,点击ExpandableListView group为此设备下的所有service,点击group会展开此service下的所有特征
/** * ExpandableListView 子条目点击监听 点击条目,会把正在通信的特征关闭通知,再把点击条目的特征打开通知 */ private final ExpandableListView.OnChildClickListener servicesListClickListener = new ExpandableListView.OnChildClickListener() { @Override public boolean onChildClick(ExpandableListView parent, View v, int groupPosition, int childPosition, long id) { if (mGattCharacteristics != null) { final BluetoothGattCharacteristic characteristic = mGattCharacteristics.get(groupPosition).get(childPosition); /** * 正常流程 * 打开 指定接收特征 (指定UUID)通知 * 在 指定写入特征 写入数据 */ //从指定UUID的特征打开接收(测试用) if (characteristic.getUuid().toString().equals("0000ff02-0000-1000-8000-00805f9b34fb")) { mBluetoothLeService.setCharacteristicNotification( characteristic, true); }// //从指定UUID的特征写入(测试用) if (characteristic.getUuid().toString().equals("0000ff01-0000-1000-8000-00805f9b34fb")) { mBluetoothLeService.write(characteristic); } return true; } return false; } };
本例中
UUID为0000ff02-0000-1000-8000-00805f9b34fb的特征为读取特征
UUID为0000ff01-0000-1000-8000-00805f9b34fb的特征为写入特征
点击0000ff02-0000-1000-8000-00805f9b34fb条目打开读取通知
mBluetoothGatt.setCharacteristicNotification(characteristic, enabled);
点击0000ff01-0000-1000-8000-00805f9b34fb条目写入数据
//写入数据(测试用) public void write(BluetoothGattCharacteristic characteristic) { //握手 characteristic.setValue(hexStringToByteArray("A2")); mBluetoothGatt.writeCharacteristic(characteristic); //握手和命令不能超过4秒,但又不能马上 SystemClock.sleep(500); //命令 characteristic.setValue("SINSAM"); mBluetoothGatt.writeCharacteristic(characteristic); }
数据写入后,就会在mGattCallback的onCharacteristicRead回调里通过characteristic.getValue()
拿到数据了
ok基本总结完了
源码点我
最后一张美图镇楼。。(按照自己对低功耗蓝牙的理解画的图)
- 安卓低功耗蓝牙开发
- 安卓低功耗蓝牙
- 安卓低功耗蓝牙
- 安卓低功耗蓝牙(BLE)开发教程
- 链接:蓝牙:蓝牙低功耗
- TI_ble低功耗蓝牙
- 蓝牙设计 低功耗
- 蓝牙低功耗安全性
- 低功耗蓝牙BLE
- 蓝牙低功耗入门
- 蓝牙低功耗(BLE)
- 低功耗蓝牙学习
- 低功耗蓝牙简述
- 蓝牙低功耗(BLE)
- 安卓低功耗蓝牙——手机作为外围设备
- 4.0低功耗蓝牙解决方案
- 低功耗蓝牙的基础
- 【蓝牙低功耗BLE】引言
- 选择排序函数
- vue2.0之多页面的开发
- iOS WKWebView禁止长按事件(包括超链接)
- sublime 中 swig 文件如何高亮
- LeetCode 165. Compare Version Numbers
- 安卓低功耗蓝牙
- Hadoop五个进程的作用和联系
- js之dom事件的高级补充
- WRTnode-连接互联网
- dorado7通用代码
- ora12514无监听
- PyCaffe-mean文件转换成npy文件
- Webpack飞行手册
- 随手记-融云 聊天界面标题栏被顶上去 解决方法