Android蓝牙Ble基本操作-(连接2)

前言:
连接采用nordicsemi库,其中nRF Connect也是使用该库。

蓝牙连接库nordicsemi官网nRF Connect apk使用教程nRF Connect apk下载地址蓝牙UUID介绍

效果图:
在这里插入图片描述

首先app\build.gradle加入:

  //蓝牙库
  implementation 'no.nordicsemi.android:ble:2.2.4'

快捷找ID路径如上:
在这里插入图片描述

    id 'kotlin-android-extensions'

1、蓝牙管理类MyBleManager:

/**
 * 蓝牙UUID介绍:https://blog.csdn.net/jiangchao3392/article/details/90213465
 * 基本的UUID:0000xxxx-0000-1000-8000-00805F9B34FB【设备可自定义各种格式】
 * 类说明:蓝牙管理类
 */
class MyBleManager(context: Context) : BleManager(context) {

    private val TAG = "MyBleManager"

    //服务UUID
    private val SERVICE_UUID: UUID = UUID.fromString("0000001-0000-1000-8000-00805F9B34FB")

    //写入UUID
    private val WRITE_UUID = UUID.fromString("00000002-0000-1000-8000-00805F9B34FB")

    //特征码UUID【监听】
    private val NOTIFY_UUID = UUID.fromString("00000003-0000-1000-8000-00805F9B34FB")

    //蓝牙GATT特性
    private var writeChar: BluetoothGattCharacteristic? = null
    private var notifyChar: BluetoothGattCharacteristic? = null

    //设备是否需要Dfu升级【后续预留DFU设备升级】
    var isDeviceUpdater = false

    override fun getGattCallback(): BleManagerGattCallback {
        return MyBleManagerGattCallback()
    }

    /**
     * Gatt回调类,蓝牙连接过程中的操作一般都是通过此类来完成
     */
    private inner class MyBleManagerGattCallback : BleManagerGattCallback() {

        public override fun isRequiredServiceSupported(gatt: BluetoothGatt): Boolean {
            //校验设备是否拥有我们所需的服务与特征
            val service = gatt.getService(SERVICE_UUID)
            //是否需要更新
            if (isDeviceUpdater && null == service) {
                return true
            }
            if (service != null) {
                //读写特征
                writeChar = service.getCharacteristic(WRITE_UUID)
                //通知特征
                notifyChar = service.getCharacteristic(NOTIFY_UUID)
            }
            //校验读写特征是否拥有写入数据的权限
            var notify = false
            if (notifyChar != null) {
                val properties = notifyChar!!.properties
                notify = properties and BluetoothGattCharacteristic.PROPERTY_NOTIFY != 0
            }
            var writeRequest = false
            if (writeChar != null) {
                val properties = writeChar!!.properties
                writeRequest =
                    properties and BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE != 0
                writeChar!!.writeType = BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT
            }
            //如果找到了所有需要的服务,则返回true
            return writeChar != null && notifyChar != null && notify && writeRequest
        }

        //在这里初始化设备。通常需要启用通知和设置required
        override fun initialize() {
            //创建一个原子请求队列。来自队列的请求将按顺序执行。
            beginAtomicRequestQueue()
                //最大20字节,GATT需要额外3字节。这将允许数据包大小为244字节
                .add(requestMtu(23)
                    .with { device: BluetoothDevice?, mtu: Int ->
                        Log.e(TAG, "with: $mtu")
                    }
                    .fail { device: BluetoothDevice?, status: Int ->
                        Log.e(TAG, "fail: $status")
                    })
                .add(enableNotifications(notifyChar))
                .done { device: BluetoothDevice? ->
                    Log.e(TAG, "done: " + device!!.name)
                    Log.e(TAG, "done: " + device.address)
                }
                .enqueue()
            //通过notificationDataCallback进行通知监听
            setNotificationCallback(notifyChar)
                .with { device: BluetoothDevice?, data: Data? ->
                    listener.invoke(device, data!!.value)
                }
            //enableNotifications(notifyChar).enqueue();
        }

        /**
         * 断开连接
         */
        override fun onDeviceDisconnected() {
            //设备断开连接。置null
            writeChar = null
            notifyChar = null
        }
    }

    /**
     * 通知数据回调
     */
    private lateinit var listener: (BluetoothDevice?, ByteArray?) -> Unit

    fun setOnListener(deviceData: (device: BluetoothDevice?, byteArray: ByteArray?) -> Unit) {
        listener = deviceData
    }
}

2、蓝牙连接类ConnectUtils:

/**
 * 类说明:蓝牙连接类
 */
class ConnectUtils private constructor() : ConnectionObserver {

    private val TAG = "ConnectUtils"

    //设备管理类
    private var bleManager: MyBleManager? = null

    //dfu升级【预留】
    var isDfuUpdate = false

    /**
     * 单例
     * Lazy是接受一个 lambda 并返回一个 Lazy 实例的函数,返回的实例可以作为实现延迟属性的委托:
     * 第一次调用 get() 会执行已传递给 lazy() 的 lambda 表达式并记录结果, 后续调用 get() 只是返回记录的结果
     */
    companion object {
        val getInstance: ConnectUtils by lazy {
            ConnectUtils()
        }
    }

    /**
     * 开始连接设备
     */
    fun startConnect(context: Context, device: BluetoothDevice) {
        Log.e(TAG, "startConnect: 连接的设备名:" + device.name)
        bleManager = MyBleManager(context)
        //设置连接观察器
        bleManager!!.setConnectionObserver(this)
        //数据回调
        bleManager!!.setOnListener { device, byteArray ->
            //从结构上来看apply函数和run函数很像,唯一不同点就是它们各自返回的值不一样,
            //run函数是以闭包形式返回最后一行代码的值,而apply函数的返回的是传入对象的本身。
            byteArray.apply {
                //返回基础字节数组。
                Log.e(TAG, "响应的数据ByteArray: " + ByteUtils.bytesToString(byteArray!!))
            }
        }
        //开始连接
        bleManager!!.connect(device)
            //是否断连自动重连
            .useAutoConnect(true)
            //连接超时10秒
            .timeout(10000)
            //重连的次数,每次重连延时100
            .retry(3, 100)
            //设置完成回调
            .done {
                //连接完成,关闭加载框
                LoadingDialog.closeTimerDialog()
                Log.e(TAG, "startConnect: 初始化连接成功")
            }
            //将请求排队以进行异步执行。
            .enqueue()
    }

    /**
     * 断开连接
     */
    fun disconnect() {
        if (null != bleManager) {
            bleManager!!.disconnect()
            bleManager!!.close()
        }
    }

    override fun onDeviceDisconnecting(device: BluetoothDevice) {
        Log.e(TAG, "onDeviceDisconnecting: 设备断开连接中...")
        listener.invoke(device, "设备断开连接中")
    }

    override fun onDeviceDisconnected(device: BluetoothDevice, reason: Int) {
        Log.e(TAG, "onDeviceDisconnected: 设备断开连接" + device.name)
        Log.e(TAG, "onDeviceDisconnected: 设备断开连接" + device.address)
        listener.invoke(device, "设备断开连接")
    }

    override fun onDeviceReady(device: BluetoothDevice) {
        //方法在所有初始化请求都已完成时调用
        Log.e(TAG, "onDeviceReady: " + device.name)
        Log.e(TAG, "onDeviceReady: " + device.address)
    }

    override fun onDeviceConnected(device: BluetoothDevice) {
        Log.e(TAG, "onDeviceConnected: 设备已连接 ")
        listener.invoke(device, "设备已连接")
    }

    override fun onDeviceFailedToConnect(device: BluetoothDevice, reason: Int) {
        Log.e(TAG, "onDeviceFailedToConnect: 设备连接失败超时$reason")
        listener.invoke(device, "设备连接超时")
    }

    override fun onDeviceConnecting(device: BluetoothDevice) {
        Log.e(TAG, "onDeviceConnecting: 设备连接中...")
        listener.invoke(device, "设备连接中")
    }

    /**
     * 连接状态回调
     */
    private lateinit var listener: (BluetoothDevice, String) -> Unit

    fun setOnListener(deviceData: (device: BluetoothDevice, state: String) -> Unit) {
        listener = deviceData
    }
}

3、使用类MainActivity:

/**
 * 类说明:扫描+连接
 */
class MainActivity : AppCompatActivity() {

    private val TAG = "MainActivity"
    private var bluetoothDevice: BluetoothDevice? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        BleScanUtils.getInstance.startScanBle(this)
        BleScanUtils.getInstance.setOnListener {
            for (i in it) {
                if (i.name == "KFC 6666") {
                    bluetoothDevice = i
                    //扫描到关闭扫描
                    BleScanUtils.getInstance.stopScanBle()
                }
                Log.e(TAG, "onCreate: " + i.address)
                Log.e(TAG, "onCreate: " + i.name)
            }
        }
        startConnectBtn.setOnClickListener {
            if (null != bluetoothDevice) {
                if (startConnectBtn.text.toString() == "断开连接") {
                    //断开连接【手动断连不走回调】
                    ConnectUtils.getInstance.disconnect()
                    startConnectBtn.text = "开始连接"
                    connectStateTv.text = "设备已断连"
                } else {
                    //开始连接
                    LoadingDialog.showTimerDialog(this)
                    ConnectUtils.getInstance.startConnect(this@MainActivity, bluetoothDevice!!)
                }
            }
        }
        //连接状态
        ConnectUtils.getInstance.setOnListener { device, state ->
            connectStateTv.text = state
            startConnectBtn.text = "断开连接"
        }
    }

    /**
     * 关闭扫描
     */
    override fun onDestroy() {
        super.onDestroy()
        BleScanUtils.getInstance.stopScanBle()
    }
}

对应布局:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center_horizontal"
    android:orientation="vertical">

    <TextView
        android:id="@+id/connectStateTv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="50dp"
        android:text="设备未连接"
        android:textSize="18sp" />

    <Button
        android:id="@+id/startConnectBtn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="20dp"
        android:text="开始连接" />

</LinearLayout>

4、蓝牙工具类ByteUtils:

/**
 * 类说明:蓝牙工具类
 */
class ByteUtils {

    companion object {

        private val STRING_ARRAY = "0123456789ABCDEF".toCharArray()

        /**
         * 字节数组转16进制Hex字符串【大写】
         */
        fun bytesToString(bytes: ByteArray): String {
            val hexChars = CharArray(bytes.size * 2)
            for (j in bytes.indices) {
                val v: Int = bytes[j].toInt() and 0xFF
                hexChars[j * 2] = STRING_ARRAY[v ushr 4]
                hexChars[j * 2 + 1] = STRING_ARRAY[v and 0x0F]
            }
            return String(hexChars)
        }

        /**
         * 字节数组转16进制Hex字符串【小写】
         */
//        fun bytesToHex(bytes: ByteArray): String? {
//            val sb = StringBuffer()
//            for (i in bytes.indices) {
//                val hex = Integer.toHexString(bytes[i].toInt() and 0xFF)
//                if (hex.length < 2) {
//                    sb.append(0)
//                }
//                sb.append(hex)
//            }
//            return sb.toString()
//        }
    }
}

5、连接加载框:LoadingDialog

/**
 * 类说明:连接加载框
 */
class LoadingDialog {

    companion object {

        private lateinit var dialog: Dialog
        private lateinit var timer: CountDownTimer

        /**
         * 倒计时弹框
         */
        fun showTimerDialog(activity: AppCompatActivity) {
            showProgressDialog(activity)
            //计时10秒;每隔一秒执行
            timer = object : CountDownTimer(10000, 1000) {
                override fun onTick(millisUntilFinished: Long) {
                }

                override fun onFinish() {
                    Toast.makeText(activity, "蓝牙连接连接超时", Toast.LENGTH_SHORT).show()
                    closeTimerDialog()
                }
            }.start()
        }

        /**
         * 关闭计时器
         */
        fun closeTimerDialog() {
            closeDialog()
            timer.cancel()
        }

        /**
         * 显示弹框
         */
        private fun showProgressDialog(context: Context) {
            dialog = Dialog(context, R.style.dialog_style)
            dialog.setContentView(R.layout.loading_dialog)
            dialog.setCanceledOnTouchOutside(false)//点击外部不关闭
            dialog.setCancelable(false)//点击返回键不关闭
            dialog.show()//显示弹框
        }

        /**
         * 关闭弹框
         */
        private fun closeDialog() {
            if (null != dialog) {
                dialog.dismiss()
            }
        }
    }
}

对应布局loading_dialog.xml:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <LinearLayout
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:layout_centerInParent="true"
        android:background="@drawable/linear_shape"
        android:gravity="center"
        android:padding="20dp">

        <!--indeterminateDuration:转一圈的时间0.8-->
        <ProgressBar
            android:id="@+id/loadProgressBar"
            style="?android:attr/progressBarStyleLarge"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:indeterminateDrawable="@drawable/progress_shape"
            android:indeterminateDuration="800" />
    </LinearLayout>
</RelativeLayout>

Dialog样式:

 <style name="dialog_style" parent="@android:style/Theme.Dialog">
     <!-- 设置未浮动窗口 -->
     <item name="android:windowIsFloating">true</item>
     <!-- 设置无边框 -->
     <item name="android:windowFrame">@null</item>
     <!-- 设置无标题 -->
     <item name="android:windowNoTitle">true</item>
     <!-- 设置完全透明-->
     <item name="android:windowBackground">@android:color/transparent</item>
     <!-- 设置屏幕变暗 -->
     <item name="android:backgroundDimEnabled">true</item>
 </style>

资源文件:

linear_shape.xml

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">

    <corners android:radius="10dp" />

    <solid android:color="#ffffff" />
</shape>

progress_shape.xml

<?xml version="1.0" encoding="utf-8"?>
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
    android:fromDegrees="0"
    android:pivotX="50%"
    android:pivotY="50%"
    android:toDegrees="360">

    <shape
        android:innerRadiusRatio="3"
        android:shape="ring"
        android:thicknessRatio="10"
        android:useLevel="false">

        <gradient
            android:centerColor="#333333"
            android:centerY="0.5"
            android:endColor="#666666"
            android:startColor="#999999"
            android:type="sweep"
            android:useLevel="false" />
    </shape>
</rotate>

连接过程:
在这里插入图片描述
主动关机设备,在开机重连过程:
在这里插入图片描述
下一篇:数据传输的两种方式和设备Dfu升级。

  • 2
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 10
    评论
Android中,创建Bluetooth Low Energy(BLE)服务的蓝牙socket连接可以通过以下步骤完成: 1. 配置蓝牙适配器:首先,需要获取设备的蓝牙适配器,并确保其已启用。可以使用BluetoothAdapter类的getDefaultAdapter()方法获取默认的蓝牙适配器实例,并调用isEnabled()方法检查其是否已启用。 2. 扫描设备:使用BluetoothAdapter的startLeScan()方法扫描附近的BLE设备。在回调函数onLeScan()中,可以获取到扫描到的设备列表。 3. 连接设备:在扫描到目标设备后,调用BluetoothDevice的connectGatt()方法来创建GATT连接。其中,GATT(Generic Attribute Profile)是BLE连接的框架。该方法返回BluetoothGatt对象,用于管理GATT连接。 4. 发现服务:连接成功后,调用BluetoothGatt的discoverServices()方法来发现设备提供的GATT服务。在回调函数onServicesDiscovered()中,可以获取到所有服务列表。 5. 获取服务和特征:在服务发现完成后,通过BluetoothGatt的getService()方法获取指定的GATT服务。然后,使用getService()方法获取指定服务中的GATT特征。 6. 创建并连接GATT服务器:使用Gatt连接连接函数连接设备服务器。 7. 连接成功后,可以使用BluetoothGatt的readCharacteristic()和writeCharacteristic()等方法来读取和写入GATT特征的值。 需要注意的是,BLE连接是异步的,所以在连接过程中需要实现相应的回调函数来处理连接和数据传输的事件。 以上是在Android中创建BLE服务的蓝牙socket连接的基本步骤。根据具体的使用场景和需求,可能还需要进一步处理异常情况、设置通知等操作。
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值