Android BlueToothBLE入门(二)——设备的连接和通讯(附Demo源码地址)

news2024/11/21 0:16:34

学更好的别人,

做更好的自己。

——《微卡智享》

f98a7c1995354157b92adeec3f3f0d95.jpeg

本文长度为7870,预计阅读12分钟

前言

接《Android BlueToothBLE入门(一)——低功耗蓝牙介绍》上篇,这篇文章主要就是来做Demo实现Android两台设备的数据通讯。

265be150c55a258e1f6706ac4b187866.png

实现效果

a9419e281e36da4547eac0c7a83bfeab.jpeg

Android BLE Demo简介

256e157d1778e648caefe10bac58b2a3.png

微卡智享

01

目录及使用的组件

5b702abca0dd6934c392134519936e8c.png

整个Demo的目录上图中已经做了说明,其中最核心的是BlueToothBLEUtil类,这是把这个Demo中用到的BLE蓝牙方法都放到这里了,因为中心设备(Client)和外围设备(Server)统一用的这个程序,所以这个类里面中心设备和外围设备用到的都做了一个封装,当时还有不少要加的,后面会再补充。

Demo使用的MVI架构(Jeppack Compose还不会,所以用的viewBinding),像RecyclerView的适配器这块还是使用的BaseQuickAdapter,现在4.0在测试过程中了,所以我直接用的4.0beta版,蓝牙权限的申请采用了easypermissions,确实比自己写方便了许多。

build.gradle相关依赖项

09dcaad06b31cb7f24cc55c73f24c631.png

dependencies {
    'androidx.lifecycle:lifecycle-livedata-ktx:2.4.1'
    'androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.1'
    'androidx.lifecycle:lifecycle-runtime-ktx:2.4.1'
    //使用协程
    "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4"
    "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4"


    "io.github.cymchad:BaseRecyclerViewAdapterHelper:4.0.0-beta04"
    // 使用 Android X 的应用添加该依赖
    'pub.devrel:easypermissions:3.0.0'
}

02

蓝牙核心类BlueToothBLEUtil

外围设备和中心设备通讯,我们就用自己定义的服务即可,所以类中我们已经定义好常量来实现。

上一篇介绍过蓝牙技术联盟SIG定义UUID共用了一个基本的UUID:0x0000xxxx-0000-1000-8000-00805F9B34FB。总共128位,为了进一步简化基本UUID,每一个蓝牙技术联盟定义的属性有一个唯一的16位UUID,以代替上面的基本UUID的‘x’部分。使用16位的UUID便于记忆和操作。

所以类中我们定义的服务UUID只是中间xxxx四位即可,写了一个函数来直接生成对应的UUID

5bbfc6913293fc3e1a4747e010ab39b5.png

1489f97e1ade6e92d179a7221d9734e5.png

代码中使用BLE蓝牙相关Api时,Android Studio会经常提示要先判断是否有蓝牙权限,所以这里也是把蓝牙是否做过初始化,和判断是否有相关的蓝牙权限写了一个函数调用

1d1ee7d358d67e675fcc1edbe7d1e755.png

蓝牙权限

9befaca7fc6d9872dbdac39d75a078d2.png

检测是否有相关权限

231d91049d1188143156cd4a89b930cc.png

调用蓝牙API时先检测是否有对应的权限

像扫描设备,连接设备时需要知道返回的结果,用到了回调,那类中直接就是传入相磁的CallBack回调函数,在UI界面写回调函数即可。如下面这个扫描蓝牙设备函数

f61a3c4d0586d22469834111bb540efb.png

参数为ScanCallback

3c54c36d841f4df2168248b6466b4924.png

ScanFragment中定义ScanCallback,实现onScanResult中发送意图

5c532a8aa4f6ef4c26470a42a0757f13.png

点击扫描设备直接调用类中函数并传入回调函数

BlueToothBLEUtil源码

package vac.test.bluetoothbledemo.repository


import android.Manifest
import android.app.Application
import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothDevice
import android.bluetooth.BluetoothGatt
import android.bluetooth.BluetoothGattCallback
import android.bluetooth.BluetoothGattCharacteristic
import android.bluetooth.BluetoothGattDescriptor
import android.bluetooth.BluetoothGattServer
import android.bluetooth.BluetoothGattServerCallback
import android.bluetooth.BluetoothGattService
import android.bluetooth.BluetoothManager
import android.bluetooth.le.AdvertiseCallback
import android.bluetooth.le.AdvertiseData
import android.bluetooth.le.AdvertiseSettings
import android.bluetooth.le.BluetoothLeAdvertiser
import android.bluetooth.le.ScanCallback
import android.bluetooth.le.ScanFilter
import android.bluetooth.le.ScanSettings
import android.content.Context
import android.content.pm.PackageManager
import android.os.Build
import android.os.ParcelUuid
import android.util.Log
import android.widget.Toast
import androidx.core.app.ActivityCompat
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import pub.devrel.easypermissions.AfterPermissionGranted
import pub.devrel.easypermissions.EasyPermissions
import vac.test.bluetoothbledemo.BaseApp
import vac.test.bluetoothbledemo.EncodeUtil
import vac.test.bluetoothbledemo.bytesToHexString
import vac.test.bluetoothbledemo.ui.MainActivity
import java.io.IOException
import java.util.UUID


object BlueToothBLEUtil {
    //服务 UUID
    const val BLESERVER = "2603"


    //特征 UUID
    const val BLECHARACTERISTIC = "ca01"


    //描述 UUID
    const val BLEDESCRIPTOR = "da01"


    //蓝牙相关权限
    const val REQUEST_CODE_PERMISSIONS = 10
    val REQUIRED_BLEPERMISSIONS = arrayOf(
        Manifest.permission.BLUETOOTH, Manifest.permission.BLUETOOTH_ADMIN,
        Manifest.permission.BLUETOOTH_CONNECT, Manifest.permission.BLUETOOTH_SCAN,
        Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.BLUETOOTH_ADVERTISE
    )




    private var mBluetoothManager: BluetoothManager? = null
    private var mBluetoothAdapter: BluetoothAdapter? = null
    private var mBluetoothGattService: BluetoothGattService? = null
    private var mBluetoothGattServer: BluetoothGattServer? = null
    private var mBluetoothDevice: BluetoothDevice? = null
    private var mBluetoothGatt: BluetoothGatt? = null


    //BLE广播操作类
    private var mBluetoothLeAdvertiser: BluetoothLeAdvertiser? = null


    //是否初始化
    var hasInit = false


    lateinit var mApplication: Application


    //检测蓝牙权限
    fun checkBlueToothPermission(permissions: String = ""): Boolean {
        if (!hasInit) throw IOException("未初始化蓝牙BlueTooth!")
        if (permissions == "") return true
        return ActivityCompat.checkSelfPermission(
            mApplication.applicationContext,
            permissions
        ) == PackageManager.PERMISSION_GRANTED
    }




    fun init(application: Application): Boolean {
        if (hasInit) return true
        mApplication = application


        //初始化ble设配器
        mBluetoothManager =
            mApplication.applicationContext.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
        //初始化适配器
        mBluetoothAdapter = mBluetoothManager!!.adapter


        hasInit = true
        return hasInit
    }


    fun destory() {
        mBluetoothGatt = null
        mBluetoothDevice = null
        mBluetoothGattService = null
        mBluetoothAdapter = null
        hasInit = false
    }


    //获取UUID
    /*
    蓝牙技术联盟SIG定义UUID共用了一个基本的UUID:0x0000xxxx-0000-1000-8000-00805F9B34FB。
    总共128位,为了进一步简化基本UUID,每一个蓝牙技术联盟定义的属性有一个唯一的16位UUID,
    以代替上面的基本UUID的‘x’部分。使用16位的UUID便于记忆和操作
     */
    fun getUUID(baseuuid: String): UUID {
        return UUID.fromString("0000${baseuuid}-0000-1000-8000-00805f9b34fb")
    }


    //广播时间(设置为0则持续广播)
    val Time = 0


    //是否在扫描中
    private var mScanning: Boolean = false


    //获取BluetoothManager
    fun getBluetoothManager(): BluetoothManager? {
        return if (checkBlueToothPermission()) {
            mBluetoothManager
        } else {
            null
        }
    }


    //获取BluetoothAdapter
    fun getBluetoothAdapter(): BluetoothAdapter? {
        return if (checkBlueToothPermission()) {
            mBluetoothAdapter
        } else {
            null
        }
    }


    //region 服务端外围设备相关函数
    /**
     * 添加Gatt 服务和特征
     * 广播是广播,只有添加Gatt服务和特征后,连接才有服务和特征用于数据交换
     */
    //获取Gatt服务
    fun getGattService(): BluetoothGattService {
        //初始化Service
        //创建服务,并初始化服务的UUID和服务类型。
        //BluetoothGattService.SERVICE_TYPE_PRIMARY 为主要服务类型
        val mGattService = BluetoothGattService(
            getUUID(BLESERVER),
            BluetoothGattService.SERVICE_TYPE_PRIMARY
        )


        //初始化特征(添加读写权限)
        //在服务端配置特征时,设置BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE,
        //那么onCharacteristicWriteRequest()回调时,不需要GattServer进行response才能进行响应。
        val mGattCharacteristic = BluetoothGattCharacteristic(
            getUUID(BLECHARACTERISTIC),
            BluetoothGattCharacteristic.PROPERTY_WRITE or
                    BluetoothGattCharacteristic.PROPERTY_NOTIFY or
                    BluetoothGattCharacteristic.PROPERTY_READ,
            (BluetoothGattCharacteristic.PERMISSION_WRITE or BluetoothGattCharacteristic.PERMISSION_READ)
        )


        //初始化描述
        val mGattDescriptor = BluetoothGattDescriptor(
            getUUID(BLEDESCRIPTOR),
            BluetoothGattDescriptor.PERMISSION_READ or BluetoothGattDescriptor.PERMISSION_WRITE
        )


        //Service添加特征值
        mGattService.addCharacteristic(mGattCharacteristic)


        //特征值添加描述
        mGattCharacteristic.addDescriptor(mGattDescriptor)


        return mGattService
    }


    //添加服务
    fun addGattServer(mGattServerCallback: BluetoothGattServerCallback) {
        if (checkBlueToothPermission(Manifest.permission.BLUETOOTH_CONNECT)) {
            mBluetoothGattService = getGattService()
            mBluetoothGattServer =
                mBluetoothManager!!.openGattServer(
                    mApplication.applicationContext, mGattServerCallback
                )


            mBluetoothGattServer!!.addService(mBluetoothGattService)
        }
    }


    //开启广播
    //官网建议获取mBluetoothLeAdvertiser时,先做mBluetoothAdapter.isMultipleAdvertisementSupported判断,
    // 但部分华为手机支持Ble广播却还是返回false,所以最后以mBluetoothLeAdvertiser是否不为空且蓝牙打开为准
    fun startAdvertising(phonename: String, mAdvertiseCallback: AdvertiseCallback): Boolean {
        if (checkBlueToothPermission(Manifest.permission.BLUETOOTH_CONNECT)) {
            mBluetoothAdapter!!.name = phonename
            mBluetoothLeAdvertiser = mBluetoothAdapter!!.bluetoothLeAdvertiser
            //蓝牙关闭或者不支持
            return if (mBluetoothLeAdvertiser != null && mBluetoothAdapter!!.isEnabled) {
                Log.d(
                    "pkg", "mBluetoothLeAdvertiser != null = ${mBluetoothLeAdvertiser != null} " +
                            "mBluetoothAdapter.isMultipleAdvertisementSupported = ${mBluetoothAdapter!!.isMultipleAdvertisementSupported}"
                )
                //开始广播(不附带扫描响应报文)
                mBluetoothLeAdvertiser?.startAdvertising(
                    getAdvertiseSettings(),
                    getAdvertiseData(), mAdvertiseCallback
                )
                true
            } else {
                false
            }
        } else {
            return false
        }
    }


    //关闭蓝牙广播
    fun stopAdvertising(mAdvertiseCallback: AdvertiseCallback): Boolean {
        if (checkBlueToothPermission(Manifest.permission.BLUETOOTH_ADVERTISE)) {
            mBluetoothLeAdvertiser?.let { advertiser ->
                advertiser.stopAdvertising(mAdvertiseCallback)
            }
            return true
        } else {
            return false
        }
    }
    //endregion




    fun scanBlueToothDevice(scancallback: ScanCallback) {
        if (mScanning) return
        if (checkBlueToothPermission(Manifest.permission.BLUETOOTH_SCAN)) {
            //扫描设置
            /**
             * 三种模式
             * - SCAN_MODE_LOW_POWER : 低功耗模式,默认此模式,如果应用不在前台,则强制此模式
             * - SCAN_MODE_BALANCED :平衡模式,一定频率下返回结果
             * - SCAN_MODE_LOW_LATENCY 高功耗模式,建议应用在前台才使用此模式
             */
            val builder = ScanSettings.Builder()
                .setScanMode(ScanSettings.SCAN_MODE_LOW_POWER)


            /**
             * 三种回调模式
             * - CALLBACK_TYPE_ALL_MATCHED : 寻找符合过滤条件的广播,如果没有,则返回全部广播
             * - CALLBACK_TYPE_FIRST_MATCH : 仅筛选匹配第一个广播包出发结果回调的
             * - CALLBACK_TYPE_MATCH_LOST : 这个看英文文档吧,不满足第一个条件的时候,不好解释
             */
            builder.setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES)


            //判断手机蓝牙芯片是否支持皮批处理扫描
            if (mBluetoothAdapter!!.isOffloadedFilteringSupported) {
                builder.setReportDelay(0L)
            }
            mScanning = true
            //3秒后关闭
            CoroutineScope(Dispatchers.IO).launch {
                delay(6000)
                stopScanBlueToothDevice(scancallback)
                Log.i("bluetooth", "关闭扫描")
            }


            //过滤掉不是自己程序发送的广播
            val filter = getScanFilter()
            mBluetoothAdapter!!.bluetoothLeScanner?.startScan(filter, builder.build(), scancallback)
            //过滤特定的 UUID 设备
            //bluetoothAdapter?.bluetoothLeScanner?.startScan()
        }
    }


    fun stopScanBlueToothDevice(scancallback: ScanCallback) {
        //连接时要先关闭扫描
        if (mScanning) {
            if (checkBlueToothPermission(Manifest.permission.BLUETOOTH_SCAN)) {
                mBluetoothAdapter!!.bluetoothLeScanner?.stopScan(scancallback)
                mScanning = false
            }
        }
    }


    //初始化广播设置
    fun getAdvertiseSettings(): AdvertiseSettings {
        //初始化广播设置
        return AdvertiseSettings.Builder()
            //设置广播模式,以控制广播的功率和延迟。ADVERTISE_MODE_LOW_LATENCY为高功率,低延迟
            .setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_LOW_POWER)
            //设置蓝牙广播发射功率级别
            .setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_LOW)
            //广播时限。最多180000毫秒。值为0将禁用时间限制。(不设置则为无限广播时长)
            .setTimeout(Time)
            //设置广告类型是可连接还是不可连接。
            .setConnectable(true)
            .build()
    }


    //设置广播报文
    fun getAdvertiseData(): AdvertiseData {
        return AdvertiseData.Builder()
            //设置广播包中是否包含设备名称。
            .setIncludeDeviceName(true)
            //设置广播包中是否包含发射功率
            .setIncludeTxPowerLevel(true)
            //设置UUID
            .addServiceUuid(ParcelUuid(getUUID(BLESERVER)))
            .build()
    }




    //设置扫描过滤
    fun getScanFilter(): ArrayList<ScanFilter> {
        val scanFilterList = ArrayList<ScanFilter>()
        val builder = ScanFilter.Builder()
        builder.setServiceUuid(ParcelUuid(getUUID(BLESERVER)))
        scanFilterList.add(builder.build())
        return scanFilterList
    }




    //获取原生蓝牙对象
    fun getBlueToothDevice(macAddress: String): BluetoothDevice? {
        return if (checkBlueToothPermission()) {
            mBluetoothDevice = mBluetoothAdapter!!.getRemoteDevice(macAddress)
            if (mBluetoothDevice == null) throw IOException("获取不到BluetoothDevice")
            mBluetoothDevice!!
        } else {
            null
        }
    }


    //申请通讯字节长度
    fun requestMTP(size: Int = 512): Boolean {
        return if (checkBlueToothPermission()) {
            mBluetoothGatt?.let {
                it.requestMtu(size)
            } ?: false
        } else {
            false
        }
    }


    //连接蓝牙Gatt
    fun connect(macAddress: String, callback: BluetoothGattCallback): BluetoothGatt? {
        return if (checkBlueToothPermission(Manifest.permission.BLUETOOTH_CONNECT)) {
            if (mBluetoothDevice == null)
                getBlueToothDevice(macAddress)


            mBluetoothGatt =
                mBluetoothDevice!!.connectGatt(mApplication.applicationContext, false, callback)
            mBluetoothGatt
        } else {
            null
        }
    }


    //断开蓝牙Gatt
    fun disConnect() {
        if (checkBlueToothPermission(Manifest.permission.BLUETOOTH_CONNECT)) {
            mBluetoothGatt?.let {
                it.disconnect()
                //调用close()后,连接时传入callback会被置空,无法得到断开连接时onConnectionStateChange()回调
                it.close()
            }
        }
    }


    //获取蓝牙GattService
    fun getBlueToothGattService(gatt: BluetoothGatt): List<BluetoothGattService> {
        return gatt.services
    }


    //发送Characteristic
    fun writeCharacteristic(
        srvuuid: String,
        charuuid: String,
        byteArray: ByteArray
    ) {
        if (checkBlueToothPermission(Manifest.permission.BLUETOOTH_CONNECT)) {
            mBluetoothGatt?.let {
                val characteristic =
                    it.getService(getUUID(srvuuid)).getCharacteristic(getUUID(charuuid))
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
                    it.writeCharacteristic(
                        characteristic,
                        byteArray,
                        BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE
                    )
                } else {
                    characteristic.setValue(byteArray)
                    it.writeCharacteristic(characteristic)
                }
            } ?: {
                throw IOException("mBluetoothGatt为空")
            }
        }
    }


    //发送Characteristic
    fun writeCharacteristic(
        characteristic: BluetoothGattCharacteristic,
        byteArray: ByteArray
    ) {
        if (checkBlueToothPermission(Manifest.permission.BLUETOOTH_CONNECT)) {
//        var hexstr = byteArrsyToHexString(byteArray)
//        var transbytes = hexstr!!.toByteArray()


            mBluetoothGatt?.let {
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
                    it.writeCharacteristic(
                        characteristic,
                        byteArray,
                        BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE
                    )
                } else {
                    characteristic.setValue(byteArray)
                    it.writeCharacteristic(characteristic)
                }
            } ?: {
                throw IOException("mBluetoothGatt为空")
            }
        }
    }


    fun readCharacteristic(characteristic: BluetoothGattCharacteristic): ByteArray? {
        return if (checkBlueToothPermission(Manifest.permission.BLUETOOTH_CONNECT)) {
            var byteArray: ByteArray? = null
            mBluetoothGatt?.let {
                it.readCharacteristic(characteristic)
                byteArray = characteristic.value
            }
            byteArray
        } else {
            null
        }
    }


    //发送返回值sendResponse
    fun sendResponse(
        device: BluetoothDevice, requestId: Int, offset: Int, value: ByteArray
    ) {
        if (checkBlueToothPermission(Manifest.permission.BLUETOOTH_CONNECT)) {
            mBluetoothGattServer!!.sendResponse(
                device, requestId, BluetoothGatt.GATT_SUCCESS,
                offset, value
            )
        }
    }


    fun setCharacteristicNotify(characteristic: BluetoothGattCharacteristic, bool: Boolean) {
        if (checkBlueToothPermission(Manifest.permission.BLUETOOTH_CONNECT)) {
            mBluetoothGatt?.let {
                it.setCharacteristicNotification(characteristic, bool)
            }
        }
    }


    fun notifyCharacteristicChanged(
        device: BluetoothDevice,
        characteristic: BluetoothGattCharacteristic,
        byteArray: ByteArray
    ) {
        if (checkBlueToothPermission(Manifest.permission.BLUETOOTH_CONNECT)) {
            //回复客户端,让客户端读取该特征新赋予的值,获取由服务端发送的数据
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
                mBluetoothGattServer!!.notifyCharacteristicChanged(
                    device,
                    characteristic,
                    false,
                    byteArray
                )
            } else {
                characteristic.value = byteArray
                mBluetoothGattServer!!.notifyCharacteristicChanged(device, characteristic, false)
            }
        }
    }




}

03

适配器BaseQuickAdapter

4.0版本的BaseQuickAdapter,里面的ViewHolder要自己定义,用法和原来有点不太一样

6ccfbcf7337f99f3db16804e18e242f6.png

41bc85769100e1b49234d69a87e2caa2.png

还有原来我用BaseQuickAdapter中直接用的二级列表,当时也是会有问题,具体问题可以看《Android BaseQuickAdapter3.0.4版本二级列表的使用及遇到的问题》,正好这次服务的列表刷新中又需要实现二级列表,现在我是改为自定义添加了,同样绑定了viewBinding。

40fdcf34d78bbe061f6cd00cb4f5b79e.png

68ca2469271136e75b01b846e913a722.png

04

Fragment中使用ViewBinding注意事项

在Fragment中使用viewBinding,为了防止内存泄漏,Google有标准的写法,不过每个Fragment都这样写比较麻烦,所以这里定义了一个BaseFragment,用于处理viewBinding内存泄露问题。

abstract class BaseFragment<T : ViewBinding> : Fragment() {


    private var _binding: T? = null
    protected val binding: T get() = _binding!!


    abstract val bindingInflater: (LayoutInflater, ViewGroup?, Bundle?) -> T


    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        _binding = bindingInflater.invoke(inflater, container, savedInstanceState)
        return binding.root
    }


    override fun onDestroyView() {
        super.onDestroyView()
        _binding = null
    }
}

aaff3b28a61a7b999318d1a01ec83946.png

这样一个基本的蓝牙Ble通讯就完成了。

后续问题

上面的视频中通讯传输是没问题,但是如果发送大点的数据,就不行了,蓝牙BLE发送数据默认单次最大传输20个byte,如果是一般的协议命令,如:开关灯、前进左右等等,是没有问题的,如果是需要发送如:图片、BIN文档、音乐等大数据量的文件,则需要做数据的处理。

基本说考虑到蓝牙发送大数据量时应该通过两个途径结合实现:

  1. 申请修改MTU值,MTU: 最大传输单元(MAXIMUM TRANSMISSION UNIT)

  2. 分包数据发送

简单的通讯Demo实现后,接下来就准备开始研究分包通讯的问题了。

源码地址

https://github.com/Vaccae/AndroidBLEDemo.git

点击原文链接可以看到“码云”的源码地址

b555b12c37de5bcf8a19d835eb445af1.png

f17be96a3f171ddee3a8d5048af22020.png

往期精彩回顾

 

51110eba9a28ec644b8f640c1c437822.jpeg

Android BlueToothBLE入门(一)——低功耗蓝牙介绍

 

 

9132f2df2517286d7a36a8a289f0a1c6.jpeg

Android监听消息(二)——电话及短信监听

 

 

557e546de405bbff90b2fe4225d807f9.jpeg

Android监听消息(一)——应用消息捕获

 

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

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

相关文章

chatgpt赋能python:Python如何快速处理数据

Python如何快速处理数据 在当今数据爆炸的时代&#xff0c;数据处理已经成为一项非常重要的任务。因此&#xff0c;如何快速、高效地处理数据就成为了每个数据科学家、数据工程师以及数据分析师的必备技能之一。而Python正是其中的佼佼者。 为什么选择Python进行数据处理 Py…

Spring事物失效的八大场景

1.方法内的自调用&#xff1a;spring事物是基于aop的&#xff0c;只要使用代理对象调用某个方法时&#xff0c;spring事物才能生效&#xff0c;而在一个方法内使用this.xxx()时。this并不是代理对象&#xff0c;所以会失效&#xff08;实际上是transaction注解失效&#xff09;…

用程序控制对文本的复制和粘贴pyperclip模块

【小白从小学Python、C、Java】 【等级考试500强双证书考研】 【Python-数据分析】 用程序控制对文本的复制和粘贴 pyperclip模块 选择题 关于下列代码说法错误的是&#xff1f; import pyperclip print(【执行】pyperclip.copy("Python 太强大了&#xff01;")) p…

读发布!设计与部署稳定的分布式系统(第2版)笔记01_生产环境的生存法则

1. 系统“应该”做什么 1.1. 添加所需特性 2. 系统“不应该”做什么 2.1. 崩溃 2.2. 停止响应 2.3. 丢失数据 2.4. 侵犯隐私 2.5. 损失金钱 2.6. 摧毁公司 2.7. “杀死”客户 3. QA部门的测试 3.1. 团队的大部分工作是想方设法地通过测试 3.2. 做了敏捷、务实和自动…

【设计模式与范式:行为型】57 | 观察者模式(下):如何实现一个异步非阻塞的EventBus框架?

上一节课中&#xff0c;我们学习了观察者模式的原理、实现、应用场景&#xff0c;重点介绍了不同应用场景下&#xff0c;几种不同的实现方式&#xff0c;包括&#xff1a;同步阻塞、异步非阻塞、进程内、进程间的实现方式。 同步阻塞是最经典的实现方式&#xff0c;主要是为了…

GreenPlum分布式集群部署实战

&#x1f4e2;&#x1f4e2;&#x1f4e2;&#x1f4e3;&#x1f4e3;&#x1f4e3; 哈喽&#xff01;大家好&#xff0c;我是【IT邦德】&#xff0c;江湖人称jeames007&#xff0c;10余年DBA及大数据工作经验 一位上进心十足的【大数据领域博主】&#xff01;&#x1f61c;&am…

GC演变过程、三色标记法、大白话讲解G1

文章目录 GC演变过程并发垃圾回收需要解决的问题怎么确定一个垃圾?并发收集存在的问题 三色标记法CMS垃圾收集器G1垃圾收集器介绍,主要特点优点使用注意点 GC演变过程 在Java中,垃圾收集一直是一个非常重要的组成部分, 到目前为止,垃圾收集器已经有十种了, 在不停的优化. 那为…

GoogleTest之Actions的用法

目录 返回值Actions的组合验证复杂参数mock副作用改变mock对象的行为设置返回类型的默认值使用自定义函数作为Actions 通用示例 namespace mock_action { class Foo { public:virtual ~Foo() {}virtual int& GetBar() 0; // 1virtual int GetPointerValue() 0; //…

Linux CentOS7虚拟机配置静态IP并允许上网的配置方法

文章目录 前言一、开启本地电脑VMnet8二、Linux配置静态IP1. NAT模式设置2. 开启虚拟机登录root用户3. 执行命令设置静态IP4. 重启网卡① 重启网卡 (正常)② 重启网卡 (异常)③ 解决方式&#xff1a;禁用NetworkManager 5. 查看ip6. 本地电脑cmd窗口ping虚拟机7. 虚拟机ping本地…

Golang每日一练(leetDay0095) 第一个错误的版本、完全平方数

目录 278. 第一个错误的版本 First Bad Version &#x1f31f; 279. 完全平方数 Perfect Squares &#x1f31f;&#x1f31f; &#x1f31f; 每日一练刷题专栏 &#x1f31f; Rust每日一练 专栏 Golang每日一练 专栏 Python每日一练 专栏 C/C每日一练 专栏 Java每日…

springboot的数据访问和数据视图

当使用 Spring Boot 进行数据访问时&#xff0c;我们可以选择使用 MyBatis 或 JPA&#xff08;Java Persistence API&#xff09;来实现增删改查操作。下面我将分别给出使用这两种方式整合数据访问的详细步骤和示例&#xff0c;同时结合 Thymeleaf 实现数据展现。 方式一: 使用…

AI实战营:语义分割与MMSegmentation

目录 OpenMMLab图像分割算法库MMSegmentation 深度学习下的语义分割模型 全卷积网络Fully Convolutional Network 201 ​编辑 上下文信息与PSPNet模型 空洞卷积与DeepLab模型 语义分割算法总结 语义分割 前沿算法 SegFormer K-Net MaskFormer Mask2Former Seg…

PySide2 or PyQt5???该如何抉择???

1. 区别 Qt库里面有非常强大的图形界面开发库&#xff0c;但是Qt库是C语言开发的&#xff0c;PySide2、PyQt5可以让我们通过Python语言使用Qt。 但是 PySide2、PyQt5 这两者有什么区别呢&#xff1f; 可以形象地这样说&#xff1a; PySide2 是Qt的 亲儿子 &#xff0c; PyQt5 …

面向对象程序设计|静态友元

题目一&#xff1a;复数运算 题目描述&#xff1a; 复数类的声明如下 要求如下&#xff1a; 1. 实现复数类和友元函数addCom和outCom&#xff1b; 2. 参考addCom函数为复数类增加一个友元函数minusCom&#xff0c;用于实现两个复数的减法&#xff1b; 3. 在main函数中&…

待办事项JS:DHTMLX To Do List 1.2 cRACK

DHTMLX To Do List用于有效任务管理的DHTMLX JavaScript 待办事项列表 使用 JavaScript/HTML 5 中的待办事项列表来管理您的任务并确定其优先级。将组件连接到 DHTMLX 甘特图&#xff0c;并允许用户以简单直观的方式快速组织他们的业务流程。 DHTMLX JavaScript 待办事项列表的…

chatgpt赋能python:Python建模块最佳实践

Python建模块最佳实践 Python是一种灵活、易于使用的编程语言&#xff0c;因其强大的模块化支持和丰富的第三方模块而备受推崇。本文将介绍Python建模块的最佳实践&#xff0c;以便帮助开发人员创建可重用、可维护和易于测试的Python模块。 基本概念 在Python中&#xff0c;…

创业很长时间以后…

创业过很长时间以后…综合能力是有滴 创业和打工后的思维习惯 为了效率&#xff0c;一般情况是这样滴 趣讲大白话&#xff1a;区别还是有滴 【趣讲信息科技195期】 **************************** 创业还是很难滴 每年成立很多新公司 有很多公司关门 公司平均生存时间&#xff1…

右值引用以及move移动语义和forward 完美转发

右值引用 右值引用最简单的作用&#xff1a;可以避免无谓的复制&#xff0c;提高了程序性能&#xff08;在移动构造函数中有体现&#xff09;。 什么是右值 最基本的解释&#xff1a; 左值可以取地址、位于等号左边&#xff1b; 右值没法取地址&#xff0c;位于等号右边。&…

UnityVR--组件9--VideoPlayerAudioSource

目录 前言 视频组件VideoPlayer参数解释 RenderMode渲染方式 VideoPlayer类中的API 音频组件AudioSource参数解释 AudioSource类中的常见API&简单应用 前言 在之前的VR场景中已经使用过VideoPlayer播放视频&#xff08;Unity.UI的交互&#xff08;6&#xff09;-播放…

chatgpt赋能python:Python怎么快速读取一组图片的RGB值?

Python怎么快速读取一组图片的RGB值&#xff1f; 简介 Python是一种非常流行的程序设计语言&#xff0c;它具有易于学习、简洁明了的语法和强大的功能。Python被广泛应用于数据分析、人工智能、科学计算、Web开发、游戏开发等领域。在这篇文章中&#xff0c;我们将介绍如何使…