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

这篇具有很好参考价值的文章主要介绍了Android BlueToothBLE入门(二)——设备的连接和通讯(附Demo源码地址)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

学更好的别人,

做更好的自己。

——《微卡智享》

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

前言

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

android 蓝牙连接方式,android,android studio,ide

实现效果

android 蓝牙连接方式,android,android studio,ide

Android BLE Demo简介

android 蓝牙连接方式,android,android studio,ide

微卡智享

01

目录及使用的组件

android 蓝牙连接方式,android,android studio,ide

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

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

build.gradle相关依赖项

android 蓝牙连接方式,android,android studio,ide

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

android 蓝牙连接方式,android,android studio,ide

android 蓝牙连接方式,android,android studio,ide

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

android 蓝牙连接方式,android,android studio,ide

蓝牙权限

android 蓝牙连接方式,android,android studio,ide

检测是否有相关权限

android 蓝牙连接方式,android,android studio,ide

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

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

android 蓝牙连接方式,android,android studio,ide

参数为ScanCallback

android 蓝牙连接方式,android,android studio,ide

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

android 蓝牙连接方式,android,android studio,ide

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

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要自己定义,用法和原来有点不太一样

android 蓝牙连接方式,android,android studio,ide

android 蓝牙连接方式,android,android studio,ide

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

android 蓝牙连接方式,android,android studio,ide

android 蓝牙连接方式,android,android studio,ide

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
    }
}

android 蓝牙连接方式,android,android studio,ide

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

后续问题

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

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

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

  2. 分包数据发送

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

源码地址

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

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

android 蓝牙连接方式,android,android studio,ide

android 蓝牙连接方式,android,android studio,ide

往期精彩回顾

 文章来源地址https://www.toymoban.com/news/detail-675103.html

android 蓝牙连接方式,android,android studio,ide

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

 

 

android 蓝牙连接方式,android,android studio,ide

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

 

 

android 蓝牙连接方式,android,android studio,ide

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

 

到了这里,关于Android BlueToothBLE入门(二)——设备的连接和通讯(附Demo源码地址)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 即时通讯独立系统源码包含Android 、iOS、PC

    demo软件园每日更新资源,请看到最后就能获取你想要的: 1.经典版哇呼星聊即时通讯独立系统源码 包含Android 、iOS、PC 自带教程 哇呼星聊即时通讯系统源码 Android+iOS+PC三端 附教程 服务器最低配置4H4G 这套安装跟shiku一样 1.安装宝塔,只安装Nginx,其他不用安装,不用创建站

    2024年02月01日
    浏览(37)
  • 使用网络adb连接android设备

    1.系统默认开启网络adb 1.1 打开cmd管理员,输入adb connect IP,例如:adb connect 192.168.4.39 1.2 返回connected to 192.168.4.39:5555 成功连接 ,可进行其它adb 指令操作,  如连接失败有些设备需要在设置中开启网络adb开关,一般是在开发者选项中。 1.3 断开adb连接 在cmd窗口中输入adb discon

    2024年02月15日
    浏览(41)
  • Modbus的常见问题解答:多台设备如何连接?为什么要加终端电阻?RS485总线可挂接多少个设备?在RS485通讯中,最大传输距离是多少?

    多台RS485设备如何连接呢? 使用屏蔽双绞线,采用手拉手菊花链式拓扑结构将网关和各串行设备节点连接起来,并在网络起始端和末尾端设备的RS485+和RS485-之间各并接一个120Ω电阻以减少信号在两端的反射。 什么情况下在RS485总线上要增加终端电阻? RS485总线随着传输距离的

    2024年02月10日
    浏览(68)
  • Android连接蓝牙设备问题(android.permission.BLUETOOTH)

            近期遇到一个问题,之前发布的APP连接蓝牙都是正常的,现在有人反映连不上了。经过测试发现:android 12 和 harmonyOS 3.0.0 都会有这个问题,而之前的版本就不会有这个。         经过网上一番查找,原来是因为最近Google发布的Android 12,新引入了 BLUETOOTH_SCAN、

    2024年01月16日
    浏览(45)
  • 韦东山Linux驱动入门实验班(2)hello驱动---驱动层与应用层通讯,以及自动产生设备节点

    (1)学习韦东山老师的Linux,因为他讲的很精简,以至于很多人听不懂。接下来我讲介绍韦东山老师的驱动实验班的第二个Hello程序。 (2)注意,请先学习完视频再来看这个教程!本文仅供入门学习!如需深入,请搜索其他博客! (3)因为上一个教程已经讲的很详细了,所

    2024年02月05日
    浏览(48)
  • Android设备与Mac的连接方法

    Android设备与Mac的连接方法 在现代科技的发展下,Android设备和Mac电脑的连接成为了一种常见需求。本文将介绍几种常见的方法来实现Android设备与Mac之间的连接,包括USB连接、Wi-Fi连接以及蓝牙连接,并附上相应的源代码。 一、USB连接 USB连接是最直接和稳定的方式之一,可以

    2024年02月07日
    浏览(33)
  • android studio通过wifi、无线连接设备

    AndroidStudio无线wifi调试设备_android studio wifi_zwylovemzj的博客-CSDN博客 使用​​adbWireless​​工具,其能够让手机用无线来取代USB连接而使用ADB工具 1. 手机需要与电脑在同一局域网内 2. 把adbWireless安装到手机上,并开启,上面会显示一个IP地址 3. 在Android studio的Terminal里面执行​​

    2024年02月09日
    浏览(49)
  • 通过adb 连接多台Android 设备时的操作

    通过adb 连接 多台 android设备时,执行指令的时候,会报出超出一台的连接设备的错误提示,这个时候只能adb disconnect,关闭所有,然后再连接指定的设备; 所以不论是有线多台设备还是无线多台设备或是混合多设备,应该怎么控制操作呢? 看一下3个的命令 1、adb devices 查看所有连接设

    2024年01月23日
    浏览(41)
  • android@adb连接电脑和android设备@安装和卸载@清除app数据

    Android 调试桥 (adb) | Android 开发者 | Android Developers android设备端设置和准备 USB选项: 选择 文件传输 而不是仅充电(只会导致文件传输等功能无法生效) 典型错误: more than one device/emulator 打开 开发者选项 (developerOptions) 启用USB调试(USB debugging) Switche to debugging mode when USB is connected 允

    2024年02月04日
    浏览(55)
  • “List of Devices Attached“:Android设备连接问题解析

    大家好,我是免费搭建查券返利机器人赚佣金就用微赚淘客系统3.0的小编,也是冬天不穿秋裤,天冷也要风度的程序猿!今天,我们将聚焦于一个在Android开发和移动设备管理中经常遇到的问题,那就是\\\"list of devices attached\\\"。让我们一起深入了解这个话题,了解其中的奥秘和解

    2024年03月13日
    浏览(48)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包