Android Ble蓝牙App(七)扫描过滤

news2024/11/27 9:39:57

Ble蓝牙App(七)扫描过滤

  • 前言
  • 目录
  • 正文
    • 一、增加菜单
    • 二、使用MMKV
      • ① 添加依赖
      • ② 封装MMKV
      • ③ 使用MMKV
    • 三、过滤空设备名
    • 四、过滤Mac地址
    • 五、过滤RSSI
    • 六、源码

前言

  在上一篇文章中了解了MTU的相关知识以及对于设备操作信息的展示,本篇文章中将增加扫描设备的过滤功能让你更方便的扫描想要找的低功耗蓝牙设备。

在这里插入图片描述

目录

  • Ble蓝牙App(一)扫描
  • Ble蓝牙App(二)连接与发现服务
  • Ble蓝牙App(三)特性和属性
  • Ble蓝牙App(四)UI优化和描述符
  • Ble蓝牙App(五)数据操作
  • Ble蓝牙App(六)请求MTU与显示设备信息
  • Ble蓝牙App(七)扫描过滤

正文

  增加扫描过滤主要就是让扫描设备的时候更方便找到想要的设备,下面我们来看有哪些功能的增加。

一、增加菜单

  为了不占用扫描页面的空间,我打算通过添加菜单来进行扫描的过滤操作,那么首先我们在menu下增加一个menu_scan.xml文件,代码如下所示:

<menu xmlns:android="http://schemas.android.com/apk/res/android">

    <item
        android:id="@+id/item_filter_null"
        android:checkable="true"
        android:title="过滤空设备名" />
    <item
        android:id="@+id/item_filter_mac"
        android:checkable="true"
        android:title="过滤Mac地址" />
    <item
        android:id="@+id/item_filter_rssi"
        android:checkable="true"
        android:title="过滤RSSI" />

</menu>

菜单中有三个Item,看一下预览效果图:

在这里插入图片描述
  三个Item都是选中Item,选中表示这个过滤功能项启用,可以全部都选中,也可以任意选择,之后我们进入到ScanActivity,首先是创建菜单和菜单选中,修改地方有三处:

第一处:在onCreate()函数中增加支持ActionBar,代码如下所示:

    override fun onCreate(savedInstanceState: Bundle?) {
		...
        setSupportActionBar(binding.toolbar)
        ...
    }

第二处:在ScanActivity中重写onCreateOptionsMenu()函数,代码如下所示:

	private lateinit var mMenu: Menu
	
    override fun onCreateOptionsMenu(menu: Menu): Boolean {
        menuInflater.inflate(R.menu.menu_main, menu)
        mMenu = menu
        return true
    }

创建选项菜单,再创建一个mMenu变量,在后面会用到的这个变量。

第三处:在ScanActivity中重写onOptionsItemSelected()函数,代码如下所示:

    override fun onOptionsItemSelected(item: MenuItem): Boolean {
        when(item.itemId) {
            R.id.item_filter_null -> { // 过滤空设备名称

            }
            R.id.item_filter_mac -> { // 过滤Mac地址
            
            }
            R.id.item_filter_rssi -> { // 过滤RSSI
     
            }
        }
        return true
    }

现在三个Item的点击事件中什么都不做,我们一步一步给它加上,现在菜单就创建好了。

二、使用MMKV

  因为我们修改的菜单项会涉及到保存过滤设置的功能,所以需要将一些参数报错到手机中,那么我们可以使用SP、DataStore等方式,但是这里我是用MMKV,主要是因为用起来比较的方便,下面我们来使用MMKV。

① 添加依赖

  MMKV是腾讯的一个开源项目,已经发布在mavenCentral()仓库中了,我们在App中使用只需要在app模块下的build.gradle中的dependencies{}闭包中添加如下依赖代码即可:

dependencies {
    ...
    //mmkv
    implementation 'com.tencent:mmkv:1.2.14'
}

然后点击Sync Now,同步一下,添加依赖就完成了。

② 封装MMKV

  针对于MMKV的使用其实非常简单,就是两步,先初始化,然后使用就好了,那么为了使用的更方便,我们可以简单封装一下MMKV,做成一个工具类,下面我们在com.llw.goodble包下新建一个utils包,utils包下新建一个MVUtils类,代码如下所示:

object MVUtils {

    val mmkv = MMKV.defaultMMKV()

    fun put(key: String, value: Any): Boolean {
        return when (value) {
            is String -> mmkv.encode(key, value)
            is Float -> mmkv.encode(key, value)
            is Boolean -> mmkv.encode(key, value)
            is Int -> mmkv.encode(key, value)
            is Long -> mmkv.encode(key, value)
            is Double -> mmkv.encode(key, value)
            is ByteArray -> mmkv.encode(key, value)
            is Parcelable -> mmkv.encode(key, value)
            else -> false
        }
    }

    fun put(key: String, sets: Set<String>?): Boolean {
        if (sets == null) {
            return false
        }
        return mmkv.encode(key, sets)
    }

    fun getInt(key: String, defaultValue: Int = 0) = mmkv.decodeInt(key, defaultValue)

    fun getDouble(key: String, defaultValue: Double = 0.00) = mmkv.decodeDouble(key, defaultValue)

    fun getLong(key: String, defaultValue: Long = 0L) = mmkv.decodeLong(key, defaultValue)

    fun getBoolean(key: String, defaultValue: Boolean = false) = mmkv.decodeBool(key, defaultValue)

    fun getFloat(key: String, defaultValue: Float = 0F) = mmkv.decodeFloat(key, defaultValue)

    fun getByteArray(key: String) = mmkv.decodeBytes(key)

    fun getString(key: String, defaultValue: String = "") = mmkv.decodeString(key, defaultValue)

    inline fun <reified T : Parcelable> getParcelable(key: String) =
        mmkv.decodeParcelable(key, T::class.java)

    fun getStringSet(key: String) = mmkv.decodeStringSet(key, Collections.emptySet())

    fun removeKey(key: String) = mmkv.removeValueForKey(key)

    fun clearAll() = mmkv.clearAll()
}

  这里实际上大体就分为三个部分,首先是初始化,然后是数据的存和取,最后是清除数据,是不是很简单呢?

③ 使用MMKV

  使用MMKV,首先需要做的就是初始化,我们需要在BleApponCreate()函数中进行初始化,代码如下所示:

    override fun onCreate() {
        ...
        //mmkv初始化
        MMKV.initialize(this)
    }

  使用MMKV同样是采用键值对的形式,那么基于我们的菜单功能,我们需要增加一些键,在BleConstant中增加如下常量,代码如下所示:

    //过滤RSSI
    const val FILTER_RSSI_FLAG = "filterRssiFlag"
    //RSSI 值
    const val FILTER_RSSI_VALUE = "filterRssiValue"
    //过滤空设备名
    const val FILTER_NULL_FLAG = "filterNullFlag"
    //是否过滤Mac地址
    const val FILTER_MAC_FLAG = "filterMacFlag"
    //需要过滤的Mac地址
    const val FILTER_MAC_VALUE = "filterMacValue"

下面我们修改ScanActivity中的onCreateOptionsMenu()函数,代码如下所示:

    override fun onCreateOptionsMenu(menu: Menu): Boolean {
        menuInflater.inflate(R.menu.menu_scan, menu)
        mMenu = menu
        mMenu.findItem(R.id.item_filter_rssi).isChecked = MVUtils.getBoolean(FILTER_RSSI_FLAG)
        if (MVUtils.getBoolean(FILTER_RSSI_FLAG)) {
            mMenu.findItem(R.id.item_filter_rssi).title =
                "过滤RSSI:-" + MVUtils.getInt(FILTER_RSSI_VALUE, 100)
        }
        mMenu.findItem(R.id.item_filter_null).isChecked = MVUtils.getBoolean(FILTER_NULL_FLAG)
        mMenu.findItem(R.id.item_filter_mac).isChecked = MVUtils.getBoolean(FILTER_MAC_FLAG)
        return true
    }

  在这里的代码就是在创建菜单的时候,判断一下保存的参数,是否需要选中Item,可以修改Item的选中状态和标题内容,这里就是获取参数。

三、过滤空设备名

  下面我们来保存参数,修改onOptionsItemSelected()函数中的代码:

            R.id.item_filter_null -> { // 过滤空设备名称
                if (bleCore.isScanning()) stopScan()
                val filterNull = MVUtils.getBoolean(FILTER_NULL_FLAG)
                MVUtils.put(FILTER_NULL_FLAG, !filterNull)
                mMenu.findItem(R.id.item_filter_null).isChecked = MVUtils.getBoolean(FILTER_NULL_FLAG)
                showMsg(if (MVUtils.getBoolean(FILTER_NULL_FLAG)) "过滤空设备名称的设备" else "保留空设备名称的设备")
                if (!bleCore.isScanning()) startScan()
            }

  这里看到就是在点击过滤空设备Item时,首先停止扫描,然后获取参数值,再保存,根据值设置Item是否选中,最后开始扫描,那么我们怎么过滤这个空设备名称的设备呢?还需要修改扫描回调中的代码:

    override fun onScanResult(result: ScanResult) {
        //过滤空设备名
        if (MVUtils.getBoolean(FILTER_NULL_FLAG)) {
            if (result.scanRecord!!.deviceName == null) {
                return
            }
            if (result.scanRecord!!.deviceName!!.isEmpty()) {
                return
            }
        }
        ...
    }

  这里我们只需要在原有的条件上再增加一个判断即可,因为缺省值是false,所以如果是不过滤空设备名就不会执行判断里面空处理和空设备名处理,看一下运行的效果。

在这里插入图片描述
  我们看到默认是不过滤空设备名称的,当选中过滤空设备名后就会过滤设备名称为空的设备,只不过我们这里对于空设备名称的设备显示的UI还没有处理的很好,下面我们简单改一下,将onScanResult()函数中的这一行代码:

	val bleDevice = BleDevice(result.scanRecord!!.deviceName, result.device.address, result.rssi, result.device)

改成

	val realName = result.scanRecord?.deviceName?.let { it.ifEmpty { BleConstant.UNKNOWN_DEVICE } } ?: BleConstant.UNKNOWN_DEVICE
    val bleDevice = BleDevice(realName, result.device.address, result.rssi, result.device)

  这里改的目的就是首先判断获取的设备名是否为空,如果为空则返回一个Unknown device作为设备名称,不为空则检查是否为空字符串,是的话也返回Unknown device,不是则返回本身设备名称,再运行一下就可以了。

四、过滤Mac地址

  下面我们要做过滤Mac地址,那么要过滤Mac地址,首先要输入Mac地址,那么我们可以写一个弹窗来进行输入的工作,在layout下创建一个dialog_settings_mac.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"
    style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="@color/white">

    <com.google.android.material.appbar.MaterialToolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:title="过滤Mac地址" />

    <com.google.android.material.textfield.TextInputLayout
        android:id="@+id/data_layout"
        style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginEnd="16dp"
        app:boxStrokeColor="@color/black"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/toolbar">

        <com.google.android.material.textfield.TextInputEditText
            android:id="@+id/et_data"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="Mac Address"
            android:lines="1"
            android:singleLine="true" />
    </com.google.android.material.textfield.TextInputLayout>

    <CheckBox
        android:id="@+id/cb_format_check"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Mac地址格式检查"
        app:layout_constraintStart_toStartOf="@+id/data_layout"
        app:layout_constraintTop_toBottomOf="@+id/data_layout" />

    <Button
        android:id="@+id/btn_negative"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginEnd="18dp"
        android:layout_weight="1"
        android:text="取消"
        app:layout_constraintEnd_toStartOf="@+id/btn_positive"
        app:layout_constraintTop_toTopOf="@+id/btn_positive" />

    <Button
        android:id="@+id/btn_positive"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="16dp"
        android:layout_weight="1"
        android:text="确定"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="@+id/data_layout"
        app:layout_constraintTop_toBottomOf="@+id/cb_format_check" />

</androidx.constraintlayout.widget.ConstraintLayout>

这个布局中有一个检查Mac地址正确性的复选框,同样我们需要在BleUtils中增加一个函数,代码如下所示:

    fun isValidMac(macStr: String) = Regex("([A-Fa-f0-9]{2}[:]){5}[A-Fa-f0-9]{2}").matches(macStr)

下面我们回到ScanActivity中,写一个showSettingMacDialog()函数,代码如下所示:

    private fun showSettingMacDialog() {
        val dialog = BottomSheetDialog(this, R.style.BottomSheetDialogStyle)
        val macBinding = DialogSettingMacBinding.inflate(layoutInflater)
        macBinding.btnPositive.setOnClickListener {
            val inputData = macBinding.etData.text.toString()
            if (inputData.isEmpty()) {
                macBinding.dataLayout.error = "请输入Mac地址"
                return@setOnClickListener
            }
            if (macBinding.cbFormatCheck.isChecked) {
                if (!BleUtils.isValidMac(inputData)) {
                    macBinding.dataLayout.error = "请输入正确的Mac地址"
                    return@setOnClickListener
                }
            }
            if (bleCore.isScanning()) stopScan()
            MVUtils.put(FILTER_MAC_VALUE, inputData)
            MVUtils.put(FILTER_MAC_FLAG, true)
            mMenu.findItem(R.id.item_filter_mac).isChecked = true
            showMsg("过滤Mac地址")
            if (!bleCore.isScanning()) startScan()
            dialog.dismiss()
        }
        macBinding.btnNegative.setOnClickListener {
            dialog.dismiss()
        }
        dialog.setContentView(macBinding.root)
        dialog.show()
    }

  弹窗中点击确定按钮就会先检查一遍,然后就会保存Mac地址,再保存过滤标识,然后我们修改一下过滤Mac地址Item的点击事件,代码如下所示:

            R.id.item_filter_mac -> { // 过滤Mac地址
                if (MVUtils.getBoolean(FILTER_MAC_FLAG)) {
                    mMenu.findItem(R.id.item_filter_mac).isChecked = false
                    MVUtils.put(FILTER_MAC_FLAG, false)
                    MVUtils.put(FILTER_MAC_VALUE, "")
                    showMsg("不过滤设备地址")
                } else {
                    showSettingMacDialog()
                }
            }

  首先判断是否过滤,有的话就不再过滤,没有的话就显示输入Mac地址弹窗,如果过滤了,我们就需要在扫描回调函数中增加一个过滤的选项。

    override fun onScanResult(result: ScanResult) {
        //过滤空设备名
		...
        //过滤Mac地址
        if (MVUtils.getBoolean(FILTER_MAC_FLAG)) {
            val filterMac: String? = MVUtils.getString(FILTER_MAC_VALUE, "")
            if (filterMac!!.isNotEmpty()) {
                if (!result.device.address.contains(filterMac)) return
            }
        }
        ...
    }

  过滤的位置可以放在过滤空设备名称之后或者之前都可以,最后还需要修改onCreateOptionsMenu()函数中的代码如下所示:

    override fun onCreateOptionsMenu(menu: Menu): Boolean {
        menuInflater.inflate(R.menu.menu_scan, menu)
        mMenu = menu
        mMenu.findItem(R.id.item_filter_null).isChecked = MVUtils.getBoolean(FILTER_NULL_FLAG)
        mMenu.findItem(R.id.item_filter_mac).isChecked = MVUtils.getBoolean(FILTER_MAC_FLAG)
        return true
    }

增加了一个对于Mac地址Item项是否选中的判断,下面我们可以运行看看,我们的过滤是否有效果。

在这里插入图片描述

这样过滤Mac地址就做好了,下面过滤RSSI信号强度。

五、过滤RSSI

  与过滤Mac地址一样,过滤RSSI首先要做的就是设置RSSI,对此,我们同样在layout下创建一个dialog_settings_rssi.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"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/white">

    <com.google.android.material.appbar.MaterialToolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:title="过滤RSSI" />

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginTop="16dp"
        android:text="RSSI:"
        android:textColor="@color/black"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/toolbar" />

    <androidx.appcompat.widget.AppCompatSeekBar
        android:id="@+id/sb_rssi"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:max="100"
        android:min="35"
        android:progress="100"
        android:progressTint="@color/orange"
        android:thumbTint="@color/dark_orange"
        app:layout_constraintBottom_toBottomOf="@+id/textView"
        app:layout_constraintEnd_toStartOf="@+id/tv_rssi"
        app:layout_constraintStart_toEndOf="@+id/textView"
        app:layout_constraintTop_toTopOf="@+id/textView" />

    <TextView
        android:id="@+id/tv_rssi"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginEnd="16dp"
        android:text="-100 dBm"
        android:textColor="@color/black"
        app:layout_constraintBottom_toBottomOf="@+id/textView"
        app:layout_constraintEnd_toEndOf="parent" />

    <Button
        android:id="@+id/btn_negative"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginEnd="18dp"
        android:layout_weight="1"
        android:text="取消"
        app:layout_constraintEnd_toStartOf="@+id/btn_positive"
        app:layout_constraintTop_toTopOf="@+id/btn_positive" />

    <Button
        android:id="@+id/btn_positive"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="16dp"
        android:layout_weight="1"
        android:text="确定"
        app:layout_constraintEnd_toEndOf="@+id/tv_rssi"
        app:layout_constraintTop_toBottomOf="@+id/tv_rssi" />
</androidx.constraintlayout.widget.ConstraintLayout>

然后就是在ScanActivity中增加一个showSettingRssi()函数,代码如下所示:

    private fun showSettingRssi() {
        val dialog = BottomSheetDialog(this, R.style.BottomSheetDialogStyle)
        val rssiBinding = DialogSettingRssiBinding.inflate(layoutInflater)
        var progress = 100
        rssiBinding.sbRssi.setOnSeekBarChangeListener(object : OnSeekBarChangeListener {
            @SuppressLint("SetTextI18n")
            override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
                rssiBinding.tvRssi.text = "-$progress dBm"
            }
            override fun onStartTrackingTouch(seekBar: SeekBar) {}
            override fun onStopTrackingTouch(seekBar: SeekBar) {
                progress = seekBar.progress
            }
        })
        val rssi: Int = MVUtils.getInt(FILTER_RSSI_VALUE, 100)
        rssiBinding.sbRssi.progress = rssi
        rssiBinding.tvRssi.text = String.format("-%s dBm", rssi)
        rssiBinding.btnPositive.setOnClickListener {
            //保存
            if (bleCore.isScanning()) stopScan()
            MVUtils.put(FILTER_RSSI_FLAG, true)
            //保存设置的RSSI值
            MVUtils.put(FILTER_RSSI_VALUE, progress)
            mMenu.findItem(R.id.item_filter_rssi).isChecked = true
            mMenu.findItem(R.id.item_filter_rssi).title = "过滤RSSI:-$progress"
            showMsg("过滤RSSI:-" + progress + "dBm")
            if (!bleCore.isScanning()) startScan()
            dialog.dismiss()
        }
        rssiBinding.btnNegative.setOnClickListener { dialog.dismiss() }
        dialog.setContentView(rssiBinding.root)
        dialog.show()
    }

  在点击确定按钮的时候,保存设置的RSSI信号强度值,如果没有设置就是默认的值,然后我们修改一下过滤RSSI Item的点击事件,代码如下所示:

            R.id.item_filter_rssi -> { // 过滤RSSI
                if (MVUtils.getBoolean(FILTER_RSSI_FLAG)) {
                    if (bleCore.isScanning()) stopScan()
                    //关闭过滤RSSI
                    MVUtils.put(FILTER_RSSI_FLAG, false)
                    mMenu.findItem(R.id.item_filter_rssi).isChecked = false
                    MVUtils.put(FILTER_RSSI_VALUE, 100)
                    showMsg("取消过滤RSSI")
                    if (!bleCore.isScanning()) startScan()
                } else {
                    showSettingRssi()
                }
            }

  当前已有过滤RSSI,再次点击时就会取消过滤的信息,知道你再次设置RSSI过滤值,接下来就是扫描回调中,根据这个设置项进行一次过滤:

    override fun onScanResult(result: ScanResult) {
        //过滤Mac地址
		...
        //过滤RSSI
        if (MVUtils.getBoolean(FILTER_RSSI_FLAG)) {
            val rssi: Int = -MVUtils.getInt(FILTER_RSSI_VALUE, 100)
            if (result.rssi < rssi) {
                return
            }
        }
		...
    }

  最后为了保存设置项,是我们再次打开App时,UI上是正确的,我们修改onCreateOptionsMenu()函数,代码如下所示:

    override fun onCreateOptionsMenu(menu: Menu): Boolean {
        menuInflater.inflate(R.menu.menu_scan, menu)
        mMenu = menu
        mMenu.findItem(R.id.item_filter_null).isChecked = MVUtils.getBoolean(FILTER_NULL_FLAG)
        mMenu.findItem(R.id.item_filter_mac).isChecked = MVUtils.getBoolean(FILTER_MAC_FLAG)
        mMenu.findItem(R.id.item_filter_rssi).isChecked = MVUtils.getBoolean(FILTER_RSSI_FLAG)
        if (MVUtils.getBoolean(FILTER_RSSI_FLAG)) {
            mMenu.findItem(R.id.item_filter_rssi).title =
                "过滤RSSI:-" + MVUtils.getInt(FILTER_RSSI_VALUE, 100)
        }
        return true
    }

运行一下,看看效果:

在这里插入图片描述

  关于扫描过滤的功能就写好了,本文内容介绍。

六、源码

如果对你有所帮助的话,不妨 StarFork,山高水长,后会有期~

源码地址:GoodBle

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

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

相关文章

uniapp 在 onLoad 事件中 this.$refs 娶不到的问题

现象 本人想在主页面加载的时候调用子组件的方法。示例代码如下&#xff1a; 运行&#xff0c;发现 this.$refs 取不到。如下图所示&#xff1a; 解决方法&#xff0c;把onLoad 换为 onReady 就可以了。

存储过程报Illegal mix of collations错误的解决方法

CREATE PROCEDURE maxAgeStudent(IN _gender CHAR) BEGINDECLARE maxage INT DEFAULT 0;SELECT max(age) INTO maxage FROM student where gender _gender;SELECT * from student WHERE age maxage and gender _gender; END; 在调用的时候 call maxAgeStudent(1) 产生了报…

MySQL数据库的增删改查(进阶)

目录 数据库约束 约束类型 NULL约束 UNIQUE&#xff1a;唯一约束 DEFAULT&#xff1a;默认值约束 PRIMARY KEY&#xff1a;主键约束 FOREIGN KEY&#xff1a;外键约束 表的设计 一对一关系 一对多关系 多对多关系 查询 聚合查询 聚合函数 GROUP BY子句 HAVING …

LeetCode 1123. 最深叶节点的最近公共祖先:DFS

【LetMeFly】1123.最深叶节点的最近公共祖先 力扣题目链接&#xff1a;https://leetcode.cn/problems/lowest-common-ancestor-of-deepest-leaves/ 给你一个有根节点 root 的二叉树&#xff0c;返回它 最深的叶节点的最近公共祖先 。 回想一下&#xff1a; 叶节点 是二叉树…

2023年09月在线IDE流行度最新排名

点击查看最新在线IDE流行度最新排名&#xff08;每月更新&#xff09; 2023年09月在线IDE流行度最新排名 TOP 在线IDE排名是通过分析在线ide名称在谷歌上被搜索的频率而创建的 在线IDE被搜索的次数越多&#xff0c;人们就会认为它越受欢迎。原始数据来自谷歌Trends 如果您相…

如何创建专栏

前言 今天&#xff0c;有一个粉丝问我该如何创建一个专栏&#xff0c;好的&#xff0c;安排上&#xff01; 什么是专栏&#xff1f; 专栏是用户写的一部分博客的分类。可以理解为&#xff1a; 1.我有一些文件&#xff08;文件代指博客&#xff09;&#xff0c;于是我创建了一…

十六、MySQL常用函数有哪些?

1、函数 说到函数&#xff0c;就必须知道其本质是什么&#xff0c;在MySQL中&#xff0c;函数是指一段可以直接被另一段程序调用的程序或代码。 2、字符串函数 &#xff08;1&#xff09;函数 &#xff08;2&#xff09;字符串连接函数 字符串连接函数&#xff1a; select c…

第三章 LInux多线程开发 3.1-3.5线程创建 终止 分离

创建线程&#xff1a;&#xff08;好好记住 可能会叫写代码&#xff09; 一般情况下,main函数所在的线程我们称之为主线程&#xff08;main线程&#xff09;&#xff0c;其余创建的线程称之为子线程。 程序中默认只有一个进程&#xff0c;fork()函数调用&#xff0c;2进行 程序…

ComfyUI 安装

背景&#xff1a; stable diffussion XL最先适配&#xff0c;专业性强的SD操作界面 安装步骤&#xff1a; git clone GitHub - comfyanonymous/ComfyUI: A powerful and modular stable diffusion GUI with a graph/nodes interface. 1、pip install torch torchvision torc…

【压力测试指南】没有任何文档,小白也可以做的压力测试

前言 一般在执行压力测试之前&#xff0c;会由开发提供出接口文档&#xff0c;包含一些接口的详细参数&#xff0c;便于测试工程师编写测试脚本。但在某些情况下&#xff0c;接口等相关文档缺失&#xff0c;那作为Tester&#xff0c;我们该如何顺利的实施压力测试呢&#xff1…

问道管理:光刻胶概念再度活跃,广信材料两连板,蓝英装备等涨停

光刻胶概念6日盘中再度活泼&#xff0c;截至发稿&#xff0c;扬帆新材、广信资料、蓝英配备“20cm”涨停&#xff0c;盛剑环境亦涨停&#xff0c;高盟新材涨超9%&#xff0c;同益股份、容大感光涨超5%。 值得注意的是&#xff0c;广信资料已连续两个交易日涨停。公司近来在成绩…

快速回顾Intel的发展史,看看你都用过哪些处理器

前言 提到CPU我们第一个想起的就是Intel其次才是AMD&#xff0c;有计算机的地方就有它的身影&#xff0c;Intel一直占据着半导体金字塔的顶端&#xff0c;也一直牵制着整个PC市场&#xff0c;这些年间Intel发布了各种各样的CPU&#xff0c;我大体给大家总结一下&#xff0c;看…

聊聊 HTMX 吧

写在前面 最近看了几篇关于 htmx 的文章&#xff0c;自己也去看了一眼官网&#xff0c;也去油管看了一下当时 htmx 发布会的时候他们的演示&#xff0c;下面说几点我对这个所谓的新型起来的技术的看法&#xff0c; 他的来源是什么 首先说一下他虽然是一个新型的技术&#xff0c…

【每日一题】73. 矩阵置零

73. 矩阵置零 - 力扣&#xff08;LeetCode&#xff09; 给定一个 m x n 的矩阵&#xff0c;如果一个元素为 0 &#xff0c;则将其所在行和列的所有元素都设为 0 。请使用 原地 算法。 示例 1&#xff1a; 输入&#xff1a;matrix [[1,1,1],[1,0,1],[1,1,1]] 输出&#xff1a;[…

一文讲解Transformer

我们本篇文章来详细讲解Transformer: 首次提出在&#xff1a;Attention is all you need (arxiv.org) 简单来说&#xff0c;Transfomer就是一种Seq2seq结构&#xff0c;它基于多头自注意力机制&#xff0c;解决了传统RNN在计算过程中不能够并行化的问题。即相较于RNN而言&…

c语言每日一练(14)【加强版】

前言&#xff1a;每日一练系列&#xff0c;每一期都包含5道选择题&#xff0c;2道编程题&#xff0c;博主会尽可能详细地进行讲解&#xff0c;令初学者也能听的清晰。博主有时会将一些难题综合成每日一练加强版&#xff0c;加强版是特殊的&#xff0c;它仅包含5道选择题&#x…

如何将home目录空间扩充到根目录下

目录 1、查看查看磁盘使用情况2、扩容思路3、卸载并删除/home4、扩大/root逻辑卷5、扩大/文件系统6、重建/home逻辑卷7、创建/home文件系统8、将新建的文件系统挂载到/home目录下9、恢复/home并删除备份10、再次查看看磁盘存储 系统&#xff1a;centos7.9 1、查看查看磁盘使用…

shell指令练习

一、使用cut截取出Ubuntu用户的家目录&#xff0c;要求&#xff1a;不能使用":"作为分割 ubuntuubuntu:01_day$ grep "^Ubuntu" /etc/passwd -ni | cut -d "/" -f 2,3 | cut -c 1-11 home/ubuntu

【算法专题突破】双指针 - 和为s的两个数字(6)

目录 1. 题目解析 2. 算法原理 3. 代码编写 写在最后&#xff1a; 1. 题目解析 题目链接&#xff1a;剑指 Offer 57. 和为s的两个数字 - 力扣&#xff08;Leetcode&#xff09; 这道题题目就一句话但是也是有信息可以提取的&#xff0c; 最重要的就是开始的那句话&#…

SonarQube介绍和安装

docker安装postgres数据库 docker安装sonarqube 安装前在官网上确定一下可用的版本号 创建sonarqube_docker目录 本实验中&#xff0c;jdk,maven,jenkins,postgres,sonarqube都安装在同一台服务器上。 docker compose启动 修改虚拟机内存 sonarqube启动成功 默认用户名和密…