当前位置: 首页 > 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…...

多车环境下车载毫米波雷达是否会相互干扰?

在汽车工业迈向智能化与自动化的进程中&#xff0c;毫米波雷达已然成为了车辆感知体系中不可或缺的一部分。这种波长介于1毫米至10毫米之间的电磁波进行探测的装置&#xff0c;凭借其能够穿透雨雪、浓雾及强光直射的全天候工作能力&#xff0c;为高级驾驶辅助系统提供了关键的距…...

GESP2025年3月认证C++三级( 第三部分编程题(2、词频统计)

一、&#x1f9d9;‍♂️ 故事背景&#xff1a;单词王国选举大会 在“单词王国”里 &#x1f3f0; 有很多单词来参加“最受欢迎单词”比赛&#xff01; &#x1f449; 比如&#xff1a; Apple banana apple Orange banana apple二、&#x1f3af; 任务 &#x1f449; 找出&…...

考研408计算机学科专业基础综合——计算机网络复习

考研408计算机学科专业基础综合 计算机网络复习 核心说明&#xff1a;本笔记聚焦考研408计算机网络高频考点、必背知识点&#xff0c;贴合命题规律&#xff08;选择题为主、大题集中在核心协议&#xff09;&#xff0c;剔除冗余内容&#xff0c;突出重难点&#xff0c;适配冲刺…...

终极跨平台Iwara视频社区客户端:5个核心功能完全指南

终极跨平台Iwara视频社区客户端&#xff1a;5个核心功能完全指南 【免费下载链接】iwrqk Unofficial Iwara Flutter Client 项目地址: https://gitcode.com/gh_mirrors/iw/iwrqk IwrQk是一款基于Flutter框架开发的跨平台Iwara视频社区客户端应用&#xff0c;为你提供在i…...

英语祈使句结构

英语祈使句:极简结构 + 直白例句(一看就懂) 核心: 主语一律省略(默认 You 你 / 你们),直接用动词开头,用来命令 / 请求 / 禁止 一、基础核心结构(3 种万能模板) 1 肯定祈使句(直接让做某事) 结构:动词原形 + 其他成分(宾语 / 状语) 例句: Open the door.…...

DXVK:彻底解决Linux游戏兼容性难题的Vulkan翻译层

DXVK&#xff1a;彻底解决Linux游戏兼容性难题的Vulkan翻译层 【免费下载链接】dxvk Vulkan-based implementation of D3D8, 9, 10 and 11 for Linux / Wine 项目地址: https://gitcode.com/gh_mirrors/dx/dxvk 你是否曾经因为心爱的Windows游戏无法在Linux上运行而感到…...

新谈设计模式 Chapter 05 — 原型模式 Prototype

Chapter 05 — 原型模式 Prototype灵魂速记&#xff1a;复印机——照着原件复制一份&#xff0c;省得从头再造。秒懂类比 你有一份精心排版的简历模板。每次投不同公司&#xff0c;你不是从头写一份新的&#xff0c;而是复印一份&#xff0c;改几个字。 原型模式就是这个"…...

突破Cursor限制的5个实用技巧:从原理到实践

突破Cursor限制的5个实用技巧&#xff1a;从原理到实践 【免费下载链接】cursor-free-vip [Support 0.45]&#xff08;Multi Language 多语言&#xff09;自动注册 Cursor Ai &#xff0c;自动重置机器ID &#xff0c; 免费升级使用Pro 功能: Youve reached your trial request…...

AI 赋能下新型网络钓鱼攻击演进与多维度防御技术研究

摘要 生成式人工智能的普及使网络钓鱼攻击进入智能化、隐蔽化新阶段&#xff0c;攻击周期大幅缩短、伪装精度显著提升&#xff0c;传统基于规则与特征库的防御机制失效。本文结合 ESET 安全研究与企业实测数据&#xff0c;剖析 AI 驱动钓鱼攻击的技术机理、混淆手段与传播路径&…...

如何让AI读懂古文?GuwenBERT带来的古典汉语处理革命

如何让AI读懂古文&#xff1f;GuwenBERT带来的古典汉语处理革命 【免费下载链接】guwenbert GuwenBERT: 古文预训练语言模型&#xff08;古文BERT&#xff09; A Pre-trained Language Model for Classical Chinese (Literary Chinese) 项目地址: https://gitcode.com/gh_mir…...