Android Ble蓝牙App(二)连接与发现服务
Ble蓝牙App(二)连接与发现服务
- 前言
- 正文
- 一、GATT回调
- 二、连接和断连
- 三、连接状态回调
- 四、发现服务
- 五、服务适配器
- 六、显示服务
- 七、源码
前言
在上一篇中我们进行扫描设备的处理,本文中进行连接和发现服务的数据处理,运行效果图如下所示:

正文
现在我们从MainActivity进入到ScanActivity,选中一个设备返回到MainActivity,下面要对选中的设备进行处理,首先我们来做连接。
一、GATT回调
在之前我们写了一个BleCore,这里面是对扫描的封装,那么对于连接来说我们同样可以封装到这里,我们可以在BleCore中写一个BleGattCallback 类,代码如下所示:
class BleGattCallback : BluetoothGattCallback() {/*** 连接状态改变*/override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) {}/*** 发现服务*/override fun onServicesDiscovered(gatt: BluetoothGatt, status: Int) {}}
因为本文要做的事情是连接和发现服务,所以我们就先重写这两个函数,注意一点的是,蓝牙的操作都是在子线程中进行的,如果我们需要知道当前是否连接,则需要写一个接口用于回调到Activity中,在ble包下新建一个BleCallback接口,代码如下所示:
interface BleCallback {/*** 设备的所有信息*/fun deviceInfo(info: String)/*** 连接状态* @param state true or false*/fun onConnectionStateChange(state: Boolean)/*** 发现服务*/fun onServicesDiscovered(services: List<BluetoothGattService>)
}
接口中定义了三个函数,通过注释我们清晰的知道都是什么作用,这里着重介绍第一个函数,这个函数会显示设备各个时候的状态信息,从连接之后的所有动作,如果我们需要保存设备的操作日志的话,可以通过这个来进行处理保存。
然后回到BleCore,在companion object中声明变量和设置接口回调的函数:
@SuppressLint("StaticFieldLeak")companion object {...private var mGatt: BluetoothGatt? = nullprivate var mBleCallback: BleCallback? = nullprivate lateinit var mBleGattCallback: BleGattCallback/*** 是否连接*/private var mIsConnected = false/*** 设备信息*/private fun deviceInfo(info: String) = mBleCallback?.deviceInfo(info)/*** 连接状态*/private fun connectState(state: Boolean) {mIsConnected = statemBleCallback?.onConnectionStateChange(state)}}
同时在 companion object外创建一个函数,代码如下所示:
fun setBleCallback(bleCallback: BleCallback) {mBleCallback = bleCallback}
此函数和setPhyScanCallback()函数是同级的,下面我们增加连接和断连的函数。
二、连接和断连
在BleCore中增加如下代码:
/*** 连接蓝牙设备*/fun connect(device: BluetoothDevice) {deviceInfo("连接中...")mGatt = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {device.connectGatt(context, false, mBleGattCallback, BluetoothDevice.TRANSPORT_LE, BluetoothDevice.PHY_LE_2M_MASK)} else {device.connectGatt(context, false, mBleGattCallback)}}/*** 断开连接*/fun disconnect() {deviceInfo("断开连接...")mGatt?.disconnect()}
连接与断开连接,调用时会触发onConnectionStateChange()函数。
三、连接状态回调
下面修改这个函数的代码,如下所示:
override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) {val address = gatt.device.addresswhen (newState) {BluetoothProfile.STATE_CONNECTED -> {deviceInfo("已连接:$address")connectState(true)}BluetoothProfile.STATE_DISCONNECTED -> {deviceInfo("已断开连接:$address")connectState(false)}else -> {Log.d(TAG, "onConnectionStateChange: $status")connectState(false)mGatt?.close()mGatt = null}}}
在回调中,连接成功和断开连接都会有一个对应的状态码,通过状态回调到接口函数中,然后回到MainActivity中使用一下这个回调,首先我们修改一下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:layout_width="match_parent"android:layout_height="match_parent"tools:context=".MainActivity"><com.google.android.material.appbar.MaterialToolbarandroid:id="@+id/toolbar"android:layout_width="match_parent"android:layout_height="?attr/actionBarSize"android:background="@color/orange"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toTopOf="parent"app:navigationIcon="@drawable/ic_scan_ble"app:title="GoodBle"app:titleCentered="true"app:titleTextColor="@color/white"><TextViewandroid:id="@+id/tv_disconnect"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="end"android:layout_marginEnd="8dp"android:visibility="gone"android:padding="8dp"android:text="断开连接"android:textColor="@color/white" /></com.google.android.material.appbar.MaterialToolbar><TextViewandroid:id="@+id/tv_device_info"android:layout_width="0dp"android:layout_height="wrap_content"android:padding="16dp"android:text="设备信息"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toBottomOf="@+id/toolbar" /></androidx.constraintlayout.widget.ConstraintLayout>
在XML中只增加了两个TextView,分别用于断连和显示设备状态,然后我们修改MainActivity中的代码,如下所示:
class MainActivity : BaseActivity(), BleCallback {private val binding by viewBinding(ActivityMainBinding::inflate)private lateinit var bleCore: BleCore@SuppressLint("MissingPermission")private val scanIntent =registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->if (result.resultCode == Activity.RESULT_OK) {if (result.data == null) return@registerForActivityResult//获取选中的设备val device = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {result.data!!.getParcelableExtra("device", BluetoothDevice::class.java)} else {result.data!!.getParcelableExtra("device") as BluetoothDevice?}//连接设备if (device != null) bleCore.connect(device)}}override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_main)bleCore = (application as BleApp).getBleCore()bleCore.setBleCallback(this@MainActivity)//进入扫描页面binding.toolbar.setNavigationOnClickListener { scanIntent.launch(Intent(this,ScanActivity::class.java)) }//断开连接binding.tvDisconnect.setOnClickListener {binding.tvDisconnect.visibility = View.GONEbleCore.disconnect()}}override fun deviceInfo(info: String) {runOnUiThread {binding.tvDeviceInfo.text = info}}override fun onConnectionStateChange(state: Boolean) {runOnUiThread {if (state) binding.tvDisconnect.visibility = View.VISIBLE}}override fun onServicesDiscovered(services: List<BluetoothGattService>) {}
}
这里我们首先是通过Activity Result API的StartActivityForResult()函数进行页面跳转,在返回的时候拿到device对象,这在前一篇已经写好了,拿到device对象之后调用BleCore的connect()函数进行连接设备,在onCreate()函数中进行BleCore的赋值,然后设置Ble的回调,实现BleCallback接口,重写里面的函数,当连接成功之后会通过回调deviceInfo()得到设备状态,因为是子线程所以在ui线程中渲染UI。而onConnectionStateChange()函数,回调连接成功或者失败,如果成功则为ture,就显示tvDisconnect控件,此时连接成功,点击这个tvDisconnect就会断开连接,点击监听就在onCreate()中写好了,下面我们运行一下看看效果。

从这个效果图来看,我们连接成功之后有状态,点击断开连接也会有状态改变,那么连接就写好了。
四、发现服务
连接写好了,下面可以写发现服务了,我们可以在连接成功的处理中进行发现服务,下面我们修改一下BleGattCallback中的onConnectionStateChange()函数中的代码,如下图所示:

通过gatt.discoverServices()进行发现服务的动作,在此之前通过deviceInfo设置当前的动作状态,发现服务执行会触发onServicesDiscovered()回调,在这个回调中我们可以回调到页面,修改代码如下所示:
override fun onServicesDiscovered(gatt: BluetoothGatt, status: Int) {if (status == BluetoothGatt.GATT_SUCCESS) {deviceInfo("发现了 ${gatt.services.size} 个服务")gatt.services?.let { mBleCallback?.onServicesDiscovered(it) }}}
在回调中设置发现服务的个数,然后回调,因为服务是多个的,那么下面我们就需要使用一个列表是装载服务,首先我们修改一下activity_main.xml,在里面增加一个RecyclerView,代码如下所示:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout...>...<androidx.recyclerview.widget.RecyclerViewandroid:id="@+id/rv_service"android:layout_width="match_parent"android:layout_height="wrap_content"android:background="@color/white"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toBottomOf="@+id/tv_device_info" /></androidx.constraintlayout.widget.ConstraintLayout>
五、服务适配器
要显示服务列表数据,首先需要一个适配器,而适配器又需要一个item去渲染数据,下面我们在layout下创建一个item_service.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:id="@+id/item_service"android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_marginBottom="2dp"android:background="@color/white"android:orientation="vertical"><TextViewandroid:id="@+id/tv_service_name"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginStart="16dp"android:layout_marginTop="8dp"android:text="服务"android:textColor="@color/black"android:textSize="16sp"android:textStyle="bold"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toTopOf="parent" /><TextViewandroid:id="@+id/tv_uuid_title"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="UUID:"app:layout_constraintStart_toStartOf="@+id/tv_service_name"app:layout_constraintTop_toBottomOf="@+id/tv_service_name" /><TextViewandroid:id="@+id/tv_service_uuid"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="UUID"android:textColor="@color/black"app:layout_constraintBottom_toBottomOf="@+id/tv_uuid_title"app:layout_constraintStart_toEndOf="@+id/tv_uuid_title"app:layout_constraintTop_toTopOf="@+id/tv_uuid_title" /><TextViewandroid:id="@+id/tv_service_info"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginBottom="8dp"android:text="PRIMARY SERVICE"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintStart_toStartOf="@+id/tv_service_name"app:layout_constraintTop_toBottomOf="@+id/tv_uuid_title" /></androidx.constraintlayout.widget.ConstraintLayout>
下面我们在ble包下新建一个BleUtils类,代码如下所示:
object BleUtils {private val generic = "-0000-1000-8000-00805F9B34FB"/*** 获取蓝牙服务名称* @param uuid UUID*/fun getServiceName(uuid: UUID) =when ("0x${uuid.toString().substring(4, 8).uppercase(Locale.getDefault())}") {"0x1800" -> "Generic Access service""0x1801" -> "Generic Attribute service""0x1802" -> "Immediate Alert service""0x1803" -> "Link Loss service""0x1804" -> "Tx Power service""0x1805" -> "Current Time service""0x1806" -> "Reference Time Update service""0x1807" -> "Next DST Change service""0x1808" -> "Glucose service""0x1809" -> "Health Thermometer service""0x180A" -> "Device Information service""0x180D" -> "Heart Rate service""0x180E" -> "Phone Alert Status service""0x180F" -> "Battery service""0x1810" -> "Blood Pressure service""0x1811" -> "Alert Notification service""0x1812" -> "Human Interface Device service""0x1813" -> "Scan Parameters service""0x1814" -> "Running Speed and Cadence service""0x1815" -> "Automation IO service""0x1816" -> "Cycling Speed and Cadence service""0x1818" -> "Cycling Power service""0x1819" -> "Location and Navigation service""0x181A" -> "Environmental Sensing service""0x181B" -> "Body Composition service""0x181C" -> "User Data service""0x181D" -> "Weight Scale service""0x181E" -> "Bond Management service""0x181F" -> "Continuous Glucose Monitoring service""0x1820" -> "Internet Protocol Support service""0x1821" -> "Indoor Positioning service""0x1822" -> "Pulse Oximeter service""0x1823" -> "HTTP Proxy service""0x1824" -> "Transport Discovery service""0x1825" -> "Object Transfer service""0x1826" -> "Fitness Machine service""0x1827" -> "Mesh Provisioning service""0x1828" -> "Mesh Proxy service""0x1829" -> "Reconnection Configuration service""0x183A" -> "Insulin Delivery service""0x183B" -> "Binary Sensor service""0x183C" -> "Emergency Configuration service""0x183D" -> "Authorization Control service""0x183E" -> "Physical Activity Monitor service""0x183F" -> "Elapsed Time service""0x1840" -> "Generic Health Sensor service""0x1843" -> "Audio Input Control service""0x1844" -> "Volume Control service""0x1845" -> "Volume Offset Control service""0x1846" -> "Coordinated Set Identification service""0x1847" -> "Device Time service""0x1848" -> "Media Control service""0x1849" -> "Generic Media Control service""0x184A" -> "Constant Tone Extension service""0x184B" -> "Telephone Bearer service""0x184C" -> "Generic Telephone Bearer service""0x184D" -> "Microphone Control service""0x184E" -> "Audio Stream Control service""0x184F" -> "Broadcast Audio Scan service""0x1850" -> " Published Audio Capabilities service""0x1851" -> "Basic Audio Announcement service""0x1852" -> "Broadcast Audio Announcement service""0x1853" -> "Common Audio service""0x1854" -> "Hearing Access service""0x1855" -> "Telephony and Media Audio service""0x1856" -> "Public Broadcast Announcement service""0x1857" -> "Electronic Shelf Label service"else -> "Unknown Service"}fun getServiceUUID(uuid: UUID) ="0x${uuid.toString().substring(4, 8).uppercase(Locale.getDefault())}"
}
这里需要说明一下蓝牙的UUID,蓝牙UUID(Universally Unique Identifier)是用于唯一标识蓝牙设备和服务的一种标识符。它是一个128位长的数字,在蓝牙通信中起到唯一标识的作用。蓝牙UUID按照标准分为两种类型:
-
16位UUID:这些UUID通常用于蓝牙标准定义的一些通用服务和特性。例如,设备名称服务的UUID是 00001800-0000-1000-8000-00805F9B34FB。
-
128位UUID:这些UUID通常用于自定义的服务和特性,以确保全球唯一性。可以自行生成一个128位的UUID作为自定义的服务或特性标识。例如,一个自定义的服务UUID可以是 0000XXXX-0000-1000-8000-00805F9B34FB,其中的 XXXX 部分可以是任意的16进制数字。
在蓝牙通信中,设备使用UUID来发布和查找服务以及识别特性。UUID是蓝牙设备之间进行通信时的重要标识,确保了设备和服务的唯一性。
那么getServiceName()中的键你就知道是什么意思了,0x1800就是16进制数字,而对应的值则是SIG定义的,可以参考这个文档:Assigned_Numbers.pdf。如果你的值找不到对应的,那说明它不是SIG规范的,你这个服务UUID就是自己公司自定义的。
下面我们写适配器,在adapter包下新建一个ServiceAdapter类,代码如下所示:
class ServiceAdapter(private val services: List<BluetoothGattService>
) : RecyclerView.Adapter<ServiceAdapter.ViewHolder>() {private var mOnItemClickListener: OnItemClickListener? = nullfun setOnItemClickListener(mOnItemClickListener: OnItemClickListener?) {this.mOnItemClickListener = mOnItemClickListener}override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {val viewHolder = ViewHolder(ItemServiceBinding.inflate(LayoutInflater.from(parent.context), parent, false))viewHolder.binding.itemService.setOnClickListener { mOnItemClickListener?.onItemClick(it, viewHolder.adapterPosition) }return viewHolder}override fun onBindViewHolder(holder: ViewHolder, position: Int) {holder.binding.tvServiceName.text = BleUtils.getServiceName(services[position].uuid)holder.binding.tvServiceUuid.text = BleUtils.getServiceUUID(services[position].uuid)}override fun getItemCount() = services.sizeclass ViewHolder(itemView: ItemServiceBinding) : RecyclerView.ViewHolder(itemView.root) {var binding: ItemServiceBindinginit {binding = itemView}}
}
这里的代码就是比较简单的,就是基本的写法,下面回到MainActivity中进行显示数据。
六、显示服务
首先声明变量:
private var mServiceAdapter: ServiceAdapter? = nullprivate val mServiceList: MutableList<BluetoothGattService> = mutableListOf()
然后实现OnItemClickListener 接口
class MainActivity : BaseActivity(), BleCallback, OnItemClickListener {
重写onItemClick()函数。
override fun onItemClick(view: View?, position: Int) {showMsg(mServiceList[position].uuid.toString())}
修改onServicesDiscovered()函数,代码如下所示:
override fun onServicesDiscovered(services: List<BluetoothGattService>) {runOnUiThread {mServiceList.clear()mServiceList.addAll(services)mServiceAdapter ?: run {mServiceAdapter = ServiceAdapter(mServiceList)binding.rvService.apply {(itemAnimator as SimpleItemAnimator).supportsChangeAnimations = falselayoutManager = LinearLayoutManager(this@MainActivity)adapter = mServiceAdapter}mServiceAdapter!!.setOnItemClickListener(this@MainActivity)mServiceAdapter}mServiceAdapter!!.notifyDataSetChanged()}}
这里的写法其实和扫描设备哪里如出一辙,下面我们运行一下看看,什么效果。

七、源码
如果对你有所帮助的话,不妨 Star 或 Fork,山高水长,后会有期~
源码地址:GoodBle
相关文章:
Android Ble蓝牙App(二)连接与发现服务
Ble蓝牙App(二)连接与发现服务 前言正文一、GATT回调二、连接和断连三、连接状态回调四、发现服务五、服务适配器六、显示服务七、源码 前言 在上一篇中我们进行扫描设备的处理,本文中进行连接和发现服务的数据处理,运行效果图如下…...
Android 自定义按钮(可滑动、点击)
按钮图片素材 https://download.csdn.net/download/Lan_Se_Tian_Ma/88151085 px 和 dp 转换工具类(Java) // px 和 dp 转换工具类 public class DensityUtil {/*** 根据手机的分辨率从 dip 的单位 转成为 px(像素)*/public static int dip2px(Conte…...
mac录屏怎么打开?很简单,让我来教你!
mac电脑作为一款广受欢迎的电脑系统,提供了多种方式来满足用户录屏的需求。无论您是要录制教学视频、制作演示文稿,还是记录游戏精彩瞬间,mac电脑都能帮助您实现这些目标。本文将为您介绍两种mac录屏的方法。通过本文的指导,您将能…...
Stable Diffusion AI绘画学习指南【插件安装设置】
插件安装的方式 可用列表方式安装,点开Extensions 选项卡,找到如下图,找到Available选项卡,点load from加载可用插件,在可用插件列表中找到要装的插件按install 按扭按装,安装完后(Apply and restart UI)应…...
APP开发中的性能优化:提升用户满意度的关键
APP开发中的性能优化是需要持续进行的,它不仅能够让用户体验到 APP的使用感受,还能在一定程度上提升用户的满意度,从而提升 APP的粘性和转化率。不过在实际开发中,很多 APP开发公司会存在性能优化上的问题,这就需要了解…...
Golang 切片 常用方法
文章目录 移除指定位置的元素查找元素的位置查找最大最小的元素去重随机打乱排序二维排序sort.Sort 排序 下面的方法省略一些校验,如数组越界等,且都采用泛型(要求go版本 > 1.18) 移除指定位置的元素 package mainimport ("fmt" )func Del…...
【Linux后端服务器开发】poll/epoll多路转接IO服务器
目录 一、poll原理 二、poll实现多路转接IO服务器 三、epoll函数接口 四、epoll的工作原理 五、epoll实现多路转接IO服务器 一、poll原理 poll函数接口 #include <poll.h> int poll(struct pollfd *fds, nfds_t nfds, int timeout);// pollfd结构 struct pollfd …...
【设计模式——学习笔记】23种设计模式——命令模式Command(原理讲解+应用场景介绍+案例介绍+Java代码实现)
文章目录 案例引入介绍基础介绍登场角色 案例实现案例一实现 案例二介绍实现拓展 命令模式在JdbcTemplate源码中的应用总结文章说明 案例引入 有一套智能家电,其中有照明灯、风扇、冰箱、洗衣机,这些智能家电来自不同的厂家,我们不想针对每一…...
Rust中的高吞吐量流处理
本篇文章主要介绍了Rust中流处理的概念、方法和优化。作者不仅介绍了流处理的基本概念以及Rust中常用的流处理库,还使用这些库实现了一个流处理程序。 最后,作者介绍了如何通过测量空闲和阻塞时间来优化流处理程序的性能,并将这些内容同步至…...
探索编程世界的宝藏:程序员必掌握的20大算法
文章目录 1 引言2 冒泡排序算法:编程世界的排序魔法 🧙♀️🔢3 选择排序算法:排序世界的精确挑选器 🎯🔢4 插入排序算法:排序世界的巧妙插珠者 ✨🔢5 快速排序算法:排序…...
Android NFC通信示例
前言 近距离无线通信 (NFC) 是一组近距离无线技术,通常只有在距离不超过 4 厘米时才能启动连接。借助 NFC,您可以在 NFC 标签与 Android 设备之间或者两台 Android 设备之间共享小型负载。 支持 NFC 的 Android 设备同时支持以下三种主要操作模式&…...
2023年08月IDE流行度最新排名
点击查看最新IDE流行度最新排名(每月更新) 2023年08月IDE流行度最新排名 顶级IDE排名是通过分析在谷歌上搜索IDE下载页面的频率而创建的 一个IDE被搜索的次数越多,这个IDE就被认为越受欢迎。原始数据来自谷歌Trends 如果您相信集体智慧&am…...
使用Beego和MySQL实现帖子和评论的应用,并进行接口测试(附源码和代码深度剖析)
文章目录 小项目介绍源码分析main.gorouter.gomodels/user.gomodels/Post.gomodels/comment.gocontrollers/post.gocontrollers/comment.go 接口测试测试增加帖子测试查看帖子测试增加评论测试查看评论 小项目介绍 经过对需求的分析,我增加了一些额外的东西&#x…...
物联网潜在的巨大价值在于大数据分析
物联网潜在的巨大价值在于大数据分析 从数据里去挖掘市场或者用户的精准需求。 往小的说,后台可以统计用户家里各各插座一年甚至更久的用电情况,这些数据也可以通过app或者小程序展现给用户。 用户可以很直观看到自己一年的用电情况,哪个家…...
SSL原理详解
SSL协议结构: SSL协议分为两层,下层为SSL记录协议,上层为SSL握手协议、SSL密码变化协议和SSL警告协议。 1.下层为SSL记录协议,主要作用是为高层协议提供基本的安全服务 建立在可靠的传输之上,负责对上层的数据进行分块…...
linux下的etc目录代表什么意思
在Linux系统中,/etc目录是一个非常重要的目录,它包含了系统的配置文件和相关的配置信息。下面是一些/etc目录中常见的文件和目录: 1. /etc/passwd:此文件包含了所有用户账户的信息,包括用户名、用户ID、用户所属的组I…...
iOS 两种方式设置状态栏
1、ios9.0以前设置状态栏字体颜色 ///白色 [[UIApplication sharedApplication]setStatusBarStyle:UIStatusBarStyleLightContent]; ///黑色 [[UIApplication sharedApplication]setStatusBarStyle:UIStatusBarStyleDefault]; 会看到如下提示: setStatusBarSty…...
html5:webSocket 基础使用
一、理解 HTML5 WebSocket HTML5 WebSocket是一种新型的网络协议,它能够在客户端和服务器之间建立实时的双向通信通道,使得浏览器和服务器之间的数据传输更加高效、快速和可靠。相比传统的HTTP协议,WebSocket协议使用更少的网络开销…...
html学习10-----总结(完)
<!DOCTYPE html> <html><head><meta charset"utf-8"/><title>html总结</title></head><body><h1>HTML总结</h1><br/><h2>文本格式化</h2><hr/><p><b>粗体文本<…...
Spring使用P命名空间实现注入数值信息-----Spring框架
<?xml version"1.0" encoding"UTF-8"?> <beans xmlns"http://www.springframework.org/schema/beans"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xmlns:p"http://www.springframework.org/schema/p"x…...
生成xcframework
打包 XCFramework 的方法 XCFramework 是苹果推出的一种多平台二进制分发格式,可以包含多个架构和平台的代码。打包 XCFramework 通常用于分发库或框架。 使用 Xcode 命令行工具打包 通过 xcodebuild 命令可以打包 XCFramework。确保项目已经配置好需要支持的平台…...
docker详细操作--未完待续
docker介绍 docker官网: Docker:加速容器应用程序开发 harbor官网:Harbor - Harbor 中文 使用docker加速器: Docker镜像极速下载服务 - 毫秒镜像 是什么 Docker 是一种开源的容器化平台,用于将应用程序及其依赖项(如库、运行时环…...
Admin.Net中的消息通信SignalR解释
定义集线器接口 IOnlineUserHub public interface IOnlineUserHub {/// 在线用户列表Task OnlineUserList(OnlineUserList context);/// 强制下线Task ForceOffline(object context);/// 发布站内消息Task PublicNotice(SysNotice context);/// 接收消息Task ReceiveMessage(…...
java调用dll出现unsatisfiedLinkError以及JNA和JNI的区别
UnsatisfiedLinkError 在对接硬件设备中,我们会遇到使用 java 调用 dll文件 的情况,此时大概率出现UnsatisfiedLinkError链接错误,原因可能有如下几种 类名错误包名错误方法名参数错误使用 JNI 协议调用,结果 dll 未实现 JNI 协…...
OkHttp 中实现断点续传 demo
在 OkHttp 中实现断点续传主要通过以下步骤完成,核心是利用 HTTP 协议的 Range 请求头指定下载范围: 实现原理 Range 请求头:向服务器请求文件的特定字节范围(如 Range: bytes1024-) 本地文件记录:保存已…...
数据链路层的主要功能是什么
数据链路层(OSI模型第2层)的核心功能是在相邻网络节点(如交换机、主机)间提供可靠的数据帧传输服务,主要职责包括: 🔑 核心功能详解: 帧封装与解封装 封装: 将网络层下发…...
从零实现STL哈希容器:unordered_map/unordered_set封装详解
本篇文章是对C学习的STL哈希容器自主实现部分的学习分享 希望也能为你带来些帮助~ 那咱们废话不多说,直接开始吧! 一、源码结构分析 1. SGISTL30实现剖析 // hash_set核心结构 template <class Value, class HashFcn, ...> class hash_set {ty…...
tree 树组件大数据卡顿问题优化
问题背景 项目中有用到树组件用来做文件目录,但是由于这个树组件的节点越来越多,导致页面在滚动这个树组件的时候浏览器就很容易卡死。这种问题基本上都是因为dom节点太多,导致的浏览器卡顿,这里很明显就需要用到虚拟列表的技术&…...
稳定币的深度剖析与展望
一、引言 在当今数字化浪潮席卷全球的时代,加密货币作为一种新兴的金融现象,正以前所未有的速度改变着我们对传统货币和金融体系的认知。然而,加密货币市场的高度波动性却成为了其广泛应用和普及的一大障碍。在这样的背景下,稳定…...
深入理解Optional:处理空指针异常
1. 使用Optional处理可能为空的集合 在Java开发中,集合判空是一个常见但容易出错的场景。传统方式虽然可行,但存在一些潜在问题: // 传统判空方式 if (!CollectionUtils.isEmpty(userInfoList)) {for (UserInfo userInfo : userInfoList) {…...
