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

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"><itemandroid:id="@+id/item_filter_null"android:checkable="true"android:title="过滤空设备名" /><itemandroid:id="@+id/item_filter_mac"android:checkable="true"android:title="过滤Mac地址" /><itemandroid: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: Menuoverride fun onCreateOptionsMenu(menu: Menu): Boolean {menuInflater.inflate(R.menu.menu_main, menu)mMenu = menureturn 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 {...//mmkvimplementation '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中增加如下常量,代码如下所示:

    //过滤RSSIconst 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 = menumMenu.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_DEVICEval 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.MaterialToolbarandroid: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.TextInputLayoutandroid: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.TextInputEditTextandroid: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><CheckBoxandroid: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" /><Buttonandroid: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" /><Buttonandroid: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 = trueshowMsg("过滤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 = falseMVUtils.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 = menumMenu.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.MaterialToolbarandroid: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" /><TextViewandroid: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.AppCompatSeekBarandroid: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" /><TextViewandroid: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" /><Buttonandroid: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" /><Buttonandroid: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 = 100rssiBinding.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 = rssirssiBinding.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 = truemMenu.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 -> { // 过滤RSSIif (MVUtils.getBoolean(FILTER_RSSI_FLAG)) {if (bleCore.isScanning()) stopScan()//关闭过滤RSSIMVUtils.put(FILTER_RSSI_FLAG, false)mMenu.findItem(R.id.item_filter_rssi).isChecked = falseMVUtils.put(FILTER_RSSI_VALUE, 100)showMsg("取消过滤RSSI")if (!bleCore.isScanning()) startScan()} else {showSettingRssi()}}

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

    override fun onScanResult(result: ScanResult) {//过滤Mac地址...//过滤RSSIif (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 = menumMenu.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.mzph.cn/news/77211.shtml

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

相关文章

Electron和vue3集成(推荐仅用于开发)

本篇我们仅实现Electron和vue3通过先运行起vue3项目&#xff0c;再将vue3的url地址交由Electron打开的方案&#xff0c;仅由Electron在vue3项目上套一层壳来达到脱离本机浏览器运行目的 1、参考快速上手 | Vue.js搭建起vue3初始项目 npm install -g vue npm install -g vue/c…

Android 文字转语音播放实现

1&#xff0c;TextToSpeech类是android自带的&#xff0c;但是部分设备需要支持TTS需要增加语音库&#xff0c;我使用的是讯飞语音&#xff08;离线的哦&#xff09;。请自行下载并安装讯飞语音APK&#xff0c;然后到系统设置中设置TTS功能默认使用该选项。有自带TTS库的可以省…

口袋参谋:淘宝不限类目,透视竞品实时销量!快试试这个插件

​在运营一家店铺之前&#xff0c;可以先了解各类目宝贝的市场行情&#xff0c;及时掌握不同类目宝贝的价格、销售情况&#xff0c;根据需求制定出属于自己的营销策略。 【可跨类目竞店透视】功能&#xff1a; 支持一键获取任意店铺宝贝概况信息 【跨类目竞店透视】功能使用 …

SEO百度优化基础知识全解析(了解百度SEO标签作用)

百度SEO优化的作用介绍&#xff1a; 百度SEO优化是指通过对网站的内部结构、外部链接、内容质量、用户体验等方面进行优化&#xff0c;提升网站在百度搜索结果中的排名&#xff0c;从而提高网站的曝光率和流量。通过百度SEO优化&#xff0c;可以让更多的潜在用户找到你的网站&…

Navicat15工具连接PostgreSQL15失败

1.错误现象及原因 错误现象&#xff1a; 错误原因&#xff1a; postgresql 15版本中 pg_database 系统表把 datlastsysoid 列删除了&#xff0c;所以造成了此错误。 2.解决方法 &#xff08;1&#xff09;将Navicat工具更新到官网最新版本。 &#xff08;2&#xff09;更换…

C++编译静态成员函数报错: “osgGA::DriveManipulator::setEye”: 非静态成员函数的非法调用

来看代码 .h文件中 static void computePosition(const osg::Vec3d& eye,const osg::Vec3d& lv,const osg::Vec3d& up); void setEye(const osg::Vec3d& eye); void setRotation( const osg::Quat& rotation );osg::Vec3d _eye; osg::Quat _rotation…

探索程序员需要掌握的算法?

文章目录 一&#xff1a;引言二&#xff1a;常见算法介绍三&#xff1a;重点算法总结 &#x1f389;欢迎来到数据结构学习专栏~探索程序员需要掌握的算法&#xff1f; ☆* o(≧▽≦)o *☆嗨~我是IT陈寒&#x1f379;✨博客主页&#xff1a;IT陈寒的博客&#x1f388;该系列文章…

【大数据Hive】hive 加载数据常用方案使用详解

目录 一、前言 二、load 命令使用 2.1 load 概述 2.1.1 load 语法规则 2.1.2 load语法规则重要参数说明 2.2 load 数据加载操作演示 2.2.1 前置准备 2.2.2 加载本地数据 2.2.3 HDFS加载数据 2.2.4 从HDFS加载数据到分区表中并指定分区 2.3 hive3.0 load 命令新特性 …

解决SVN文件不显示绿色小钩图标问题

问题描述&#xff1a; 今天重新安装了SVN&#xff0c;发现从中央服务器拉取文件到本地仓库后&#xff0c;对应的文件没有绿色的小钩图标&#xff0c;于是查了一下解决方案&#xff0c;在这里总结一下。 解决方案一&#xff1a; 原因&#xff1a;状态缓存设置问题造成的。 在…

【数据结构】树的基础知识及三种存储结构

&#x1f490; &#x1f338; &#x1f337; &#x1f340; &#x1f339; &#x1f33b; &#x1f33a; &#x1f341; &#x1f343; &#x1f342; &#x1f33f; &#x1f344;&#x1f35d; &#x1f35b; &#x1f364; &#x1f4c3;个人主页 &#xff1a;阿然成长日记 …

【SG滤波】三阶滤波、五阶滤波、七阶滤波(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

【C++】AVL树的插入操作实现以及验证是否正确(带平衡因子)

文章目录 前言一、AVL树结点的定义二、AVL树的插入&#xff08;Insert&#xff09;插入完整代码&#xff1a;1.左单旋&#xff08;RotateL&#xff09;2.右单旋&#xff08;RotateR&#xff09;3.先右单旋再左单旋&#xff08;RotateRL&#xff09;1.保存的bf为02.保存的bf为13…

Excel中将文本格式的数值转换为数字

在使用excel时&#xff0c;有时需要对数字列进行各种计算&#xff0c;比如求平均值&#xff0c;我们都知道应该使用AVERAGE()函数&#xff0c;但是很多时候结果却“不尽如人意”。 1 问题&#xff1a; 使用AVERAGE函数&#xff1a; 结果&#xff1a; 可以看到单元格左上角有个…

docker 方式安装mysql 主从方式keepalived实现高可用

一、环境介绍 二、MySQL安装 在两台服务器上都安装mysql 1、拉取镜像 docker pull mysql:8.0.272、创建挂载目录 mkdir -p /data/mysql/3、运行容器 主节点 docker run \--restartalways \--name master_mysql -p 3306:3306 \-e MYSQL_ROOT_PASSWORD123456 -d \-v /data/m…

基于视觉重定位的室内AR导航APP的大创项目思路(3)手机相机内参数据获取和相机标定

文章目录 相机内参为什么要获取相机的内参数据&#xff1a;获取相机内存数据的方法棋盘格标定自动相机标定 前情提要&#xff1a; 是第一次做项目的小白&#xff0c;文章内的资料介绍如有错误&#xff0c;请多包含&#xff01; 相机内参 相机内参是本身的物理数据&#xff0c…

消息队列MQ

一、消息队列 网络端的Http请求默认采用的是同步请求方式&#xff0c;客户端与服务器端是基于请求和响应模式进行通信的。也就意味着&#xff0c;客户端发起请求。必须要等待服务器端完成处理结果给客户端才能继续进行下一步操作&#xff0c;如果服务器发送网络延迟、宕机、卡顿…

使用C语言EasyX 创建动态爱心背景

简介 在计算机图形学的世界中&#xff0c;有很多方法可以使程序的界面更加吸引人。在本篇博客中&#xff0c;我将向大家介绍如何使用 EasyX 图形库在 C 中创建一个动态的爱心背景。这不仅是一个简单的动画效果&#xff0c;它还包括背景的星星、旋转的心形以及一个美观的背景渐…

Java基于SpringBoot+Vue的 4S店车辆管理系统

博主介绍&#xff1a;✌程序员徐师兄、7年大厂程序员经历。全网粉丝30W,Csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ 文章目录 1 简介2 技术栈3 功能总览4 系统设计4.1 系统设计主要功能4.2 数据库设计4.2.1 数据库设计规范4.2…

freeRTOS系列教程之freeRTOS入门与工程实践【文档+视频教程+进阶视频教程】

《freeRTOS入门与工程实践》 前言课程内容授课方式 学前知识普及学前准备硬件准备资料准备 视频教程文档教程更好的阅读体验 前言 课程内容 嵌入式软件工程师的学习路线一般是&#xff1a;单片机->RTOS->Linux。当你掌握单片机开发后&#xff0c;如果要进一步提升编程水…

VUE 的eslint 代码规范检查

报错&#xff1a; You may use special comments to disable some warnings. Use // eslint-disable-next-line to ignore the next line. Use /* eslint-disable */ to ignore all warnings in a file. 解决&#xff1a; 注释&#xff1a;...(config.dev.useEslint ? [creat…