【Android】蓝牙开发——BLE(低功耗蓝牙)(附完整Demo)

article/2025/10/15 15:21:14

目录

目录

前言

一、相关概念介绍

二、实战开发

三、项目演示

四、Demo案例源码地址

五、更新记录

1、2020/12/29 :修改 setupService()中错误

2、2021/05/14 :更新连接方法(解决部分蓝牙设备连接失败的问题)

3、2022/11/25 :实际开发过程,需要将三个UUID改成具体蓝牙设备的UUID

 问:如何查看具体蓝牙设备中的这三个UUID?

答:手机下载nrf工具app,搜索连接上蓝牙设备后,即可查看蓝牙设备中支持的服务UUID、读写特征UUID等信息。


前言

之前的几篇文章,主要介绍了经典蓝牙开发相关的知识,今天我们就来看看低功耗蓝牙的开发。如果小伙伴们对之前的文章感兴趣兴趣,也可以看看,欢迎提出不足或者建议。

【Android】蓝牙开发——经典蓝牙(附Demo源码)

【Android】蓝牙开发——经典蓝牙配对介绍(通过手机系统蓝牙演示)

【Android】蓝牙开发—— 经典蓝牙配对介绍(Java代码实现演示)附Demo源码

一、相关概念介绍

BLE,全称 Bluetooth Low Energy,即低功耗蓝牙。BLE关键术语和概念,可以查看官网介绍:

蓝牙低功耗概览  |  Android 开发者  |  Android Developers

首先,我们需要知道,BLE设备有以下几个方面的东西:

1、一个BLE设备包含多个服务,这些服务通过UUID来区分。

2、一个服务包含1个或多个特征,这些特征也是通过UUID来区分。

3、一个特征包含一个Value和多个描述符,一个描述符包含一个Value,

其次,我们来看一看Android中BLE相关的对象:

1、BluetoothDevice :蓝牙设备

2、BluetoothGatt:建立的连接

3、BluetoothGattCallback:连接回调

3、BluetoothGattService:服务

4、BluetoothGattCharacteristic:服务的特征

5、BluetoothGattDescriptor:特征的描述

了解了这些之后,我们开始进入BLE开发阶段。

二、实战开发

注意:BLE是在Android 4.3(API 18)以后引入的,所以要进行BLE开发,必须在Android 4.3以上版本的机子上。

1、添加相关权限

(1)蓝牙权限

<!-- 应用使用蓝牙的权限 -->
<uses-permission android:name="android.permission.BLUETOOTH"/>
<!--启动设备发现或操作蓝牙设置的权限-->
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>

(2)位置权限(BLE与经典蓝牙相比,还需要位置权限,如果没有,有的机型是扫描不到设备的,注意Android6.0以后还需要动态申请位置权限)

<!--位置权限-->
<!--Android 10以上系统,需要ACCESS_FINE_LOCATION-->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<!--Android 9以及以下系统,需要ACCESS_FINE_LOCATION-->
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>

2、使用蓝牙之前,首先要检查当前手机是否支持BLE蓝牙。如果支持BLE蓝牙,检查手机蓝牙是否已开启。如果没有开启,则需要先打开蓝牙。打开手机蓝牙,有两种方式,一种是直接enable()打开,另外一种是提示用户打开,推荐第二种方式。

 /*** 检测手机是否支持4.0蓝牙* @param context  上下文* @return true--支持4.0  false--不支持4.0*/private boolean checkBle(Context context){if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {  //API 18 Android 4.3bluetoothManager = (BluetoothManager) context.getSystemService(Context.BLUETOOTH_SERVICE);if(bluetoothManager == null){return false;}bluetooth4Adapter = bluetoothManager.getAdapter();  //BLUETOOTH权限if(bluetooth4Adapter == null){return false;}else{Log.d(TAG,"该设备支持蓝牙4.0");return true;}}else{return false;}}
/*** 获取蓝牙状态*/public boolean isEnable(){if(bluetooth4Adapter == null){return false;}return bluetooth4Adapter.isEnabled();}/*** 打开蓝牙* @param isFast  true 直接打开蓝牙  false 提示用户打开*/public void openBluetooth(Context context,boolean isFast){if(!isEnable()){if(isFast){Log.d(TAG,"直接打开手机蓝牙");bluetooth4Adapter.enable();  //BLUETOOTH_ADMIN权限}else{Log.d(TAG,"提示用户去打开手机蓝牙");Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);context.startActivity(enableBtIntent);}}else{Log.d(TAG,"手机蓝牙状态已开");}}

3、系统蓝牙已打开,则可以开启扫描设备。需要注意的是,扫描设备是耗时的操作,一旦扫描结束,就要及时停止扫描。

  扫描设备  /////扫描设备回调@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2)private BluetoothAdapter.LeScanCallback leScanCallback = new BluetoothAdapter.LeScanCallback() {@Overridepublic void onLeScan(BluetoothDevice bluetoothDevice, int rssi, byte[] bytes) {//在onLeScan()回调中尽量做少的操作,可以将扫描到的设备扔到另一个线程中处理if(bluetoothDevice == null)return;if(bluetoothDevice.getName() != null){Log.d(TAG,bluetoothDevice.getName() + "-->" + bluetoothDevice.getAddress());}else{Log.d(TAG,"null" + "-->" + bluetoothDevice.getAddress());}BLEDevice bleDevice = new BLEDevice(bluetoothDevice,rssi);if(onDeviceSearchListener != null){onDeviceSearchListener.onDeviceFound(bleDevice);  //扫描到设备回调}}};/*** 设置时间段 扫描设备* @param onDeviceSearchListener  设备扫描监听* @param scanTime  扫描时间*/public void startDiscoveryDevice(OnDeviceSearchListener onDeviceSearchListener,long scanTime){if(bluetooth4Adapter == null){Log.e(TAG,"startDiscoveryDevice-->bluetooth4Adapter == null");return;}this.onDeviceSearchListener = onDeviceSearchListener;if(onDeviceSearchListener != null){onDeviceSearchListener.onDiscoveryStart();  //开始扫描回调}if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {Log.d(TAG,"开始扫描设备");bluetooth4Adapter.startLeScan(leScanCallback);}else{return;}//设定最长扫描时间mHandler.postDelayed(stopScanRunnable,scanTime);}private Runnable stopScanRunnable = new Runnable() {@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2)@Overridepublic void run() {if(onDeviceSearchListener != null){onDeviceSearchListener.onDiscoveryOutTime();  //扫描超时回调}//scanTime之后还没有扫描到设备,就停止扫描。stopDiscoveryDevice();}};

4、扫描到目标设备之后,开始建立连接,这里注意,连接之前一定要关闭扫描,否则会影响连接。BLE与经典蓝牙不同,经典蓝牙一旦建立连接,就可以进行数据通讯,而BLE建立连接之后,还需要发现系统服务,获取特定服务及读写特征。

(1)建立连接 & 发现系统服务

/  执行连接  ////连接/通讯结果回调@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2)private BluetoothGattCallback bluetoothGattCallback = new BluetoothGattCallback() {@Overridepublic void onPhyUpdate(BluetoothGatt gatt, int txPhy, int rxPhy, int status) {super.onPhyUpdate(gatt, txPhy, rxPhy, status);}@Overridepublic void onPhyRead(BluetoothGatt gatt, int txPhy, int rxPhy, int status) {super.onPhyRead(gatt, txPhy, rxPhy, status);}//连接状态回调-连接成功/断开连接@Overridepublic void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {super.onConnectionStateChange(gatt, status, newState);Log.d(TAG,"status:" + status);Log.d(TAG,"newState:" + newState);switch(status){case BluetoothGatt.GATT_SUCCESS:Log.w(TAG,"BluetoothGatt.GATT_SUCCESS");break;case BluetoothGatt.GATT_FAILURE:Log.w(TAG,"BluetoothGatt.GATT_FAILURE");break;case BluetoothGatt.GATT_CONNECTION_CONGESTED:Log.w(TAG,"BluetoothGatt.GATT_CONNECTION_CONGESTED");break;case BluetoothGatt.GATT_INSUFFICIENT_AUTHENTICATION:Log.w(TAG,"BluetoothGatt.GATT_INSUFFICIENT_AUTHENTICATION");break;case BluetoothGatt.GATT_INSUFFICIENT_ENCRYPTION:Log.w(TAG,"BluetoothGatt.GATT_INSUFFICIENT_ENCRYPTION");break;case BluetoothGatt.GATT_INVALID_OFFSET:Log.w(TAG,"BluetoothGatt.GATT_INVALID_OFFSET");break;case BluetoothGatt.GATT_READ_NOT_PERMITTED:Log.w(TAG,"BluetoothGatt.GATT_READ_NOT_PERMITTED");break;case BluetoothGatt.GATT_REQUEST_NOT_SUPPORTED:Log.w(TAG,"BluetoothGatt.GATT_REQUEST_NOT_SUPPORTED");break;}BluetoothDevice bluetoothDevice = gatt.getDevice();Log.d(TAG,"连接的设备:" + bluetoothDevice.getName() + "  " + bluetoothDevice.getAddress());isConnectIng = false;//移除连接超时mHandler.removeCallbacks(connectOutTimeRunnable);if(newState == BluetoothGatt.STATE_CONNECTED){Log.w(TAG,"连接成功");//连接成功去发现服务gatt.discoverServices();//设置发现服务超时时间mHandler.postDelayed(serviceDiscoverOutTimeRunnable,MAX_CONNECT_TIME);if(onBleConnectListener != null){onBleConnectListener.onConnectSuccess(gatt,bluetoothDevice,status);   //连接成功回调}}else if(newState == BluetoothGatt.STATE_DISCONNECTED) {//清空系统缓存ClsUtils.refreshDeviceCache(gatt);Log.e(TAG, "断开连接status:" + status);gatt.close();  //断开连接释放连接if(status == 133){//无法连接if(onBleConnectListener != null){onBleConnectListener.onConnectFailure(gatt,bluetoothDevice,"连接异常!",status);  //133连接异常 异常断开Log.e(TAG,"连接失败status:" + status + "  " + bluetoothDevice.getAddress());}}else if(status == 62){//成功连接没有发现服务断开if(onBleConnectListener != null){onBleConnectListener.onConnectFailure(gatt,bluetoothDevice,"连接成功服务未发现断开!",status); //62没有发现服务 异常断开Log.e(TAG,"连接成功服务未发现断开status:" + status);}}else if(status == 0){if(onBleConnectListener != null){onBleConnectListener.onDisConnectSuccess(gatt,bluetoothDevice,status); //0正常断开 回调}}else if(status == 8){//因为距离远或者电池无法供电断开连接// 已经成功发现服务if(onBleConnectListener != null){onBleConnectListener.onDisConnectSuccess(gatt,bluetoothDevice,status); //8断电断开  回调}}else if(status == 34){if(onBleConnectListener != null){onBleConnectListener.onDisConnectSuccess(gatt,bluetoothDevice,status); //34断开}}else {//其它断开连接if(onBleConnectListener != null){onBleConnectListener.onDisConnectSuccess(gatt,bluetoothDevice,status); //其它断开}}}else if(newState == BluetoothGatt.STATE_CONNECTING){Log.d(TAG,"正在连接...");if(onBleConnectListener != null){onBleConnectListener.onConnecting(gatt,bluetoothDevice);  //正在连接回调}}else if(newState == BluetoothGatt.STATE_DISCONNECTING){Log.d(TAG,"正在断开...");if(onBleConnectListener != null){onBleConnectListener.onDisConnecting(gatt,bluetoothDevice); //正在断开回调}}}//发现服务@Overridepublic void onServicesDiscovered(BluetoothGatt gatt, int status) {super.onServicesDiscovered(gatt, status);//移除发现服务超时mHandler.removeCallbacks(serviceDiscoverOutTimeRunnable);Log.d(TAG,"移除发现服务超时");Log.d(TAG,"发现服务");//获取特定服务及特征if(setupService(gatt,serviceUUID,readUUID,writeUUID)){if(onBleConnectListener != null){onBleConnectListener.onServiceDiscoverySucceed(gatt,gatt.getDevice(),status);  //成功发现服务回调}}else{if(onBleConnectListener != null){onBleConnectListener.onServiceDiscoveryFailed(gatt,gatt.getDevice(),"获取服务特征异常");  //发现服务失败回调}}}}@Overridepublic void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {super.onCharacteristicRead(gatt, characteristic, status);Log.d(TAG,"读status: " + status);}//向蓝牙设备写入数据结果回调@Overridepublic void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {super.onCharacteristicWrite(gatt, characteristic, status);if(characteristic.getValue() == null){Log.e(TAG,"characteristic.getValue() == null");return;}//将收到的字节数组转换成十六进制字符串String msg = TypeConversion.bytes2HexString(characteristic.getValue(),characteristic.getValue().length);if(status == BluetoothGatt.GATT_SUCCESS){//写入成功Log.w(TAG,"写入成功:" + msg);if(onBleConnectListener != null){onBleConnectListener.onWriteSuccess(gatt,gatt.getDevice(),characteristic.getValue());  //写入成功回调}}else if(status == BluetoothGatt.GATT_FAILURE){//写入失败Log.e(TAG,"写入失败:" + msg);if(onBleConnectListener != null){onBleConnectListener.onWriteFailure(gatt,gatt.getDevice(),characteristic.getValue(),"写入失败");  //写入失败回调}}else if(status == BluetoothGatt.GATT_WRITE_NOT_PERMITTED){//没有权限Log.e(TAG,"没有权限!");}}//读取蓝牙设备发出来的数据回调@Overridepublic void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {super.onCharacteristicChanged(gatt, characteristic);//接收数据byte[] bytes = characteristic.getValue();Log.w("TAG","收到数据str:" + TypeConversion.bytes2HexString(bytes,bytes.length));if(onBleConnectListener != null){onBleConnectListener.onReceiveMessage(gatt,gatt.getDevice(),characteristic,characteristic.getValue());  //接收数据回调}}@Overridepublic void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {super.onDescriptorRead(gatt, descriptor, status);}@Overridepublic void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {super.onDescriptorWrite(gatt, descriptor, status);}@Overridepublic void onReliableWriteCompleted(BluetoothGatt gatt, int status) {super.onReliableWriteCompleted(gatt, status);Log.d(TAG,"onReliableWriteCompleted");}@Overridepublic void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) {super.onReadRemoteRssi(gatt, rssi, status);if(status == BluetoothGatt.GATT_SUCCESS){Log.w(TAG,"读取RSSI值成功,RSSI值:" + rssi + ",status" + status);if(onBleConnectListener != null){onBleConnectListener.onReadRssi(gatt,rssi,status);  //成功读取连接的信号强度回调}}else if(status == BluetoothGatt.GATT_FAILURE){Log.w(TAG,"读取RSSI值失败,status:" + status);}}//修改MTU值结果回调@Overridepublic void onMtuChanged(BluetoothGatt gatt, int mtu, int status) {super.onMtuChanged(gatt, mtu, status);///设置mtu值,即bluetoothGatt.requestMtu()时触发,提示该操作是否成功if(status == BluetoothGatt.GATT_SUCCESS){  //设置MTU成功  //MTU默认取的是23,当收到 onMtuChanged 后,会根据传递的值修改MTU,注意由于传输用掉3字节,因此传递的值需要减3。//mtu - 3Log.w(TAG,"设置MTU成功,新的MTU值:" + (mtu-3) + ",status" + status);if(onBleConnectListener != null){onBleConnectListener.onMTUSetSuccess("设置后新的MTU值 = " + (mtu-3) + "   status = " + status,mtu - 3);  //MTU设置成功}}else if(status == BluetoothGatt.GATT_FAILURE){  //设置MTU失败  Log.e(TAG,"设置MTU值失败:" + (mtu-3) + ",status" + status);if(onBleConnectListener != null){onBleConnectListener.onMTUSetFailure("设置MTU值失败:" + (mtu-3) + "   status:" + status);  //MTU设置失败}}}};/*** 通过蓝牙设备连接* @param context  上下文* @param bluetoothDevice  蓝牙设备* @param outTime          连接超时时间* @param onBleConnectListener  蓝牙连接监听者* @return*/@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2)public BluetoothGatt connectBleDevice(Context context, BluetoothDevice bluetoothDevice, long outTime,OnBleConnectListener onBleConnectListener){if(bluetoothDevice == null){Log.e(TAG,"addBLEConnectDevice-->bluetoothDevice == null");return null;}this.onBleConnectListener = onBleConnectListener;this.curConnDevice = bluetoothDevice;Log.d(TAG,"开始准备连接:" + bluetoothDevice.getName() + "-->" + bluetoothDevice.getAddress());//出现 BluetoothGatt.android.os.DeadObjectException 蓝牙没有打开try{if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){mBluetoothGatt = bluetoothDevice.connectGatt(context, false, bluetoothGattCallback,BluetoothDevice.TRANSPORT_LE,BluetoothDevice.PHY_LE_1M_MASK);}else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {mBluetoothGatt = bluetoothDevice.connectGatt(context, false, bluetoothGattCallback,BluetoothDevice.TRANSPORT_LE);} else {mBluetoothGatt = bluetoothDevice.connectGatt(context, false, bluetoothGattCallback);}}catch(Exception e){Log.e(TAG,"e:" + e.getMessage());}mHandler.postDelayed(connectOutTimeRunnable,outTime);return mBluetoothGatt;}//连接超时private Runnable connectOutTimeRunnable = new Runnable() {@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2)@Overridepublic void run() {if(mBluetoothGatt == null){Log.e(TAG,"connectOuttimeRunnable-->mBluetoothGatt == null");return;}isConnectIng = false;mBluetoothGatt.disconnect();//连接超时当作连接失败回调if(onBleConnectListener != null){onBleConnectListener.onConnectFailure(mBluetoothGatt,curConnDevice,"连接超时!",-1);  //连接失败回调}}};//发现服务超时private Runnable serviceDiscoverOutTimeRunnable = new Runnable() {@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2)@Overridepublic void run() {if(mBluetoothGatt == null){Log.e(TAG,"connectOuttimeRunnable-->mBluetoothGatt == null");return;}isConnectIng = false;mBluetoothGatt.disconnect();//发现服务超时当作连接失败回调if(onBleConnectListener != null){onBleConnectListener.onConnectFailure(mBluetoothGatt,curConnDevice,"发现服务超时!",-1);  //连接失败回调}}};

(2)发现系统服务之后,还需要获取特定服务及读写特征才能进行数据通讯。一般,读特征是用来读取蓝牙设备发出来的数据,写特征是向蓝牙设备写入数据,其中,读特征一定要设置打开通知,否则接收不到消息。

/*** 获取特定服务及特征* 1个serviceUUID -- 1个readUUID -- 1个writeUUID* @param bluetoothGatt* @param serviceUUID* @param readUUID* @param writeUUID* @return*/@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2)private boolean setupService(BluetoothGatt bluetoothGatt,String serviceUUID,String readUUID,String writeUUID) {if (bluetoothGatt == null) {Log.e(TAG, "setupService()-->bluetoothGatt == null");return false;}if(serviceUUID == null){Log.e(TAG, "setupService()-->serviceUUID == null");return false;}for (BluetoothGattService service : bluetoothGatt.getServices()) {
//            Log.d(TAG, "service = " + service.getUuid().toString());if (service.getUuid().toString().equals(serviceUUID)) {bluetoothGattService = service;}}//通过上面方法获取bluetoothGattService
//        bluetoothGattService = bleManager.getBluetoothGattService(bluetoothGatt,ConsData.MY_BLUETOOTH4_UUID);if (bluetoothGattService == null) {Log.e(TAG, "setupService()-->bluetoothGattService == null");return false;}Log.d(TAG, "setupService()-->bluetoothGattService = " + bluetoothGattService.toString());if(readUUID == null || writeUUID == null){Log.e(TAG, "setupService()-->readUUID == null || writeUUID == null");return false;}for (BluetoothGattCharacteristic characteristic : bluetoothGattService.getCharacteristics()) {if (characteristic.getUuid().toString().equals(readUUID)) {  //读特征readCharacteristic = characteristic;} else if (characteristic.getUuid().toString().equals(writeUUID)) {  //写特征writeCharacteristic = characteristic;}}if (readCharacteristic == null) {Log.e(TAG, "setupService()-->readCharacteristic == null");return false;}if (writeCharacteristic == null) {Log.e(TAG, "setupService()-->writeCharacteristic == null");return false;}//打开读通知enableNotification(true, bluetoothGatt, readCharacteristic);//重点中重点,需要重新设置List<BluetoothGattDescriptor> descriptors = readCharacteristic.getDescriptors();for (BluetoothGattDescriptor descriptor : descriptors) {descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);bluetoothGatt.writeDescriptor(descriptor);}//延迟2s,保证所有通知都能及时打开mHandler.postDelayed(new Runnable() {@Overridepublic void run() {}}, 2000);return true;}/  打开通知  ///*** 设置读特征接收通知* @param enable  为true打开通知* @param gatt    连接* @param characteristic  特征*/@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2)public void enableNotification(boolean enable, BluetoothGatt gatt, BluetoothGattCharacteristic characteristic){if(gatt == null){Log.e(TAG,"enableNotification-->gatt == null");return;}if(characteristic == null){Log.e(TAG,"enableNotification-->characteristic == null");return;}//这一步必须要有,否则接收不到通知gatt.setCharacteristicNotification(characteristic,enable);}

5、数据通讯

(1)发送数据

mBluetoothGatt.writeCharacteristic()方法的返回值,并不能真正的表示数据是否发送成功,而是通过BluetoothGattCallback回调方法onCharacteristicWrite()来判断数据是否已成功写入底层。

 //向蓝牙设备写入数据结果回调@Overridepublic void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {super.onCharacteristicWrite(gatt, characteristic, status);if(characteristic.getValue() == null){Log.e(TAG,"characteristic.getValue() == null");return;}//将收到的字节数组转换成十六进制字符串String msg = TypeConversion.bytes2HexString(characteristic.getValue(),characteristic.getValue().length);if(status == BluetoothGatt.GATT_SUCCESS){//写入成功Log.w(TAG,"写入成功:" + msg);if(onBleConnectListener != null){onBleConnectListener.onWriteSuccess(gatt,gatt.getDevice(),characteristic.getValue());  //写入成功回调}}else if(status == BluetoothGatt.GATT_FAILURE){//写入失败Log.e(TAG,"写入失败:" + msg);if(onBleConnectListener != null){onBleConnectListener.onWriteFailure(gatt,gatt.getDevice(),characteristic.getValue(),"写入失败");  //写入失败回调}}else if(status == BluetoothGatt.GATT_WRITE_NOT_PERMITTED){//没有权限Log.e(TAG,"没有权限!");}}

///  发送数据  ////*** 发送消息  byte[]数组* @param msg  消息* @return  true  false*/@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2)public boolean sendMessage( byte[] msg){if(writeCharacteristic == null){Log.e(TAG,"sendMessage(byte[])-->writeGattCharacteristic == null");return false;}if(mBluetoothGatt == null){Log.e(TAG,"sendMessage(byte[])-->mBluetoothGatt == null");return false;}boolean  b = writeCharacteristic.setValue(msg);Log.d(TAG, "写特征设置值结果:" + b);return mBluetoothGatt.writeCharacteristic(writeCharacteristic);}

(2)接收数据

接收的数据是直接通过BluetoothGattCallback回调方法onCharacteristicChanged()来获取的。

//读取蓝牙设备发出来的数据回调@Overridepublic void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {super.onCharacteristicChanged(gatt, characteristic);//接收数据byte[] bytes = characteristic.getValue();Log.w("TAG","收到数据str:" + TypeConversion.bytes2HexString(bytes,bytes.length));if(onBleConnectListener != null){onBleConnectListener.onReceiveMessage(gatt,gatt.getDevice(),characteristic,characteristic.getValue());  //接收数据回调}}

6、断开连接

BLE通讯结束之后,需要及时断开连接,并且在断开连接的回调处释放资源。否则会导致下一次执行连接操作时,导致133异常。所以,一般连接出现133异常,都是因为断开后及时释放资源。

断开连接的结果是在BluetoothGattCallback回调方法onConnectionStateChange()来获取的。(可查看上面建立连接处的代码)

 ///  断开连接  ////*** 断开连接*/@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2)public void disConnectDevice(){if(mBluetoothGatt == null){Log.e(TAG,"disConnectDevice-->bluetoothGatt == null");return;}//系统断开mBluetoothGatt.disconnect();}

三、项目演示

(1)扫描目标设备(BLEyqy),点击“连接”按钮, 会在“搜索”按钮下方显示连接结果 。

BLE扫描连接

(2)手机给目标设备发送数据成功之后,蓝牙设备把接收到的数据再回发送给手机。

BLE发送接收数据

(3)断开连接。点击“断开”按钮, 会在“搜索”按钮下方显示断开结果 。

BLE断开连接

四、Demo案例源码地址

码云:Android Bluetooth Low Energy: Android 低功耗蓝牙开发

CSDN:AndroidBLE.rar-Android代码类资源-CSDN下载

五、更新记录

1、2020/12/29 :修改 setupService()中错误

错误

2、2021/05/14 :更新连接方法(解决部分蓝牙设备连接失败的问题)

3、2022/11/25 :实际开发过程,需要将三个UUID改成具体蓝牙设备的UUID

 问:如何查看具体蓝牙设备中的这三个UUID?

答:手机下载nrf工具app,搜索连接上蓝牙设备后,即可查看蓝牙设备中支持的服务UUID、读写特征UUID等信息。


http://chatgpt.dhexx.cn/article/Nfy9tnko.shtml

相关文章

【Bluetooth开发】蓝牙开发入门

BLE 蓝牙设备在生活中无处不在&#xff0c;但是我们也只是将其作为蓝牙模块进行使用&#xff0c;发送简单的AT命令实现数据收发。 那么&#xff0c;像对于一些复杂的使用场合&#xff1a;“车载蓝牙”、"智能手表"、“蓝牙音箱”等&#xff0c;我们不得不去了解底层…

【Bluetooth蓝牙开发】一、蓝牙开发入门

一、蓝牙开发入门 文章目录 一、蓝牙开发入门 1、蓝牙概念2、蓝牙发展历程3、蓝牙技术概述 3.1 Basic Rate(BR)3.2 Low Energy&#xff08;LE&#xff09; 4、常见蓝牙架构 4.1 SOC蓝牙单芯片方案4.2 SOC蓝牙MCU方案4.3 蓝牙host controller分开方案4.4 使用场景 5、参考文档 …

vs2012做ArcGIS二次开发前期准备

解压ArcGIS 1.双击ESRI 2.点击 一路next&#xff0c;自己选择安装路径&#xff0c;建议放在非系统盘 3.开始菜单-ArcGIS-License Server Administrator 4.点击“stop/停止"&#xff0c;再点击确定 5.将破解文件中的两个文件拷到D:\Program Files (x86)\ArcGIS\License10.…

arcgis 二次开发学习笔记(一):了解二次开发有关的软件及其之间的关系

【废话篇】今天是大三开学的第一天课&#xff0c;终于意识到我口中念念不忘却没付出实际行动的“考研”来了。考研目标现在为止还没有很明确&#xff0c;只是不甘屈于人后。周围太多生活得很辛苦的人&#xff0c;只是不愿意我这一辈子也为了有关qian的小事斤斤计较&#xff0c;…

Arcobjects for java:Arcgis二次开发入门,开发一个基本地图组件

一、目的 因学习需要&#xff0c;使用Java进行Arcgis二次开发。当前对arcgis进行二次开发使用的语言基本是C#&#xff0c;使用Java对Arcgis进行二次开发的很少。于是使用java在idea上进行Arcgis二次开发&#xff0c;给入门的同学做参考&#xff0c;我自己也处于入门阶段&#…

ArcGIS二次开发基础教程(00):基础界面设计

ArcGIS二次开发基础教程(00) : 基础界面设计 (开发环境&#xff1a;VS2010ArcEngine10.2C# &#xff1b;鉴于学习ArcGIS二次开发的同学都有一定的WinForm开发和ArcGIS软件使用基础&#xff0c;故此教程不再对一些基础内容作详细阐述&#xff09; 首先新建一个Windows窗体应用程…

【ArcGIS二次开发】TOCControl右键菜单功能实现

1、添加现有项 ①右击解决方案中的项目&#xff0c;添加TOCControlContextMenu中的LayerSelectable、LayerVisibility、RemoveLayer、ZoomToLayer ②点击菜单栏中的项目&#xff0c;添加引用ESRI.ArcGIS.ADF.Local ③修改RemoveLayer中的命名空间为项目名称EngineMapTest&#…

arcgis python实例_arcgis二次开发_arcgis二次开发python_arcgis二次开发实例

[1.rar] - QQ连连看的源码.单消秒杀挂机等功能喜欢的朋友请拿去研究 [qqCHAR.rar] - qq 验证码识别程序 可以叫准确的识别出qq登陆前的验证码 [1.rar] - 本书以Visualc作为开发语言&#xff0c;结合大量实例&#xff0c;详细介绍了利用Arcobjects组件进行GIS二次开发的方法和…

【ArcGIS二次开发】Engine界面搭建

1、新建窗体项目Windows Appplication(Engine) 2、添加menuStrip、statusStrip和ToolbarControl控件&#xff0c;并设置相应的Dock属性为Top和Right 3、用SplitContainer控件把显示区域分成三部分&#xff0c;并设置splitContatiner1的Orientation属性为Horizontal 4、添加TabC…

ArcGIS二次开发基础教程(02):地图导航和鹰眼

ArcGIS二次开发基础教程(02)&#xff1a;地图导航和鹰眼 地图导航&#xff08;主要是调用命令和工具&#xff09; 地图的放缩和漫游 if(axMapControl1.CurrentTool null) {ICommand icc;//地图放大ITool tool new ControlsMapZoomInToolClass();//地图缩小//ITool tool n…

arcgis java 二次开发_arcgis二次开发_cad二次开发_java arcgis二次开发

属性查询是GIS应用不可缺少的重要功能&#xff0c;尤其是在各种业务系统中&#xff0c;根据用户输入相应的查询条件&#xff0c;从属性要素中快速定位到用户感兴趣的要素&#xff0c;为业务应用提供了便利。本文就来聊一聊QGis二次开发中如何实现属性查询功能。 其实这个功能我…

AE+ArcGIS二次开发课程设计(基于C#)

AEArcGIS二次开发课程设计&#xff08;基于C#&#xff09; 1.工作内容2.程序功能介绍3.功能模块介绍3.1 实现【创建TIN】说明3.1.1 功能说明3.1.2 代码实现&#xff08;包含了所有主要的代码&#xff0c;库引用自行导入&#xff09; 3.2 实现【TIN坡度坡向分析】说明3.2.1 功能…

ArcGIS二次开发前言

ArcGIS二次开发前言 前言环境常见bug解决方案 前言 自毕业成为GIS开发工程师已有一年多的时间&#xff0c;时间很短&#xff0c;短到不过人一生中工作时限的3.75%&#xff0c;时间很长&#xff0c;长到收藏夹已经从零攒到了一千四百多条记录&#xff0c;OneNote上也记录了几十…

ArcGIS Engine二次开发

目录 1 前言 2 准备工作 2.1 License的加入 2.2 ToolStrip控件 2.3 MenuStrip控件 2.4 帮助文档的查看 3 数据加载 3.1 矢量数据的加载 3.2 栅格数据的加载 4 地图浏览功能 1 前言 这是一份关于ArcGIS Engine二次开发的一份报告总结&#xff0c;在这份报告中包含了简单的…

ArcGIS二次开发知识点总结

空间分析定义&#xff1a;空间分析是指分析具有空间坐标或相对位置的数据和过程的理论和方法&#xff0c;是对地理空间现象的定量研究&#xff0c;其目的在于提取并传输空间数据中隐含的空间信息。 叠置分析定义&#xff1a;是指将同一坐标系统下不同信息表达的两组或多组专题…

【ArcGIS Pro二次开发】(31):ArcGIS Pro中的多线程

ArcGIS Pro与旧的ArcGIS桌面应用程序的显著不同之处在于&#xff0c;它采用多线程架构&#xff0c;可以有效的发挥多核CPU的优势。这使得二次开发工具的性能变得更好&#xff0c;但也对开发工作带来了更多的难点和挑战。 一、多线程需要注意的问题 一般情况下&#xff0c;为了…

GIS二次开发:实验一 ArcGIS Engine 开发初步

实验一 ArcGIS Engine 开发初步 一、实验目的 掌握ArcGIS Engine的安装&#xff1b;熟悉ArcGIS Engine中几个常用的控件&#xff1b;搭建第一个简单的ArcGIS Engine 程序&#xff1b;通过ICommand接口添加地图浏览工具。 二、实验仪器与设备 计算机、visual studio 软件、A…

Arcgis二次开发软件安装(Arcgis10.2、VS2012、AE10.2)

目录 一、序言 二、Arcgis10.2安装 &#xff08;一&#xff09;安装ArcGIS License Manager 1.1 ArcGIS License Manager安装 1.2 ArcGIS License Manager配置 &#xff08;二&#xff09;安装ArcGIS Desktop 1.1ArcGIS Desktop安装 1.2.ArcGIS文件替换 1.3中文显示与…

StackPanel 实现从上往下+从右往左 排列+RenderTransform特效实例分析

StackPanel:将子元素排列到可沿水平或垂直放置的行。 参考资料&#xff1a; 1. StackPanel类 2. Silverlight学习笔记&#xff08;九&#xff09;——RenderTransform特效【五种基本变换】及【矩阵变换MatrixTransform】 3. MatrixTransform矩阵变换 stack表明StackPane…

控件篇 - 子控件在StackPanel里的居中问题

如下面代码&#xff1a; <StackPanel Width"200" Height"80" Background"Tomato"><TextBlock HorizontalAlignment"Center" VerticalAlignment"Center" Text"ABCD"/></StackPanel> 原意是想通…