Android Bluetooth使用


title: Android Bluetooth
date: 2019-11-20
tag: Android
category: Android


蓝牙

传统蓝牙:比较耗电

低功耗蓝牙:

从蓝牙4.0开始包含两个蓝牙芯片模块:传统/经典蓝牙模块(Classic Bluetooth,简称BT)和低功耗蓝牙(Bluetooth Low Energy,简称BLE)

经典蓝牙是在之前的蓝牙1.0,1.2,2.0+EDR,2.1+EDR,3.0+EDR等基础上发展和完善起来的, 而低功耗蓝牙是Nokia的Wibree标准上发展起来的,是完全不同两个标准。

  1. 经典蓝牙模块(BT)

泛指蓝牙4.0以下的模块,一般用于数据量比较大的传输,如:语音、音乐、较高数据量传输等。

经典蓝牙模块可再细分为:传统蓝牙模块和高速蓝牙模块。

  • 传统蓝牙模块在2004年推出,主要代表是支持蓝牙2.1协议的模块,在智能手机爆发的时期得到广泛支持。
  • 高速蓝牙模块在2009年推出,速率提高到约24Mbps,是传统蓝牙模块的八倍。

传统蓝牙有3个功率级别,Class1,Class2,Class3,分别支持100m,10m,1m的传输距离

  1. 低功耗蓝牙模块(BLE)

泛指蓝牙4.0或更高的模块,蓝牙低功耗技术是低成本、短距离、可互操作的鲁棒性无线技术,工作在免许可的2.4GHz ISM射频频段。旨在提供显著降低的功耗 ; 可与功率要求更严格的 BLE 设备(例如近程传感器、心率监测仪和健身设备)通信

因为BLE技术采用非常快速的连接方式,因此平时可以处于“非连接”状态(节省能源),此时链路两端相互间只是知晓对方,只有在必要时才开启链路,然后在尽可能短的时间内关闭链路(每次最多传输20字节)。

低功耗蓝牙无功率级别,一般发送功率在7dBm,一般在空旷距离,达到20m应该是没有问题

注意:当用户使用 BLE 将其设备与其他设备配对时,用户设备上的所有应用都可以访问在这两个设备间传输的数据。因此,如果您的应用捕获敏感数据,您应实现应用层安全以保护此类数据的私密性

Android手机蓝牙4.x都是双模蓝牙(既有经典蓝牙也有低功耗蓝牙),而某些蓝牙设备为了省电是单模(只支持低功耗蓝牙)

  • 经典蓝牙:

    1. 传声音

      如蓝牙耳机、蓝牙音箱。蓝牙设计的时候就是为了传声音的,所以是近距离的音频传输的不二选择。

      现在也有基于WIFI的音频传输方案,例如Airplay等,但是WIFI功耗比蓝牙大很多,设备无法做到便携。

      因此固定的音响有WIFI的,移动的如耳机、便携音箱清一色都是基于经典蓝牙协议的。

    2. 传大量数据

      例如某些工控场景,使用Android或Linux主控,外挂蓝牙遥控设备的,

      可以使用经典蓝牙里的SPP协议,当作一个无线串口使用。速度比BLE传输快多了。

      这里要注意的是,iPhone没有开放

  • BLE蓝牙:

    耗电低,数据量小,如遥控类(鼠标、键盘),传感设备(心跳带、血压计、温度传感器、共享单车锁、智能锁、防丢器、室内定位)

    是目前手机和智能硬件通信的性价比最高的手段,直线距离约50米,一节5号电池能用一年,传输模组成本10块钱,远比WIFI、4G等大数据量的通信协议更实用。

    虽然蓝牙距离近了点,但胜在直连手机,价格超便宜。以室内定位为例,商场每家门店挂个蓝牙beacon,就可以对手机做到精度10米级的室内定位,一个beacon的价格也就几十块钱而已

  • 双模蓝牙:

    如智能电视遥控器、降噪耳机等。很多智能电视配的遥控器带有语音识别,需要用经典蓝牙才能传输声音。

    而如果做复杂的按键,例如原本键盘表上没有的功能,经典蓝牙的HID按键协议就不行了,得用BLE做私有协议。

    包括很多降噪耳机上通过APP来调节降噪效果,也是通过BLE来实现的私有通信协议

使用

Client

  • 权限申请

    <uses-permission android:name="android.permission.BLUETOOTH" />
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
    
    <!-- If your app targets Android 9 or lower, you can declare
           ACCESS_COARSE_LOCATION instead. -->
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    
  • 初始化,打开蓝牙,扫描

    val intentFilter = IntentFilter().apply {
    	addAction(BluetoothDevice.ACTION_FOUND)       
        addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED)
        }
    	registerReceiver(mReceiver, intentFilter)
    
    	mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter()
    	mBluetoothAdapter?.let { bluetoothAdapter ->
    		if (!bluetoothAdapter.isEnabled) {
                // enable bluetooth
                val enableBtIntent = Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE                 startActivityForResult(enableBtIntent, REQUEST_CODE_ENABLE_BT)	            }
           if (bluetoothAdapter.isDiscovering) {
                bluetoothAdapter.cancelDiscovery()
           }
           val success = bluetoothAdapter.startDiscovery()
           Log.d(localClassName, "connectionStart discovery:$success")
           // 已经配对的
           val pairedDevices: Set<BluetoothDevice>? = bluetoothAdapter.bondedDevices
           if (pairedDevices.isNullOrEmpty()) {
                mPairedDevicesArrayAdapter?.add("no devices")
           } else {
                pairedDevices.forEach { device ->
                    tv_title_paired.visibility = View.VISIBLE
                    val deviceName = device.name
                    val deviceHardwareAddress = device.address
                    mPairedDevicesArrayAdapter?.add("$deviceName\n$deviceHardwareAddress")
                    Log.d(localClassName, "for name:$deviceName. address:$deviceHardwareAddress")
                    if (deviceHardwareAddress == MAC_ADDRESS) {
                        if (mClientThread == null) {
                            mClientThread = BluetoothClientThread(this, device)
                            mClientThread?.start()
                        }
                    }
                }
            	mPairedDevicesArrayAdapter?.notifyDataSetChanged()
    		}
    	}
        // 设置可检测
    	// val discoverableIntent: Intent = Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE).apply {
        //     putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300)
    	// }
        // startActivity(discoverableIntent)
    }
    
  • 扫描结果

    private val mReceiver = object : BroadcastReceiver() {
        override fun onReceive(context: Context?, intent: Intent?) {
            intent?.action?.let { action ->
    			//attention the location permission to bluetooth
    			when(action) {
                    BluetoothDevice.ACTION_FOUND -> {
                        val device: BluetoothDevice? = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE)
                        device?.apply {
                            val deviceName = name
                            val deviceAddress = address
                            Log.d(localClassName, "onReceive name: $deviceName address: $deviceAddress")
                            if (bondState != BluetoothDevice.BOND_BONDED) {
                                 if (address == MAC_ADDRESS) {
                                mNewDevicesArrayAdapter?.insert("$deviceName\n$deviceAddress", 0)                                             
                                 } else {
                                     mNewDevicesArrayAdapter?.add("$deviceName\n$deviceAddress")                                             
                                 }
                            }
                        }
                        mNewDevicesArrayAdapter?.notifyDataSetChanged()
                    }
                    BluetoothAdapter.ACTION_DISCOVERY_FINISHED -> {
                        title = "select to connect"
                        mNewDevicesArrayAdapter?.let {
                            if (it.count == 0) {
                                it.add("no devices")
                            }
                        }
                    }
                    else -> { }
                }
    		}
        }
    }
    
  • 连接线程

    private var mSocket: BluetoothSocket? = device.createRfcommSocketToServiceRecord(UUID.fromString(BLUETOOTH_UUID))
       
    override fun run() {
        try {
            mSocket?.connect()
            mConnectedThread = mSocket?.let { ConnectedThread(it) }
            mConnectedThread?.start()
        } catch (e: IOException) {
            Log.e(javaClass.name, "connect error", e)
        }
    }
    
  • 读写线程

    private class ConnectedThread(private val socket: BluetoothSocket) : Thread() {
        private val mBuffer = ByteArray(1024)
        private val mDataOS = DataOutputStream(socket.outputStream)
        private val mDataIS = DataInputStream(socket.inputStream)
    
        init {
            Log.d(javaClass.name, "connected thread init $socket")
        }
    
        override fun run() {
            while (true) {
                try {
                    val num = mDataIS.read(mBuffer)
                    Log.d(javaClass.name, "read: ${String(mBuffer)}")
                } catch (e: IOException) {
                    Log.d(javaClass.name, "read error", e)
                }
            }
        }
    
        fun writeFile(context: Context, fileUri: String) {
            var len: Int
            var fileIS: FileInputStream? = null
            val inputStream: InputStream?
            Log.d(javaClass.name, "client socket: ${socket.isConnected}")
            try {
                inputStream = context.contentResolver.openInputStream(Uri.parse(fileUri))
                mDataOS.writeInt(FILE_SEND)
                inputStream?.let {
                    mDataOS.writeInt(it.available())
                    while (true) {
                        len = it.read(mBuffer)
                        if (len == -1) {
                            break
                        }
                    Log.d(javaClass.name, "write ${mBuffer.size} ${mBuffer.count()} ${mBuffer.lastIndex} $len")
                        mDataOS.write(mBuffer, 0, len)
                    }
                    it.close()
                    Log.d(javaClass.name, "Client data written ")
                }
            } catch (e: IOException) {
                Log.e(javaClass.name, "client socket error", e)
            } finally {
                fileIS?.close()
            }
        }
    }
    

Server

  • 权限申请

  • 打开蓝牙,保证可检测

    mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter()
    if (mBluetoothAdapter == null) {
        Log.e(localClassName, "can't apply bluetooth")
        return
    }
    mBluetoothAdapter?.enable()
    // 设置可检测
    // val discoverableIntent: Intent = Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE).apply {
    //     putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 0)
    // }
    // startActivity(discoverableIntent)
    mBluetoothAdapter?.name = "极光249"
    Log.d(localClassName, "enable: ${mBluetoothAdapter?.isEnabled} name: ${mBluetoothAdapter?.name} mac: ${mBluetoothAdapter?.address}")
    bindService(Intent(this, BluetoothServerService::class.java), mConnection, Context.BIND_AUTO_CREATE)
    
  • 启动服务线程

    private inner class AcceptThread : Thread() {
        private val mServerSocket: BluetoothServerSocket? by lazy(LazyThreadSafetyMode.NONE) {
            mBluetoothAdapter?.listenUsingInsecureRfcommWithServiceRecord(NAME, UUID.fromString(BLUETOOTH_UUID))
        }
    
        override fun run() {
            Log.d(javaClass.name, "AcceptThread begin")
            name = "AcceptThread"
            try {
                val clientSocket: BluetoothSocket? = mServerSocket?.accept()
                mHandler?.takeIf { mFilePath != null }?.apply {
                    if (mServerThread == null) {
                        mServerThread = clientSocket?.let { ServerThread(mHandler!!, mFilePath!!, it) }
                        mServerThread?.start()
                    }
                    cancel()
                }
            } catch (e: IOException) {
                Log.e(javaClass.name, "error", e)
                cancel()
            }
        }
    
        fun cancel() {
            try {
                mServerSocket?.close()
            } catch (e: IOException) {
                Log.e(javaClass.name, "Could not close the server mSocket", e)
            }
        }
    }
    
  • 读写线程

    private class ServerThread : Thread {
    
        private var mHandler: Handler
        private var mFilePath: String
        private var mSocket: BluetoothSocket
        private val mDataIS: DataInputStream
        private val mDataOS: DataOutputStream
        private val mBuffer = ByteArray(1024)
    
        constructor(handler: Handler, filePath: String, socket: BluetoothSocket): this("ServerThread", handler, filePath, socket)
    
        constructor(name: String, handler: Handler, filePath: String, socket: BluetoothSocket) {
            this.name = name
            this.mHandler = handler
            this.mFilePath = filePath
            this.mSocket = socket
            mDataIS = DataInputStream(mSocket.inputStream)
            mDataOS = DataOutputStream(mSocket.outputStream)
        }
    
        override fun run() {
            var len: Int
            var fileOS: FileOutputStream? = null
            while (true) {
                try {
                    Log.d(javaClass.name, "receive start")
                    val tag = mDataIS.readInt()
                    if (tag == FILE_SEND) {
                        val size = mDataIS.readInt()
                        var length = 0
                        val f = File(mFilePath, "bluetooth-shared-${System.currentTimeMillis()}.jpg")
                        val dirs = File(f.parent?:mFilePath)
                        dirs.takeIf { !it.exists() }?.apply { mkdirs() }
                        f.createNewFile()
                        fileOS = FileOutputStream(f)
                        while (true) {
                            len = mDataIS.read(mBuffer)
                            length += len
                            Log.d(javaClass.name, "size: $size receive $len")
                            if (len == -1) {
                                break
                            }
                            fileOS.write(mBuffer, 0, len)
                            // 记录接收的大小,在传输完成后跳出此次循环,避免一直阻塞在此
                            if (length >= size) {
                                break
                            }
                        }
                        Log.d(javaClass.name, "receive end: ${f.absolutePath} size: $size")
                        write("received ${f.path}".toByteArray())
                        val msg = Message.obtain()
                        msg.what = BluetoothServerActivity.MESSAGE_RECEIVE_FILE
                        msg.obj = f.absolutePath
                        mHandler.sendMessage(msg)
                }
                } catch (e: IOException) {
                    Log.e(javaClass.name, "readFile error", e)
                } finally {
                    fileOS?.close()
                }
            }
        }
    
        fun write(bytes: ByteArray) {
            val outputStream = mSocket.outputStream
            try {
                outputStream.write(bytes)
            } catch (e: IOException) {
                Log.e(javaClass.name, "writeFile error", e)
            }
        }
    
        fun cancle() {
            try {
                mSocket.close()
            } catch (e: IOException) {
                Log.e(javaClass.name, "close error", e)
            }
        }
    }
    

参考

Android-经典蓝牙(BT)-建立长连接传输短消息和文件

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值