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();}}}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/24766.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Flutter基础 -- Flutter容器布局

目录 1. MaterialApp 1.1 组件定义 1.2 主要功能和属性 1.3 示例 2. 脚手架 Scaffold 2.1 定义 2.2 Scaffold 的属性 2.3 示例 PS: 对于 Scaffold 和 MaterialApp 3. 线性布局 Column Row 3.1 Row 3.2 Column 4. 盒模型 Box Model 4.1 定义 4.2 示例 5. 容器 C…

数据结构及研究

**数据结构是计算机存储、组织数据的方式&#xff0c;它是相互之间存在一种或多种特定关系的数据元素的集合**Θic-1ΘΘic-2ΘΘic-3ΘΘic-4ΘΘic-5Θ。 数据结构这一概念在计算机科学领域扮演着至关重要的角色&#xff0c;它不仅决定了数据在计算机内部的存储方式&#xf…

Block Transformer:通过全局到局部的语言建模加速LLM推理

在基于transformer的自回归语言模型&#xff08;LMs&#xff09;中&#xff0c;生成令牌的成本很高&#xff0c;这是因为自注意力机制需要关注所有之前的令牌&#xff0c;通常通过在自回归解码过程中缓存所有令牌的键值&#xff08;KV&#xff09;状态来解决这个问题。但是&…

计算机组成结构—IO方式

目录 一、程序查询方式 1. 程序查询基本流程 2. 接口电路 3. 接口工作过程 二、程序中断方式 1. 程序中断基本流程 2. 接口电路 3. I/O 中断处理过程 三、DMA 方式 1. DMA 的概念和特点 2. DMA 与 CPU 的访存冲突 3. DMA 接口的功能 4. DMA 接口的组成 5. DMA 的…

Elasticsearch 认证模拟题 - 15

一、题目 原索引 task1 的字段 title 字段包含单词 The&#xff0c;查询 the 可以查出 1200 篇文档。重建 task1 索引为 task1_new&#xff0c;重建后的索引&#xff0c; title 字段查询 the 单词&#xff0c;不能匹配到任何文档。 PUT task1 {"mappings": {"…

机器学习----奥卡姆剃刀定律

奥卡姆剃刀定律&#xff08;Occam’s Razor&#xff09;是一条哲学原则&#xff0c;通常表述为“如无必要&#xff0c;勿增实体”&#xff08;Entities should not be multiplied beyond necessity&#xff09;或“在其他条件相同的情况下&#xff0c;最简单的解释往往是最好的…

Qt基于SQLite数据库的增删查改demo

一、效果展示 在Qt创建如图UI界面&#xff0c;主要包括“查询”、“添加”、“删除”、“更新”&#xff0c;四个功能模块。 查询&#xff1a;从数据库中查找所有数据的所有内容&#xff0c;并显示在左边的QListWidget控件上。 添加&#xff1a;在右边的QLineEdit标签上输入需…

pc之间的相互通信详解

如图&#xff0c;实现两台pc之间的相互通信 1.pc1和pc2之间如何进行通讯。 2.pc有mac和ip&#xff0c;首先pc1需要向sw1发送广播&#xff0c;sw1查询mac地址表&#xff0c;向router发送广播&#xff0c;router不接受广播&#xff0c;router的每个接口都有ip和mac&#xff0c;…

使用 Scapy 库编写 TCP SYN 洪水攻击脚本

一、介绍 TCP SYN 洪水攻击是一种拒绝服务攻击&#xff08;Denial-of-Service, DoS&#xff09;类型&#xff0c;攻击者通过向目标服务器发送大量的伪造TCP连接请求&#xff08;SYN包&#xff09;&#xff0c;消耗目标服务器的资源&#xff0c;导致其无法处理合法用户的请求。…

13. ESP32-HTTPClient(Arduino)

使用ESP32 Arduino框架的HTTPClient库进行HTTP请求 在ESP32开发里&#xff0c;网络通信是挺重要的一部分&#xff0c;你可能需要从服务器拿数据啊&#xff0c;或者把传感器数据发到云端什么的。不过别担心&#xff0c;ESP32 Arduino框架给我们提供了HTTPClient库&#xff0c;让…

力扣 有效的括号 栈

Problem: 20. 有效的括号 文章目录 思路复杂度&#x1f49d; Code 思路 &#x1f468;‍&#x1f3eb; 参考地址 复杂度 时间复杂度: O ( n ) O(n) O(n) 空间复杂度: O ( n ) O(n) O(n) &#x1f49d; Code class Solution {static Map<Character, Character> m…

【启明智显分享】基于工业级芯片Model3A的7寸彩色触摸屏应用于智慧电子桌牌方案

一场大型会议的布置&#xff0c;往往少不了制作安放参会人物的桌牌。制作、打印、裁剪&#xff0c;若有临时参与人员变更&#xff0c;会务方免不了手忙脚乱更新桌牌。由此&#xff0c;智能电子桌牌应运而生&#xff0c;工作人员通过系统操作更新桌牌信息&#xff0c;解决了传统…

电脑提示msvcp140.dll丢失的解决方法(附带详细msvcp140.dll文件分析)

msvcp140.dll是一个动态链接库&#xff08;DLL&#xff09;文件&#xff0c;属于Microsoft Visual C 2015 Redistributable的一部分。它全称为 "Microsoft C Runtime Library" 或 "Microsoft C Runtime Library"&#xff0c;表明该文件是微软C运行时库的一…

uniapp录音播放功能

ui效果如上。 播放就开始倒计时&#xff0c;并且改变播放icon&#xff0c;另外录音则停止上一次录音。 播放按钮&#xff08;三角形&#xff09;是播放功能&#xff0c;两竖是暂停播放功能。 const innerAudioContext wx.createInnerAudioContext();export default{data(){ret…

【设计模式深度剖析】【2】【行为型】【命令模式】| 以打开文件按钮、宏命令、图形移动与撤销为例加深理解

&#x1f448;️上一篇:模板方法模式 | 下一篇:职责链模式&#x1f449;️ 设计模式-专栏&#x1f448;️ 文章目录 命令模式定义英文原话直译如何理解呢&#xff1f; 四个角色1. Command&#xff08;命令接口&#xff09;2. ConcreteCommand&#xff08;具体命令类&…

Spring Boot 3.x集成FastDFS记录

最近在做一个课程&#xff0c;需要用讲一下SpringBoot 使用文件上传的功能&#xff0c;选择了FastDFS作为文件存储OSS。Spring Boot是最新的3.3.0版本&#xff0c;JDK版本是17&#xff0c;中间有一些坑&#xff0c;下面记录一下。 <parent><groupId>org.springfram…

基于VS2022编译GDAL

下载GDAL源码&#xff1b;下载GDAL编译需要依赖的必须代码&#xff0c;proj&#xff0c;tiff&#xff0c;geotiff三个源码&#xff0c;proj需要依赖sqlite&#xff1b;使用cmake编译proj&#xff0c;tiff&#xff0c;geotiff&#xff1b;proj有版本号要求&#xff1b;使用cmake…

Llama模型家族之拒绝抽样(Rejection Sampling)(九) 强化学习之Rejection Sampling

LlaMA 3 系列博客 基于 LlaMA 3 LangGraph 在windows本地部署大模型 &#xff08;一&#xff09; 基于 LlaMA 3 LangGraph 在windows本地部署大模型 &#xff08;二&#xff09; 基于 LlaMA 3 LangGraph 在windows本地部署大模型 &#xff08;三&#xff09; 基于 LlaMA…

Python实现半双工的实时通信SSE(Server-Sent Events)

Python实现半双工的实时通信SSE&#xff08;Server-Sent Events&#xff09; 1 简介 实现实时通信一般有WebSocket、Socket.IO和SSE&#xff08;Server-Sent Events&#xff09;三种方法。WebSocket和Socket.IO是全双工的实时双向通信技术&#xff0c;适合用于聊天和会话等&a…

三端植物大战僵尸杂交版来了

Hi&#xff0c;好久不见&#xff0c;最近植物大战僵尸杂交版蛮火的 那今天苏音整理给大家三端的植物大战僵尸杂交版包括【苹果端、电脑端、安卓端】 想要下载的直接划到最下方即可下载。 植物大战僵尸&#xff0c;作为一款古老的单机游戏&#xff0c;近期随着B站一位UP主潜艇…