当前位置: 首页 > news >正文

android BLE 蓝牙的连接(二)

下面是基于实际的项目得到的具体步骤及核心代码

1、权限问题

先判断手机是否满足android4.3以上版本,再判断手机是否开启蓝牙 主要涉及蓝牙权限和位置权限,注意不同android版本之间权限申请的差异,以及android权限动态申请和静态申请的区别

//动态申请权限相关
private String[] requestPermissionArray = new String[]{Manifest.permission.ACCESS_FINE_LOCATION,Manifest.permission.ACCESS_COARSE_LOCATION,//Manifest.permission.ACCESS_BACKGROUND_LOCATION,Manifest.permission.BLUETOOTH,Manifest.permission.BLUETOOTH_ADMIN,Manifest.permission.BLUETOOTH_SCAN,Manifest.permission.BLUETOOTH_ADVERTISE,Manifest.permission.BLUETOOTH_CONNECT,Manifest.permission.WRITE_EXTERNAL_STORAGE,Manifest.permission.READ_EXTERNAL_STORAGE
};/*** 初始化权限*/
public void initNeedPermissions() {/**Android 6.0以上动态申请权限*/if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M){final PermissionRequest permissionRequest = new PermissionRequest();permissionRequest.requestRuntimePermission(MainActivity.this, requestPermissionArray, new PermissionListener() {@Overridepublic void onGranted() {Log.d(TAG,"所有权限已被授予");}/**用户勾选“不再提醒”拒绝权限后,关闭程序再打开程序只进入该方法!*/@Overridepublic void onDenied(List<String> deniedPermissions) {deniedPermissionList = deniedPermissions;for (String deniedPermission : deniedPermissionList) {Log.e(TAG,"被拒绝权限:" + deniedPermission);}}});}
}

 静态权限

    <uses-feature android:name="android.hardware.camera" /> <uses-feature android:name="android.hardware.camera.autofocus" /> <uses-permission android:name="android.permission.BLUETOOTH" /> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /> <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" /><uses-permission android:name="android.permission.BLUETOOTH_SCAN" /><uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" /><uses-permission android:name="android.permission.READ_CONTACTS" /><uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /><uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <uses-permission android:name="android.permission.INTERNET" />

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、搜索设备

搜索蓝牙:搜索蓝牙,回调接口中查看ble设备相关信息,一定时间停止扫描 扫描设备是耗时的操作,一旦扫描结束,就要及时停止扫描。

    //扫描设备//扫描设备回调@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();}

 7、过程中遇到过的实际问题

暂无

相关文章:

android BLE 蓝牙的连接(二)

下面是基于实际的项目得到的具体步骤及核心代码 1、权限问题 先判断手机是否满足android4.3以上版本&#xff0c;再判断手机是否开启蓝牙 主要涉及蓝牙权限和位置权限&#xff0c;注意不同android版本之间权限申请的差异&#xff0c;以及android权限动态申请和静态申请的区别 …...

改编pikachu的打靶经历(题目不全)

前言 题目很少&#xff0c;只做了一些。正常版本的&#xff0c;完整的pikachu可参考下面这个师傅写的 https://www.cnblogs.com/henry666/p/16947270.html xss &#xff08;get&#xff09;反射xss 先尝试 1 这里有长度限制&#xff0c;而且&#xff0c;我改了长度&#xf…...

Linux进阶 修改文件所有者

修改文件所属组群——chgrp 修改文件所属组群很简单-chgrp命令,就是change group的缩写(我们可以利用这些来记忆命令) 语法:chgrp 组群 文件名/目录 举例: [root@redhat ~]# groupadd groupa[root@redhat ~]# groupadd groupb[root@redhat ~]# useradd -g groupa zgz[r…...

第312题|二重积分求旋转体体积(二)|武忠祥老师每日一题

解题思路&#xff1a;先画出图像&#xff0c;再利用旋转体体积计算公式进行解题。 1. 旋转体体积计算公式&#xff1a; 2.点到直线计算公式&#xff1a; 有了上面两条知识储备之后我们开始计算。 第一步&#xff1a;先计算出点到直线的距离&#xff1a; ymx&#xff0c;y-mx…...

redis基本数据结构-set

文章目录 1. set的基本介绍1.1. set底层结构之hash表的简单介绍1.2. 常用命令 2. 常见的业务场景2.1. 标签系统2.2. 社交网络好友关系 1. set的基本介绍 参考链接&#xff1a;https://mp.weixin.qq.com/s/srkd73bS2n3mjIADLVg72A redis 的 set 数据结构是一个无序的集合&#…...

Android 应用安装-提交阶段

经过前面准备、浏览、协调这些步骤&#xff0c;马上要进入提交阶段了。所谓提交&#xff0c;就是把这些安装应用的相关信息和状态都放到系统中。对于已安装普通应用&#xff0c;它其实分为两个步骤&#xff0c;先卸载旧包&#xff0c;再安装新包。当然&#xff0c;如果是新安装…...

强化学习Reinforcement Learning|Q-Learning|SARSA|DQN以及改进算法

一、强化学习RL 强化学习是机器学习的一个重要的分支&#xff0c;是一种有效的工具&#xff0c;在文献中被广泛用于解决MDP问题。在一个强化学习过程中&#xff0c;一个智能体只能通过和它所处的环境互动学习最优策略。特别地&#xff0c;智能体首先观察自己当前的状态&#xf…...

【HarmonyOS NEXT开发】如何设置水平/垂直方向的左/居中/右对齐——RelativeContainer的AlignRules设置

文章目录 【HarmonyOS NEXT开发】如何设置水平/垂直方向的左/居中/右对齐——RelativeContainer的AlignRules设置RelativeContainer 和 AlignRules 的关系AlignRules 语法详解 【HarmonyOS NEXT开发】如何设置水平/垂直方向的左/居中/右对齐——RelativeContainer的AlignRules设…...

java之认识异常

在 Java 中&#xff0c;异常&#xff08;Exception&#xff09;用于处理程序运行时出现的错误或异常情况。Java 的异常处理机制基于 try, catch, finally 和 throw 关键字。 1.异常的分类&#xff1a; 1.1&#xff1a;检查型异常(CheckedException)&#xff1a; 定义:程序在…...

JSON处理工具类

JSON处理工具类 import org.json.JSONArray; import org.json.JSONObject;import java.util.ArrayList; import java.util.List;/*** JSON处理工具类*/ public class JsonUtils {/****将json字符串转为map* param json* return java.util.Map<java.lang.String, java.lang.O…...

2022高教社杯全国大学生数学建模竞赛C题 问题一(2) Python代码演示

目录 1.2 结合玻璃的类型,分析文物样品表面有无风化化学成分含量的统计规律数据预处理绘图热力图相关系数图百分比条形图箱线图小提琴图直方图KED图描述性统计分析偏度系数峰度系数其它统计量1.2 结合玻璃的类型,分析文物样品表面有无风化化学成分含量的统计规律 数据预处理 …...

ARACom Proxy Class API 概念

1. Proxy Class 概述 生成方式&#xff1a;Proxy Class 是从 AutoSar 元模型的服务接口描述中生成的&#xff0c;ara::com 标准化了其接口&#xff0c;AP 产品供应商的工具链会生成实现该接口的代理实现类。 命名空间&#xff1a;ara::com 期望代理相关的工件在命名空间 “pro…...

【Scala入门学习】基本数据类型和变量声明

1. 基本数据类型 scala 的基本类型有 9种&#xff1a; Byte、Char、Short、Int、Long、Float、Double、Boolean、Unit Scala中没有基本数据类型的概念&#xff0c;所有的类型都是对象。 AnyVal&#xff1a;代表所有基本类型。 AnyRef&#xff1a;代表所以引用类型&#xff…...

C#基础(13)结构体

前言 随着函数的讲解完成&#xff0c;我想你已经初步有了写一些复杂逻辑功能的能力&#xff0c;但是我们会发现其实在我们大部分实际开发情况中&#xff0c;很多我们需要写的变量可能不只有一个属性。 他可能有很多变量&#xff0c;那这时候我们如果要把这些变量集中到一个东…...

Excel图片批量插入单元格排版处理插件【图片大师】

为了方便大家在图片的插入排版的重复工作中解放出来&#xff0c;最近发布了一款批量插入图片的插件&#xff0c;欢迎大家下载&#xff0c;免费试用。 这是图片的文件夹&#xff1a; 主要功能如下: 1&#xff0c;匹配单元格名称的多张图批量插入到一个单元格 该功能支持设置图…...

应用性能优化实践(二)提升应用启动和响应速度

一、提升应用启动和响应速度的方法 1、冷启动过程简介 应用启动时&#xff0c;后台无该应用的进程&#xff0c;需要创建新的进程&#xff0c;这种启动方式叫冷启动。 2、使用异步加载 使用异步加载可以在后台线程中处理耗时操作&#xff0c;从而提升应用响应速度。 3、延迟加载…...

接口测试系列文章专题

在你眼中什么是接口 HTTP协议 什么是接口测试 接口测试之工具 fiddler工具的原理 fiddler工具界面详解 fiddler工具的基本使用 fiddler使如何对手机app进行抓包的呢 fiddler手机app抓包教程 Charles自定义接口返回的数据内容 常用接口工具postman的基本使用方式 pos…...

Unity Hub自动安装指定版本Unity的Android开发环境

Unity开发Android环境要求SDK、DNK、JDK、Gradle版本都要对才能发布APK&#xff0c;自己去配置很容易出错。Unity Hub可以自动安装指定版本Unity的Android开发环境。 1.安装国内用的UnityHub&#xff08;我这里用的3.3.2-c6&#xff09; 2.找到对应的Unity版本 3.点击【从Unit…...

从0开始学ARM

1. ARM模式和寄存器 1.1 ARM处理器工作模式 Cortex系列之前的ARM处理器工作模式一共有7种。 1.1.1 工作模式 Cortex系列的ARM处理器工作模式有8种&#xff0c;多了1个monitor模式&#xff0c;如下图所示&#xff1a; ARM之所以设计出这么多种模式出来&#xff0c;就是为了…...

每日一题——第九十四题

// SortNumInFile.cpp : 此文件包含 “main” 函数。程序执行将在此处开始并结束。 // 题目&#xff1a;将一个文本文件number.txt中的数字按照从小到大排列后&#xff0c;重新写入到该文件中&#xff0c;要求排序前和排序后都输出该文件的内容。该文件中共有20个整数&#xf…...

conda相比python好处

Conda 作为 Python 的环境和包管理工具&#xff0c;相比原生 Python 生态&#xff08;如 pip 虚拟环境&#xff09;有许多独特优势&#xff0c;尤其在多项目管理、依赖处理和跨平台兼容性等方面表现更优。以下是 Conda 的核心好处&#xff1a; 一、一站式环境管理&#xff1a…...

【JavaEE】-- HTTP

1. HTTP是什么&#xff1f; HTTP&#xff08;全称为"超文本传输协议"&#xff09;是一种应用非常广泛的应用层协议&#xff0c;HTTP是基于TCP协议的一种应用层协议。 应用层协议&#xff1a;是计算机网络协议栈中最高层的协议&#xff0c;它定义了运行在不同主机上…...

以下是对华为 HarmonyOS NETX 5属性动画(ArkTS)文档的结构化整理,通过层级标题、表格和代码块提升可读性:

一、属性动画概述NETX 作用&#xff1a;实现组件通用属性的渐变过渡效果&#xff0c;提升用户体验。支持属性&#xff1a;width、height、backgroundColor、opacity、scale、rotate、translate等。注意事项&#xff1a; 布局类属性&#xff08;如宽高&#xff09;变化时&#…...

【入坑系列】TiDB 强制索引在不同库下不生效问题

文章目录 背景SQL 优化情况线上SQL运行情况分析怀疑1:执行计划绑定问题?尝试:SHOW WARNINGS 查看警告探索 TiDB 的 USE_INDEX 写法Hint 不生效问题排查解决参考背景 项目中使用 TiDB 数据库,并对 SQL 进行优化了,添加了强制索引。 UAT 环境已经生效,但 PROD 环境强制索…...

java调用dll出现unsatisfiedLinkError以及JNA和JNI的区别

UnsatisfiedLinkError 在对接硬件设备中&#xff0c;我们会遇到使用 java 调用 dll文件 的情况&#xff0c;此时大概率出现UnsatisfiedLinkError链接错误&#xff0c;原因可能有如下几种 类名错误包名错误方法名参数错误使用 JNI 协议调用&#xff0c;结果 dll 未实现 JNI 协…...

关于iview组件中使用 table , 绑定序号分页后序号从1开始的解决方案

问题描述&#xff1a;iview使用table 中type: "index",分页之后 &#xff0c;索引还是从1开始&#xff0c;试过绑定后台返回数据的id, 这种方法可行&#xff0c;就是后台返回数据的每个页面id都不完全是按照从1开始的升序&#xff0c;因此百度了下&#xff0c;找到了…...

全志A40i android7.1 调试信息打印串口由uart0改为uart3

一&#xff0c;概述 1. 目的 将调试信息打印串口由uart0改为uart3。 2. 版本信息 Uboot版本&#xff1a;2014.07&#xff1b; Kernel版本&#xff1a;Linux-3.10&#xff1b; 二&#xff0c;Uboot 1. sys_config.fex改动 使能uart3(TX:PH00 RX:PH01)&#xff0c;并让boo…...

Map相关知识

数据结构 二叉树 二叉树&#xff0c;顾名思义&#xff0c;每个节点最多有两个“叉”&#xff0c;也就是两个子节点&#xff0c;分别是左子 节点和右子节点。不过&#xff0c;二叉树并不要求每个节点都有两个子节点&#xff0c;有的节点只 有左子节点&#xff0c;有的节点只有…...

RNN避坑指南:从数学推导到LSTM/GRU工业级部署实战流程

本文较长&#xff0c;建议点赞收藏&#xff0c;以免遗失。更多AI大模型应用开发学习视频及资料&#xff0c;尽在聚客AI学院。 本文全面剖析RNN核心原理&#xff0c;深入讲解梯度消失/爆炸问题&#xff0c;并通过LSTM/GRU结构实现解决方案&#xff0c;提供时间序列预测和文本生成…...

Unsafe Fileupload篇补充-木马的详细教程与木马分享(中国蚁剑方式)

在之前的皮卡丘靶场第九期Unsafe Fileupload篇中我们学习了木马的原理并且学了一个简单的木马文件 本期内容是为了更好的为大家解释木马&#xff08;服务器方面的&#xff09;的原理&#xff0c;连接&#xff0c;以及各种木马及连接工具的分享 文件木马&#xff1a;https://w…...