Android Ble低功耗蓝牙开发
一、新建项目
在Android Studio中新建一个项目,如下图所示:
选择No Activity,然后点击Next
点击Finish,完成项目创建。
1、配置build.gradle
在android{}闭包中添加viewBinding,用于获取控件
buildFeatures {viewBinding true}
添加完成后,点击同步Sync
2、配置清单文件AndroidManifest.xml
在清单文件中,添加蓝牙相关权限如下:
<!--蓝牙连接权限--><uses-permission android:name="android.permission.BLUETOOTH" /><!--发现和配对蓝牙设备权限--><uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /><!--Android 6~11 定位权限--><uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /><uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /><!--以下三个是Android12中新增,作用与Android12及以上--><!--Android12 的蓝牙权限 如果您的应用与已配对的蓝牙设备通信或者获取当前手机蓝牙是否打开--><uses-permission android:name="android.permission.BLUETOOTH_CONNECT" /><!--Android12 的蓝牙权限 如果您的应用使当前设备可被其他蓝牙设备检测到--><uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" /><!--Android12 的蓝牙权限 如果您的应用查找蓝牙设备(如蓝牙低功耗 (BLE) 外围设备)Android12在不申请定位权限时,必须加上android:usesPermissionFlags="neverForLocation",否则搜不到设备--><uses-permissionandroid:name="android.permission.BLUETOOTH_SCAN"android:usesPermissionFlags="neverForLocation"tools:targetApi="s" />
二、搜索蓝牙设备
搜索蓝牙设备之前,需要检测手机蓝牙是否已经打开,如果未开启蓝牙,需要先开启蓝牙,才能搜索蓝牙设备。
1、创建MainActivity
在项目包名的位置,右键选择创建Empty Views Activity
勾选Launcher Activity,然后点击Finish
构建activity_main.xml布局:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:id="@+id/main"android:layout_width="match_parent"android:layout_height="match_parent"android:paddingHorizontal="15dp"android:paddingVertical="20dp"tools:context=".MainActivity"><Buttonandroid:id="@+id/btnStartScan"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="搜索蓝牙设备"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toTopOf="parent" /></androidx.constraintlayout.widget.ConstraintLayout>
MainActivity中通过ViewBinding绑定布局,代码如下:
public class MainActivity extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);ActivityMainBinding mBinding = ActivityMainBinding.inflate(getLayoutInflater());setContentView(mBinding.getRoot());}
}
2、开启蓝牙
MainActivity中添加点击事件:
public class MainActivity extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);ActivityMainBinding mBinding = ActivityMainBinding.inflate(getLayoutInflater());setContentView(mBinding.getRoot());mBinding.btnStartScan.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {}});}
}
新建个BleUtils工具类,并添加检测蓝牙是否开启、检测是否有权限的方法:
public class BleUtils {/*** 检测是否已经开启蓝牙*/public static boolean isOpenBle(Context context) {BluetoothManager manager = (BluetoothManager) context.getSystemService(Context.BLUETOOTH_SERVICE);if (manager != null) {BluetoothAdapter adapter = manager.getAdapter();if (adapter != null) {return adapter.isEnabled();}}return false;}/*** 检测是否有权限*/public static boolean hasPermission(Context context, String permission) {return context.checkSelfPermission(permission) == PackageManager.PERMISSION_GRANTED;}}
然后在搜索蓝牙设备的点击事件中检测手机是否已开启蓝牙,已开启蓝牙,可以搜索蓝牙设备,未开启蓝牙,需要先开启蓝牙
mBinding.btnStartScan.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {//判断是否开启蓝牙if (!BleUtils.isOpenBle(mContext)) {openBle();} else {//已开启蓝牙//搜索蓝牙设备searchBle();}}});
开启蓝牙需要打开蓝牙相关的权限
在build.gradle中引入三方库’com.blankj:utilcodex:1.31.1’,用于权限管理和获取各种工具类
//https://github.com/Blankj/AndroidUtilCode 工具类implementation 'com.blankj:utilcodex:1.31.1'
在MainActivity中添加开启蓝牙方法:
/*** 开启蓝牙*/@SuppressLint("MissingPermission")public void openBle() {if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {//Android12以上添加权限if (BleUtils.hasPermission(mContext, android.Manifest.permission.BLUETOOTH_CONNECT)) {//开启蓝牙Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);startActivity(intent);} else {PermissionUtils.permission(Manifest.permission.BLUETOOTH_CONNECT).callback(new PermissionUtils.SimpleCallback() {@Overridepublic void onGranted() {Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);mContext.startActivity(intent);}@Overridepublic void onDenied() {ToastUtils.showShort("Android12无此权限,无法打开蓝牙");}}).request();}} else {Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);mContext.startActivity(intent);}}
扫描蓝牙设备前,需要检测扫描蓝牙权限是否开启:
/*** 搜索蓝牙设备,检测搜索权限*/private void searchBle() {if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {//Android12以上,需要BLUETOOTH_SCAN权限if (!BleUtils.hasPermission(mContext, Manifest.permission.BLUETOOTH_CONNECT)) {PermissionUtils.permission(Manifest.permission.BLUETOOTH_CONNECT).callback(new PermissionUtils.SimpleCallback() {@Overridepublic void onGranted() {//开启蓝牙Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);startActivity(intent);}@Overridepublic void onDenied() {ToastUtils.showShort("Android12无此权限,无法打开蓝牙");}}).request();return;}if (BleUtils.hasPermission(mContext, Manifest.permission.BLUETOOTH_SCAN)) {ToastUtils.showShort("扫描蓝牙");startScan();} else {PermissionUtils.permission(Manifest.permission.BLUETOOTH_SCAN).callback(new PermissionUtils.SimpleCallback() {@Overridepublic void onGranted() {//扫描蓝牙ToastUtils.showShort("扫描蓝牙");}@Overridepublic void onDenied() {ToastUtils.showShort("Android12无此权限,无法扫描蓝牙");}}).request();}} else {if (BleUtils.hasPermission(mContext, Manifest.permission.ACCESS_FINE_LOCATION)) {ToastUtils.showShort("扫描蓝牙");startScan();} else {PermissionUtils.permission(Manifest.permission.ACCESS_FINE_LOCATION).callback(new PermissionUtils.SimpleCallback() {@Overridepublic void onGranted() {//扫描蓝牙ToastUtils.showShort("扫描蓝牙");}@Overridepublic void onDenied() {ToastUtils.showShort("Android12无此权限,无法扫描蓝牙");}}).request();}}}
3、扫描搜索蓝牙设备:
/*** 开始扫描蓝牙设备*/@SuppressLint("MissingPermission")private void startScan() {BluetoothManager manager = (BluetoothManager) mContext.getSystemService(BLUETOOTH_SERVICE);mBluetoothAdapter = manager.getAdapter();if (mBluetoothAdapter != null) {mScanner = mBluetoothAdapter.getBluetoothLeScanner();LogUtils.i("startScan");if (mScanner != null) {mHandler.removeCallbacks(scanRunnable);mScanner.startScan(scanCallback);mHandler.postDelayed(scanRunnable, 10 * 1000L);}}}/*** 扫描结果*/@SuppressLint("MissingPermission")private final ScanCallback scanCallback = new ScanCallback() {@Overridepublic void onScanResult(int callbackType, ScanResult result) {super.onScanResult(callbackType, result);if (result != null) {BluetoothDevice device = result.getDevice();int rssi = result.getRssi();String address = device.getAddress();ScanDeviceBean scanDeviceBean = new ScanDeviceBean();scanDeviceBean.setDeviceMac(device.getAddress());String deviceName = device.getName();scanDeviceBean.setDeviceName(!TextUtils.isEmpty(deviceName) ? deviceName : "Unknow");scanDeviceBean.setDeviceRssi(rssi);}}@Overridepublic void onScanFailed(int errorCode) {super.onScanFailed(errorCode);}};class StopScanRunnable implements Runnable {StopScanRunnable() {}@Overridepublic void run() {stopScan();}}/*** 停止扫描*/@SuppressLint("MissingPermission")public void stopScan() {LogUtils.i("stopScan");try {if (mScanner != null) {mHandler.removeCallbacks(scanRunnable);mScanner.stopScan(scanCallback);}} catch (Exception e) {throw new RuntimeException(e);}}
其中,ScanDeviceBean为扫描结果实体类:
public class ScanDeviceBean {private String deviceMac;private String deviceName;private int deviceRssi;public String getDeviceMac() {return deviceMac;}public void setDeviceMac(String deviceMac) {this.deviceMac = deviceMac;}public String getDeviceName() {return deviceName;}public void setDeviceName(String deviceName) {this.deviceName = deviceName;}public int getDeviceRssi() {return deviceRssi;}public void setDeviceRssi(int deviceRssi) {this.deviceRssi = deviceRssi;}
}
activity_main.xml中添加RecyclerView,用于展示搜素到的蓝牙设备:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:id="@+id/main"android:layout_width="match_parent"android:layout_height="match_parent"android:paddingHorizontal="15dp"android:paddingVertical="20dp"tools:context=".MainActivity"><Buttonandroid:id="@+id/btnStartScan"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="搜索蓝牙设备"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toTopOf="parent" /><androidx.recyclerview.widget.RecyclerViewandroid:id="@+id/recyclerView"android:layout_width="match_parent"android:layout_height="wrap_content"app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"app:layout_constraintTop_toBottomOf="@+id/btnStartScan" /></androidx.constraintlayout.widget.ConstraintLayout>
将搜索到的蓝牙设备显示到列表中:
//https://github.com/CymChad/BaseRecyclerViewAdapterHelperimplementation 'com.github.CymChad:BaseRecyclerViewAdapterHelper:3.0.4'
DeviceAdapter :
public class DeviceAdapter extends BaseQuickAdapter<ScanDeviceBean, BaseViewHolder> {public DeviceAdapter() {super(R.layout.item_device);}@Overrideprotected void convert(@NonNull BaseViewHolder baseViewHolder, ScanDeviceBean deviceEntity) {baseViewHolder.setText(R.id.tv_name, deviceEntity.getDeviceName());baseViewHolder.setText(R.id.tv_address, deviceEntity.getDeviceMac());baseViewHolder.setText(R.id.tv_rssi, deviceEntity.getDeviceRssi()+"dBm");}
}
item_device布局:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="wrap_content"android:paddingVertical="8dp"><ImageViewandroid:id="@+id/imageView"android:layout_width="50dp"android:layout_height="50dp"android:padding="8dp"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toTopOf="parent"app:srcCompat="@drawable/icon_bluetooth" /><TextViewandroid:id="@+id/tv_name"android:layout_width="0dp"android:layout_height="wrap_content"android:layout_marginStart="10dp"android:layout_marginTop="4dp"android:layout_marginEnd="10dp"android:textColor="@color/black"android:textSize="14sp"android:textStyle="bold"app:layout_constraintEnd_toStartOf="@+id/tv_rssi"app:layout_constraintStart_toEndOf="@+id/imageView"app:layout_constraintTop_toTopOf="@+id/imageView"tools:text="BLE Name" /><TextViewandroid:id="@+id/tv_address"android:layout_width="0dp"android:layout_height="wrap_content"android:layout_marginStart="10dp"android:layout_marginEnd="10dp"android:layout_marginBottom="4dp"android:textSize="12sp"app:layout_constraintBottom_toBottomOf="@+id/imageView"app:layout_constraintEnd_toStartOf="@+id/tv_rssi"app:layout_constraintStart_toEndOf="@+id/imageView"tools:text="32:65:CF:0A:DA:87" /><TextViewandroid:id="@+id/tv_rssi"android:layout_width="wrap_content"android:layout_height="wrap_content"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintTop_toTopOf="parent"tools:text="-62dBm" /></androidx.constraintlayout.widget.ConstraintLayout>
蓝牙图标icon_bluetooth.xml:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"android:width="48dp"android:height="48dp"android:autoMirrored="true"android:tint="#000000"android:viewportWidth="24.0"android:viewportHeight="24.0"><pathandroid:fillColor="@android:color/white"android:pathData="M14.24,12.01l2.32,2.32c0.28,-0.72 0.44,-1.51 0.44,-2.33 0,-0.82 -0.16,-1.59 -0.43,-2.31l-2.33,2.32zM19.53,6.71l-1.26,1.26c0.63,1.21 0.98,2.57 0.98,4.02s-0.36,2.82 -0.98,4.02l1.2,1.2c0.97,-1.54 1.54,-3.36 1.54,-5.31 -0.01,-1.89 -0.55,-3.67 -1.48,-5.19zM15.71,7.71L10,2L9,2v7.59L4.41,5 3,6.41 8.59,12 3,17.59 4.41,19 9,14.41L9,22h1l5.71,-5.71 -4.3,-4.29 4.3,-4.29zM11,5.83l1.88,1.88L11,9.59L11,5.83zM12.88,16.29L11,18.17v-3.76l1.88,1.88z" /></vector>
在MainActivity中展示蓝牙设备列表:
public class MainActivity extends AppCompatActivity {private Context mContext;private BluetoothAdapter mBluetoothAdapter;private BluetoothLeScanner mScanner;private static final Handler mHandler = new Handler();private final Runnable scanRunnable = new StopScanRunnable();private final ArrayList<ScanDeviceBean> mDeviceList = new ArrayList<>();private DeviceAdapter mDeviceAdapter;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);ActivityMainBinding mBinding = ActivityMainBinding.inflate(getLayoutInflater());setContentView(mBinding.getRoot());mContext = getApplicationContext();mDeviceAdapter = new DeviceAdapter();mBinding.recyclerView.setAdapter(mDeviceAdapter);mBinding.btnStartScan.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {//判断是否开启蓝牙if (!BleUtils.isOpenBle(mContext)) {openBle();} else {//已开启蓝牙//搜索蓝牙设备searchBle();}}});}/*** 开启蓝牙*/@SuppressLint("MissingPermission")public void openBle() {if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {//Android12以上添加权限if (BleUtils.hasPermission(mContext, android.Manifest.permission.BLUETOOTH_CONNECT)) {//开启蓝牙Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);startActivity(intent);} else {PermissionUtils.permission(Manifest.permission.BLUETOOTH_CONNECT).callback(new PermissionUtils.SimpleCallback() {@Overridepublic void onGranted() {Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);mContext.startActivity(intent);}@Overridepublic void onDenied() {ToastUtils.showShort("Android12无此权限,无法打开蓝牙");}}).request();}} else {Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);mContext.startActivity(intent);}}/*** 搜索蓝牙设备,检测搜索权限*/@SuppressLint("MissingPermission")private void searchBle() {if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {//Android12以上,需要BLUETOOTH_SCAN权限if (!BleUtils.hasPermission(mContext, Manifest.permission.BLUETOOTH_CONNECT)) {PermissionUtils.permission(Manifest.permission.BLUETOOTH_CONNECT).callback(new PermissionUtils.SimpleCallback() {@Overridepublic void onGranted() {//开启蓝牙Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);startActivity(intent);}@Overridepublic void onDenied() {ToastUtils.showShort("Android12无此权限,无法打开蓝牙");}}).request();return;}if (BleUtils.hasPermission(mContext, Manifest.permission.BLUETOOTH_SCAN)) {ToastUtils.showShort("扫描蓝牙");startScan();} else {PermissionUtils.permission(Manifest.permission.BLUETOOTH_SCAN).callback(new PermissionUtils.SimpleCallback() {@Overridepublic void onGranted() {//扫描蓝牙ToastUtils.showShort("扫描蓝牙");}@Overridepublic void onDenied() {ToastUtils.showShort("Android12无此权限,无法扫描蓝牙");}}).request();}} else {if (BleUtils.hasPermission(mContext, Manifest.permission.ACCESS_FINE_LOCATION)) {ToastUtils.showShort("扫描蓝牙");startScan();} else {PermissionUtils.permission(Manifest.permission.ACCESS_FINE_LOCATION).callback(new PermissionUtils.SimpleCallback() {@Overridepublic void onGranted() {//扫描蓝牙ToastUtils.showShort("扫描蓝牙");}@Overridepublic void onDenied() {ToastUtils.showShort("Android12无此权限,无法扫描蓝牙");}}).request();}}}/*** 开始扫描蓝牙设备*/@SuppressLint("MissingPermission")private void startScan() {BluetoothManager manager = (BluetoothManager) mContext.getSystemService(BLUETOOTH_SERVICE);mBluetoothAdapter = manager.getAdapter();if (mBluetoothAdapter != null) {mScanner = mBluetoothAdapter.getBluetoothLeScanner();LogUtils.i("startScan");if (mScanner != null) {mHandler.removeCallbacks(scanRunnable);mScanner.startScan(scanCallback);mHandler.postDelayed(scanRunnable, 10 * 1000L);}}}/*** 扫描结果*/@SuppressLint("MissingPermission")private final ScanCallback scanCallback = new ScanCallback() {@Overridepublic void onScanResult(int callbackType, ScanResult result) {super.onScanResult(callbackType, result);if (result != null) {BluetoothDevice device = result.getDevice();int rssi = result.getRssi();String address = device.getAddress();ScanDeviceBean scanDeviceBean = new ScanDeviceBean();scanDeviceBean.setDeviceMac(device.getAddress());String deviceName = device.getName();scanDeviceBean.setDeviceName(!TextUtils.isEmpty(deviceName) ? deviceName : "Unknow");scanDeviceBean.setDeviceRssi(rssi);boolean isContain = false;for (ScanDeviceBean bean : mDeviceList) {if (bean.getDeviceMac().equals(scanDeviceBean.getDeviceMac())) {isContain = true;break;}}if (!isContain) {mDeviceList.add(scanDeviceBean);mDeviceAdapter.setList(mDeviceList);}}}@Overridepublic void onScanFailed(int errorCode) {super.onScanFailed(errorCode);}};class StopScanRunnable implements Runnable {StopScanRunnable() {}@Overridepublic void run() {stopScan();}}/*** 停止扫描*/@SuppressLint("MissingPermission")public void stopScan() {LogUtils.i("stopScan");try {if (mScanner != null) {mHandler.removeCallbacks(scanRunnable);mScanner.stopScan(scanCallback);}} catch (Exception e) {throw new RuntimeException(e);}}}
搜索到的蓝牙设备列表如下:
三、连接蓝牙设备
蓝牙设备列表添加点击事件,点击连接蓝牙设备:
mDeviceAdapter.setOnItemClickListener(new OnItemClickListener() {@Overridepublic void onItemClick(@NonNull BaseQuickAdapter<?, ?> adapter, @NonNull View view, int position) {String deviceMac = mDeviceList.get(position).getDeviceMac();connectBle(deviceMac);}});
/*** 连接蓝牙设备** @param macAddress 蓝牙设备地址*/@SuppressLint("MissingPermission")public void connectBle(String macAddress) {if (mBluetoothAdapter != null && !TextUtils.isEmpty(macAddress)) {BluetoothDevice remoteDevice = mBluetoothAdapter.getRemoteDevice(macAddress);if (remoteDevice != null) {if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {//BluetoothDevice.TRANSPORT_AUTO:对于GATT连接到远程双模设备无物理传输优先。//BluetoothDevice.TRANSPORT_BREDR:GATT连接到远程双模设备优先BR/EDR。//BluetoothDevice.TRANSPORT_LE:GATT连接到远程双模设备优先BLE。mBluetoothGatt = remoteDevice.connectGatt(mContext, false, mBleGattCallBack, BluetoothDevice.TRANSPORT_LE);} else {mBluetoothGatt = remoteDevice.connectGatt(mContext, false, mBleGattCallBack);}}}}
连接状态回调:
/*** 连接状态回调*/@SuppressLint("MissingPermission")class BleGattCallBack extends BluetoothGattCallback {// @Override
// public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) {
// super.onMtuChanged(gatt, mtu, status);
// if (status == BluetoothGatt.GATT_SUCCESS) {
// LogUtils.e( "request mtu success.约定后的MTU值为:" + mtu);
// } else {
// LogUtils.e( "request mtu failed.");
// }
//
// if (mBluetoothGatt != null) {
// mBluetoothGatt.discoverServices();
// }
// }@Overridepublic void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {super.onConnectionStateChange(gatt, status, newState);LogUtils.i("onConnectionStateChange status " + status + " newState " + newState);if (newState == BluetoothProfile.STATE_CONNECTED) {LogUtils.i("蓝牙连接成功,开始发现服务");// boolean mut = gatt.requestMtu(500);//如有需要可以申请扩容
// LogUtils.i("申请MTU扩容" + mut);if (mBluetoothGatt != null) {mBluetoothGatt.discoverServices();}} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {LogUtils.e("蓝牙连接已断开");}}@Overridepublic void onServicesDiscovered(BluetoothGatt gatt, int status) {super.onServicesDiscovered(gatt, status);LogUtils.i("onServicesDiscovered status " + status);if (status == BluetoothGatt.GATT_SUCCESS) {LogUtils.i("成功获取服务,开始获取服务里的特性");if (mBluetoothGatt != null) {List<BluetoothGattService> services = mBluetoothGatt.getServices();for (BluetoothGattService gattService : services) {LogUtils.d("服务uuid " + gattService.getUuid());List<BluetoothGattCharacteristic> characteristics = gattService.getCharacteristics();for (BluetoothGattCharacteristic gattCharacteristic : characteristics) {int charaProp = gattCharacteristic.getProperties();StringBuilder stringBuilder = new StringBuilder();if ((charaProp & BluetoothGattCharacteristic.PROPERTY_READ) > 0) {stringBuilder.append(" 可读 ");}if ((charaProp & BluetoothGattCharacteristic.PROPERTY_WRITE) > 0) {stringBuilder.append(" 可写 ");}if ((charaProp & BluetoothGattCharacteristic.PROPERTY_NOTIFY) < 0) {stringBuilder.append(" 通知 ");}LogUtils.d("特性uuid " + gattCharacteristic.getUuid() + stringBuilder);}}}} else {LogUtils.e("服务获取失败");}}@Overridepublic void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {super.onCharacteristicRead(gatt, characteristic, status);//Android12及以下,适用此方法if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {//适用于Android12及以下LogUtils.i("onCharacteristicRead,(Android12及以下)读取特性:" + characteristic.getUuid() + ",status:" + status);}}@Overridepublic void onCharacteristicRead(@NonNull BluetoothGatt gatt, @NonNull BluetoothGattCharacteristic characteristic, @NonNull byte[] value, int status) {super.onCharacteristicRead(gatt, characteristic, value, status);//Android13及以上,适用此方法LogUtils.i("onCharacteristicRead,(Android13及以上)读取特性:" + characteristic.getUuid() + ",status:" + status);}@Overridepublic void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {super.onCharacteristicWrite(gatt, characteristic, status);LogUtils.i("onCharacteristicWrite,写入特性:" + characteristic.getUuid() + ",status:" + status);if (status == BluetoothGatt.GATT_SUCCESS) {//Android 13及以上版本,characteristic.getValue()方法已过时,可能存在获取不到value的可能,如需要可以通过gatt.readCharacteristic(characteristic)(前提是这个特性可读)String value = Arrays.toString(characteristic.getValue());LogUtils.i("onCharacteristicWrite,写入特性:" + characteristic.getUuid() + ",值:" + value + ",十六进制值:" + BleUtils.bytesToHex(characteristic.getValue()));} else {}}//Descriptor(描述符)中定义的属性用于描述一个characteristic值@Overridepublic void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {super.onDescriptorWrite(gatt, descriptor, status);if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {LogUtils.i("onDescriptorWrite,(Android13及以上)写描述符:" + descriptor.getUuid().toString() + " status:" + status);} else {LogUtils.i("onDescriptorWrite,(Android12及以下)写描述符:" + descriptor.getUuid().toString() + " value:" + Arrays.equals(descriptor.getValue(), BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE) + " status:" + status);}//Android 13及以上版本,descriptor.getValue()方法已过时,获取的value为null,需要可以通过gatt.readDescriptor(descriptor)获取value(前提是这个特性可读)if (status == BluetoothGatt.GATT_SUCCESS) {} else {}}@Overridepublic void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {super.onDescriptorRead(gatt, descriptor, status);//Android12及以下,适用此方法if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {//适用于Android12及以下LogUtils.i("onDescriptorRead,(Android12及以下)读描述符:" + descriptor.getUuid().toString());if (status == BluetoothGatt.GATT_SUCCESS) {} else {}}}@Overridepublic void onDescriptorRead(@NonNull BluetoothGatt gatt, @NonNull BluetoothGattDescriptor descriptor, int status, @NonNull byte[] value) {super.onDescriptorRead(gatt, descriptor, status, value);//Android13及以上,适用此方法LogUtils.i("onDescriptorRead,(Android13及以上)读描述符:" + descriptor.getUuid().toString());if (status == BluetoothGatt.GATT_SUCCESS) {} else {}}@Overridepublic void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {//通知特性发生改变super.onCharacteristicChanged(gatt, characteristic);if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {//适用于Android12及Android12以下final byte[] value = characteristic.getValue();if (value != null && value.length > 0) {String hexValue = BleUtils.bytesToHex(characteristic.getValue());LogUtils.i("onCharacteristicChanged,(Android12及以下)特性发生改变:" + characteristic.getUuid() + ",值:" + hexValue);}}}@Overridepublic void onCharacteristicChanged(@NonNull BluetoothGatt gatt, @NonNull BluetoothGattCharacteristic characteristic, @NonNull byte[] value) {super.onCharacteristicChanged(gatt, characteristic, value);//Android13及以上,适用此方法if (value.length > 0) {String hexValue = BleUtils.bytesToHex(value);LogUtils.i("onCharacteristicChanged,(Android13及以上)特性发生改变:" + characteristic.getUuid() + ",值:" + hexValue);}}}
四、读、写、通知
根据uuid,获取相应的特性,就可以进行读、写、通知操作:
/*** 读取特性值** @param gattCharacteristic 要读取的特性*/private boolean readCharacteristic(BluetoothGattCharacteristic gattCharacteristic) {if (mBluetoothGatt != null) {boolean readState = mBluetoothGatt.readCharacteristic(gattCharacteristic);LogUtils.i("readCharacteristic " + readState);return readState;}return false;}/*** 写入特性值** @param gattCharacteristic 要写入的特性*/private boolean writeCharacteristic(BluetoothGattCharacteristic gattCharacteristic, byte[] data) {if (mBluetoothGatt != null) {boolean writeState;if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {//Android13及以上,适用此方法writeState = mBluetoothGatt.writeCharacteristic(gattCharacteristic,data, BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT)== BluetoothStatusCodes.SUCCESS;} else {//Android12及以下,适用此方法writeState = mBluetoothGatt.writeCharacteristic(gattCharacteristic);}LogUtils.i("writeCharacteristic " + writeState);return writeState;}return false;}/*** 设置特性通知** @param gattCharacteristic 需要获取通知的特性* @param descriptorUuid 描述UUID* @param enable true 获取特性变化通知 false 关闭通知*/private boolean setCharacteristicNotification(BluetoothGattCharacteristic gattCharacteristic, String descriptorUuid, boolean enable) {if (mBluetoothGatt != null) {boolean notifyCharacter = mBluetoothGatt.setCharacteristicNotification(gattCharacteristic, enable);if (notifyCharacter) {BluetoothGattDescriptor descriptor = gattCharacteristic.getDescriptor(UUID.fromString(descriptorUuid));if (descriptor != null) {boolean notifyState;if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {//Android13及以上,适用此方法notifyState = mBluetoothGatt.writeDescriptor(descriptor,enable ? BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE : BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE)== BluetoothStatusCodes.SUCCESS;} else {//Android12及以下,适用此方法if (enable) {descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);} else {descriptor.setValue(BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE);}notifyState = mBluetoothGatt.writeDescriptor(descriptor);}LogUtils.i("setCharacteristicNotification notifyState " + notifyState);return notifyState;}}}return false;}
五、判断手机已连接的蓝牙设备
判断是否有已经连接的蓝牙设备,通过此方法可以看到手机连接了几个蓝牙设备,获取已连接蓝牙设备的信息:
/*** 判断是否有已经连接的蓝牙设备*/public void isConnectedBleDevice() {BluetoothManager manager = (BluetoothManager) mContext.getSystemService(BLUETOOTH_SERVICE);if (manager != null) {List<BluetoothDevice> connectedDevices = manager.getConnectedDevices(BluetoothProfile.GATT);LogUtils.d("connectedDevices.size() " + connectedDevices.size());for (BluetoothDevice bluetoothDevice : connectedDevices) {LogUtils.d(bluetoothDevice.getAddress() + " " + bluetoothDevice.getName());}}}
六、刷新缓存
蓝牙断开连接后,通过反射的方式刷新缓存:
/*** 刷新缓存*/public void refreshDeviceCache() {if (mBluetoothGatt != null) {try {Method localMethod = mBluetoothGatt.getClass().getMethod("refresh");Boolean success = (Boolean) localMethod.invoke(mBluetoothGatt);LogUtils.i("refreshDeviceCache, is success: " + success);} catch (Exception e) {LogUtils.e("exception occur while refreshing device: " + e.getMessage());e.printStackTrace();}}}
相关文章:
Android Ble低功耗蓝牙开发
一、新建项目 在Android Studio中新建一个项目,如下图所示: 选择No Activity,然后点击Next 点击Finish,完成项目创建。 1、配置build.gradle 在android{}闭包中添加viewBinding,用于获取控件 buildFeatures {viewB…...
Visual Studio的快捷按键
Visual Studio的快捷按键对于提高编程效率至关重要。以下是一些常用的Visual Studio快捷按键,并按照功能进行分类和归纳: 1. 文件操作 Ctrl O:打开文件Ctrl S:保存文件Ctrl Shift S:全部保存Ctrl N:…...
【WEB系列】过滤器Filter
Filter,过滤器,属于Servlet规范,并不是Spring独有的。其作用从命名上也可以看出一二,拦截一个请求,做一些业务逻辑操作,然后可以决定请求是否可以继续往下分发,落到其他的Filter或者对应的Servl…...
[书生·浦语大模型实战营]——LMDeploy 量化部署 LLM 实践
1.基础作业 1.1配置 LMDeploy 运行环境 创建开发机 创建新的开发机,选择镜像Cuda12.2-conda;选择10% A100*1GPU;点击“立即创建”。注意请不要选择Cuda11.7-conda的镜像,新版本的lmdeploy会出现兼容性问题。其他和之前一样&…...
TiDB-从0到1-配置篇
TiDB从0到1系列 TiDB-从0到1-体系结构TiDB-从0到1-分布式存储TiDB-从0到1-分布式事务TiDB-从0到1-MVCCTiDB-从0到1-部署篇TiDB-从0到1-配置篇 一、系统配置 TiDB的配置分为系统配置和集群配置两种。 其中系统配置对应TiDB Server(不包含TiKV和PD的参数࿰…...
微信小程序按钮设计与交互:打造极致用户体验
微信小程序作为一种流行的应用形式,其界面设计和交互体验对于用户吸引力和留存率至关重要。其中,按钮作为用户与小程序进行交互的主要方式之一,其设计和实现直接影响到用户体验的质量。在本文中,我们将探讨微信小程序按钮的设计与…...
ES6中如何使用class和extends关键字实现继承?
在ES6中,可以使用class关键字来定义类,使用extends关键字来实现继承。下面是一个示例: // 父类 class Parent {constructor(name) {this.name name;}sayHello() {console.log(Hello, my name is ${this.name});} }// 子类 class Child ex…...
Linux:基本指令
文章目录 ls指令pwd指令cd指令touch指令mkdir指令rmdir指令 && rm指令cp指令man指令echo指令输出重定向追加重定向 cat指令输入重定向 mv指令which指令alias指令more && less指令head && tail指令事件相关的指令date显示时间戳 cal指令find指令grep指令…...
商业C++静态代码检测工具PC-lint Plus 、 polysace和sonarqube对比
商业C静态代码检测工具PC-lint Plus 、 polysace和sonarqube对比 特性/工具PC-lint PlusPolyspaceSonarQube主要功能高精度静态代码分析、编码标准检查高级静态分析和形式验证、优化嵌入式系统综合性代码质量管理、静态分析、技术债务管理集成方式可集成到IDE和构建系统与开发…...
邬家桥公园
文|随意的风 原文地址 我游览过现存规模最大、保存最完整的皇家园林颐和园,瞻仰过拥有世界上最大祭天建筑群的天坛公园,那都是多年前的事情了。 邬家桥公园相比颐和园、天坛公园,气势雄伟倒谈不上。它没有西湖的水平如镜ÿ…...
Flutter 中的 RenderObjectToWidgetAdapter 小部件:全面指南
Flutter 中的 RenderObjectToWidgetAdapter 小部件:全面指南 Flutter 是一个功能强大的 UI 框架,由 Google 开发,允许开发者使用 Dart 语言构建跨平台的移动、Web 和桌面应用。在 Flutter 的渲染体系中,RenderObjectToWidgetAdap…...
SNAT与DNAT
一、SNAT策略概述 1、SNAT 策略的典型应用环境 局域网主机共享单个公网IP地址接入Internet(私有IP不能在Internet中正常路由) 局域共享上网 2、 SNAT 策略的原理 修改数据包的源地址 把从内网 --> 外网的数据的源内网地址转换成公网源地址 3、SN…...
MySql八股文知识点总结,一篇文章让mysql成为面试加分项
MySql八股文知识点总结(自检) 1.前言 参与了几次中大厂的面试,你会发现一面时对于八股文的考察也具有侧重点(MySQLRedis > 网络 > 系统 >设计模式 > java集合 >spring) 本文的目标就是通过这一篇文章让你能在面…...
Python 很好用的爬虫框架:Scrapy:
了解Scrapy 爬虫框架的工作流程: 在scrapy中, 具体工作流程是这样的: 首先第一步 当爬虫引擎<engine>启动后, 引擎会到 spider 中获取 start_url<起始url> 然后将其封装为一个request对象, 交给调度器<…...
C/C++|关于 namespace 在C++中的代码组织
命名空间(namespace)在C中用于组织代码,避免命名冲突,并提供更好的代码结构和可读性。下面详细解释命名空间在C多文件编写中的各种作用和表达。 基本概念 命名空间是一个声明区域,用于组织代码,防止不同部…...
selenium自动化测试入门 —— 上传文件
selenium无法识别非web的控件,上传文件窗口为系统自带,无法识别窗口元素。 上传文件有两种场景:input控制上传和非input控件上传。 大多数情况都是input控件上传文件,只有非常少数的使用自定义的非input上传文件。 一、input控…...
C# Math.Round() 四舍六入五取偶
文章目录 1.重载列表2. 示例 Math.Round() 为四舍六入五取偶 1.重载列表 API说明Round(Double)将小数值舍入到最近的整数值Round(Double, Int32)将小数值按指定的小数位数舍入Round(Double, Int32, MidpointRounding)将小数值按指定的小数位数舍入,MidpointRoundin…...
springboot手动触发参数校验,service层调用参数校验
背景 入参校验一般是在控制层通过javax.validation.constraints包下的规则注解如NotNull结合Valid与Validated实现,但是有时候我们的方法不提供给controller调用,这时候就无法触发自动参数校验,为此我们可以在不更改校验代码的前提下手动触发…...
动手学深度学习4.10 实战Kaggle比赛:预测房价-笔记练习(PyTorch)
以下内容为结合李沐老师的课程和教材补充的学习笔记,以及对课后练习的一些思考,自留回顾,也供同学之人交流参考。 本节课程地址:实战 Kaggle 比赛:预测房价_哔哩哔哩_bilibili 本节教材地址:4.10. 实战Ka…...
1035 插入与归并(测试点6)
solution 类型判断:插入排序中已排序的部分有序,未排序的和原数组元素相同;否则为归并排序测试点6:对于归并排序的子序列长度,不能简单视为前k个有序则子序列长度就是k 例如该测试用例的归并排序的子序列长度应该为2&…...
什么情况下需要使用分布式事务,有哪些方案?
引言:在当今的分布式系统中,数据的一致性和事务的处理成为了关键问题。随着应用程序的规模不断扩大和复杂性的增加,单一数据库事务的能力已经无法满足需求。因此,引入了分布式事务的概念,以确保跨多个节点的操作能够保…...
Java加密体系结构参考指南-Java Cryptography Architecture
本文是从英文的官网摘了翻译的,用作自己的整理和记录。水平有限,欢迎指正。版本是:22 原文地址:https://docs.oracle.com/en/java/javase/22/security/java-cryptography-architecture-jca-reference-guide.html#GUID-815542FE-CF…...
C++中避免内存泄漏的方法
在C++中,内存泄漏是一个常见的问题,它发生在程序申请了一块内存后,没有正确地释放它。这会导致程序运行时间越长,内存占用越大,最终可能导致系统崩溃。为了避免内存泄漏,你可以遵循以下一些策略: 正确使用new和delete:当你使用new操作符动态分配内存时,确保在不再需要…...
5.1 实体完整性
一个表只能有一个主键约束,且主键约束不能取空值。 通过unique约束定义唯一性,为了保证一个表非主键列不输入重复值,可在该列定义unique约束。 primary key约束与unique约束主要区别如下。 (1)一个表只能创建一个primary key约束࿰…...
(学习笔记)数据基建-数据质量
数据基建-数据质量 数据质量数据质量保障措施如何推动上下游开展数据质量活动数据质量保障如何量化产出数据质量思考全链路数据质量保障项目 数据质量 概念:数据质量,意如其名,就是数据的准确性,他是数据仓库的基石,控…...
WINUI——Behavior(行为)小结
前言 在使用MVVM进行WINUI或WPF开发时,Command在某些时候并不能满足逻辑与UI分离的要求。这时肯定就需要其它技术的支持,Behavior就是一种。在WPF中是有Behavior直接支持的,转到WINUI后,相对有一些麻烦,于是在此记录之…...
Ruoyi5.x RuoYi-Vue-Plus新建Translation翻译类
若依框架(RuoYi)中的Translation翻译类主要作用在于实现字段值的转换或翻译功能,以提高数据展示的准确性和友好性。以下是其具体作用的一些关键点: 字段值转换:若依框架在处理数据时,有时需要将某些字段的…...
类加载的奥秘
一、类的加载过程将类的字节码文件加载到Java虚拟机中进行执行。 1.通过一个类的全限定名来获取定义此类的二进制流字节码文件(如zip 包、网络、运算生成、JSP 生成、数据库读取等)。 2.将这个字节流所代表的静态存储结构(如常量池、字段、方法等)转化为…...
Spring知识点总结
1. 简介一下Spring框架。 答:Spring框架是一个开源的容器性质的轻量级框架。主要有三大特点:容器、IOC(控制反转)、AOP(面向切面编程)。 2. Spring框架有哪些优点?谈谈你的看法。 答ÿ…...
STM32Cube系列教程11:STM32 AES加解密模块性能测试
文章目录 本次测试环境本次测试所使用的系统时钟为48MHz本次测试主要测试对大量数据进行加解密所需的时间,本次为不严谨测试,忽略了程序调用耗时,结果仅供参考。 AES算法与数据加解密加密与解密对称加解密AES算法AES-ECBAES-CBC 填充算法PKCS…...
如何做婚庆公司的网站/免费推广途径与原因
华为OD机试题 华为OD机试300题大纲任务混部题目输入输出示例一输入输出说明示例二输入输出说明备注Code代码编写思路华为OD机试300题大纲 参加华为od机试,一定要注意不要完全背诵代码,需要理解之后模仿写出,通过率才会高。 华为 OD 清单查看地址:blog.csdn.net/hihell/ca…...
python网站开发的优势/seo网站推广什么意思
2013年Python社区诞生了很多实用的开发工具,这些工具在一定程度上可以帮助你节省更多的时间。本文为你汇总了这些工具,它们大部分都是开源的,你还可以通过源码来学习更多的Python开发知识。Radon是一个用于从源代码中计算出各种指标的Python工…...
wordpress集成后台无法登录/乐陵市seo关键词优化
今天在网上看到两篇关于自定义Flowlayout的实现方式,自己按着两篇文章的思路也尝试写个demo学习一下,最后实现了效果。这个功能实现起来不算太复杂,主要是父容器的高度和换行的计算。下面是我最终实现的效果图以及源码. 项目源码 package com…...
长治网站制作/网站推广的内容
RSA 实现 C# 加密首先我们来了解下什么是 RSA ,它属于不对称加密,其原理就是使用一个公钥一个私钥,公钥可以公开用以加密,私钥严格保密用于解密,那么这样大家知道了 RSA 适合于数据量不大的加密,比如加密…...
国外设计类网站/百度竞价排名怎么收费
1、连接到本机上的服务,直接打开命令mysql的命令行,输入名密码,直可登录2、连接到其他主机上的mysql 服务:假设我的MySql 服务安装在ip地址为192.168.60.129上,这此时使用以下的命令进行远程登录mysql -h 192.168.60.1…...
wordpress自定义页面创建专辑/seo是网络优化吗
一种商品需要用多个产品组成就需要运用抽象工厂模式。 概念: 抽象工厂:声明一个用于完成抽象商品对象创建操作的接口 具体工厂:实现创建具体产品对象的操作 抽象产品:声明一个用于一类产品对象的接口 具体产品:定义有相…...