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-permission
        android: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">

    <Button
        android: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 {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ActivityMainBinding mBinding = ActivityMainBinding.inflate(getLayoutInflater());
        setContentView(mBinding.getRoot());
    }
}

2、开启蓝牙

MainActivity中添加点击事件:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ActivityMainBinding mBinding = ActivityMainBinding.inflate(getLayoutInflater());
        setContentView(mBinding.getRoot());

        mBinding.btnStartScan.setOnClickListener(new View.OnClickListener() {
            @Override
            public 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() {
            @Override
            public 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() {
                    @Override
                    public void onGranted() {
                        Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
                        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                        mContext.startActivity(intent);
                    }

                    @Override
                    public 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() {
                    @Override
                    public void onGranted() {
                        //开启蓝牙
                        Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
                        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                        startActivity(intent);
                    }

                    @Override
                    public 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() {
                    @Override
                    public void onGranted() {
                        //扫描蓝牙
                        ToastUtils.showShort("扫描蓝牙");
                    }

                    @Override
                    public 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() {
                    @Override
                    public void onGranted() {
                        //扫描蓝牙
                        ToastUtils.showShort("扫描蓝牙");
                    }

                    @Override
                    public 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() {
        @Override
        public 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);

            }
        }

        @Override
        public void onScanFailed(int errorCode) {
            super.onScanFailed(errorCode);
        }
    };

    class StopScanRunnable implements Runnable {
        StopScanRunnable() {
        }

        @Override
        public 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">

    <Button
        android: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.RecyclerView
        android: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/BaseRecyclerViewAdapterHelper
    implementation 'com.github.CymChad:BaseRecyclerViewAdapterHelper:3.0.4'

在这里插入图片描述
DeviceAdapter :

public class DeviceAdapter extends BaseQuickAdapter<ScanDeviceBean, BaseViewHolder> {

    public DeviceAdapter() {
        super(R.layout.item_device);
    }

    @Override
    protected 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">

    <ImageView
        android: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" />

    <TextView
        android: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" />

    <TextView
        android: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" />

    <TextView
        android: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">

    <path
        android: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;

    @Override
    protected 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() {
            @Override
            public 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() {
                    @Override
                    public void onGranted() {
                        Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
                        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                        mContext.startActivity(intent);
                    }

                    @Override
                    public 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() {

                    @Override
                    public void onGranted() {
                        //开启蓝牙
                        Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
                        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                        startActivity(intent);
                    }

                    @Override
                    public 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() {
                    @Override
                    public void onGranted() {
                        //扫描蓝牙
                        ToastUtils.showShort("扫描蓝牙");
                    }

                    @Override
                    public 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() {
                    @Override
                    public void onGranted() {
                        //扫描蓝牙
                        ToastUtils.showShort("扫描蓝牙");
                    }

                    @Override
                    public 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() {
        @Override
        public 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);
                }
            }
        }

        @Override
        public void onScanFailed(int errorCode) {
            super.onScanFailed(errorCode);
        }
    };

    class StopScanRunnable implements Runnable {
        StopScanRunnable() {
        }

        @Override
        public 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() {
            @Override
            public 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();
//            }
//        }

    
        @Override
        public 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("蓝牙连接已断开");
            }
        }

        @Override
        public 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("服务获取失败");
            }
        }

        @Override
        public 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);
            }
        }

        @Override
        public 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);
        }

        @Override
        public 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值
        @Override
        public 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 {
            }
        }

        @Override
        public 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 {
                }
            }
        }

        @Override
        public 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 {
            }
        }

        @Override
        public 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);
                }
            }
        }

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

配置二级域名,局域网可以访问

location / { if (!-e KaTeX parse error: Expected }, got EOF at end of input: … rewrite ^(.*) /index.php?s/$1 last; } }

老师必备!一文教你如何高效收集志愿填报信息

高考志愿填报季&#xff0c;对于每一位老师来说&#xff0c;无疑是一场信息收集与管理的硬仗。如何在众多的志愿信息中&#xff0c;高效、准确地掌握每位学生的志愿意向&#xff1f; 高考志愿填报的重要性。不仅是学生人生的一个重要转折点&#xff0c;也是老师教育生涯中的一次…

24年江苏省教资认定报名照片要求

24年江苏省教资认定报名照片要求&#xff0c;速速查收&#xff01;

群晖上Docker下载的几种方式

前言 去年大概也是这个时候&#xff0c;有不少人在问 docker 查询注册表失败的问题 文章传送门&#xff1a;开源URL短链接服务Shlink 这次似乎问题更严重一些&#xff0c;不仅仅是不能查询注册表&#xff0c;而是连 docker pull 都不行了 # 镜像拉取测试 docker pull alpine:…

图文详解Windows系统下搭建mysql开发环境——mysql Community 8 和 navicat Premium 17 的安装和使用

在正式开始学习使用MySQL之前&#xff0c;我们有必要先搭建一个良好的开发环境&#xff0c;让我们的学习和工作效率事半功倍。 本文涉及到的软件百度云盘&#xff1a;链接&#xff1a;https://pan.baidu.com/s/1jj_YajEv8adeEjMrXLhOTQ?pwd1023 提取码&#xff1a;1023 目录 …

力扣hot100:394. 字符串解码(递归)

LeetCode&#xff1a;394. 字符串解码 本题容易想到用递归处理&#xff0c;在写递归时主要是需要明确自己的递归函数的定义。 不过我们也可以利用括号匹配的方式使用栈进行处理。 1、递归 定义递归函数string GetString(string & s,int & i); 表示处理处理整个numbe…

【设计模式深度剖析】【5】【行为型】【迭代器模式】

&#x1f448;️上一篇:策略模式 设计模式-专栏&#x1f448;️ 文章目录 迭代器模式定义英文原话直译如何理解呢&#xff1f; 迭代器模式的角色1. Iterator&#xff08;迭代器&#xff09;2. ConcreteIterator&#xff08;具体迭代器&#xff09;3. Aggregate&#xff08;聚…

【Javascript系统学习】(二)

函数 定义函数 函数提升仅适用于函数声明&#xff0c;而不适用于函数表达式 函数声明 函数表达式 //例子1 const factorial function fac(n) {return n < 2 ? 1 : n * fac(n - 1); };console.log(factorial(3)); // 6 //factorial(n)、fac(n)、arguments.callee() ----…

SpringBoot发邮件服务如何配置?怎么使用?

SpringBoot发邮件需要的参数&#xff1f;邮件发送性能如何优化&#xff1f; 在SpringBoot项目中配置发邮件服务是一个常见的需求&#xff0c;它允许我们通过应用程序发送通知、验证邮件或其他类型的邮件。AokSend将详细介绍如何在SpringBoot中配置发邮件服务。 SpringBoot发邮…

PDF分页处理:技术与实践

引言 在数字化办公和学习中&#xff0c;PDF文件因其便携性和格式稳定性而广受欢迎。然而&#xff0c;处理大型PDF文件时&#xff0c;我们经常需要将其拆分成单独的页面&#xff0c;以便于管理和分享。本文将探讨如何使用Python编程语言和一些流行的库来实现PDF文件的分页处理。…

[word] word文字间隙怎么调整? #媒体#职场发展

word文字间隙怎么调整&#xff1f; 在文档中的数据包含英文、数字、中文等&#xff0c;会有间隙&#xff0c;有时候误以为是空格&#xff0c;但是根本删除不了&#xff0c;其实这是默认的间隙&#xff0c;是可以调整的&#xff0c;下面教大家word文字间隙怎么调整的操作&#…

路由器作为网络扩展器——设置桥接、路由模式

下面提到的路由器都是家用路由器 一、有线桥接(交换模式) 1.连接示意图 (副路由器只看交换模式部分) 副路由器充当交换机的角色 二、无线桥接(与有线类似) &#xff08;副路由器的无线信号 连接 主路由器的无线信号&#xff09; 三、路由模式 1.连接示意图 (副路由器只看…

百华鞋业祝莘莘学子旗开得胜,一举夺魁

在知识的海洋中&#xff0c; 有一群人以笔为剑&#xff0c; 在漫长的岁月里不断磨砺&#xff0c; 只为迎接那场人生的重要战役——高考。 高考&#xff0c; 是学子们十几年寒窗苦读的见证&#xff0c; 是他们用奋斗书写青春考卷的舞台。 在这个舞台上&#xff0c; 他们将…

yarn保姆级安装和使用

目录 前言 一、yarn简介 主要特性 使用场景 二、yarn的安装 yarn的下载 配置环境变量 三、yarn的常用命令 四、yarn的常用配置项 五、npm与yarn的区别 前言 本文旨在介绍如何安装和使用Yarn&#xff0c;以及它的一些常见用法。我们将从Yarn的基本概念开始&#xff0c;…

以sqlilabs靶场为例,讲解SQL注入攻击原理【32-41关】

【Less-32】 尝试使用各种注入发现无论是单引号还是双引号都被\转义成了字符串&#xff0c;导致SQL无法注入。 解决方案&#xff1a;宽字节注入。原理&#xff1a;利用数据库和页面编码不同的问题&#xff0c;PHP发送请求到mysql时经过一次gbk编码&#xff0c;因为GBK是双字节…

易于上手的requests

Python中的requests库主要用于发送HTTP请求并获取响应结果。在现代网络编程中&#xff0c;HTTP请求是构建客户端与服务器之间通信的基础。Python作为一种高级编程语言&#xff0c;其丰富的库支持使得它在网络数据处理领域尤为突出。其中&#xff0c;requests库以其简洁、易用的…

【计算机视觉】数字图像处理基础:以像素为单位的图像基本运算(点运算、代数运算、逻辑运算、几何运算、插值)

0、前言 在上篇文章中&#xff0c;我们对什么是数字图像、以及数字图像的组成&#xff08;离散的像素点&#xff09;进行了讲解&#x1f517;【计算机视觉】数字图像处理基础知识&#xff1a;模拟和数字图像、采样量化、像素的基本关系、灰度直方图、图像的分类。 我们知道&a…

高考作文:时光之河,逐梦前行

时光之河&#xff0c;奔流不息&#xff0c;如同我们的人生旅途&#xff0c;充满了未知与挑战。站在2024年的高考门槛前&#xff0c;我们回望过去&#xff0c;展望未来&#xff0c;心中充满了期待与憧憬。 首先&#xff0c;让我们回顾一下这条时光之河中的点滴。过去的岁月里&am…

java 大型企业MES生产管理系统源码:MES系统与柔性化产线控制系统的关系、作用

MES定义为“位于上层的计划管理系统与底层的工业控制之间的面向车间层的管理信息系统”,它为操作人员/管理人员提供计划的执行、跟踪以及所有资源(人、设备、物料、客户需求等)的当前状态。 MES系统与柔性化产线控制系统的关系 MES&#xff08;制造执行系统&#xff09;是一种…

i.MX8MP平台开发分享(RDC软件配置篇)

Uboot中已经将RDC的配置写入到了OCRAM中&#xff0c;NXP在ATF中预设了SIP服务&#xff0c;SIP服务下有厂商自定义的smc命令ID。例如下面的DDR、GPC、SRC和HAB的smc回调函数。 在SRC中断处理函数中&#xff0c;对于SRC_M4_START指令&#xff0c;先读取OCRAM中的配置&#xff0c;…