前言
工作中或多或少都会遇到困扰自己很久的问题,我也毫无例外,曾经在项目中对蓝牙打印这一块也困惑和迷茫过,最近在做项目重构的时候,翻看了之前写的代码,还是决定通过两篇文章详细阐述蓝牙连接打印机完成整个打印流程的操作,目的是对工作的一种总结,其次是输出。好比玩个压缩,又是绿叉又是无尽,一身暴击 装,却不知怎么打输出,好像特无语,工作中应该也是一样,学会总结和输出这样才能提升自己,当然也希望能帮助在蓝牙打印方面存在疑惑和困扰的同学,希望在看完这两篇文章后能对经典蓝牙有更多了解和认识。
概述
我将通过上下两部分,详细阐述蓝牙打印功能整体思路以及流程,上部分主要是阐述如何通过协程开启子任务连接蓝牙,以及如何通过系统广播建立心跳包,实现断线重连机制,下部分主要阐述应用和蓝牙打印机间的通信。
基本了解
1,了解经典蓝牙和低功耗蓝牙的区别,太必要了,当初我就走了弯路,如果你对这两种蓝牙有了解,那实在是太好了。
2,协程相关知识,如何建立耗时任务等,如果你不了解协程,你也可以使用RxJava实现,或者直接开启线程建立耗时任务,姿势很多。
3,对Service和Broadcast有一定了了解,当然我相信你对这两大组件一定再熟悉不过了。
UML类图
类图.png实现过程
1,准备工作
在我们日常开发中,对整个功能的需求把控设计,应该是首当其冲的必要条件,所以通过类图我们大致定义了两个功能接口,BlueClient和BlueConnector,BlueClient接口大致包含开启蓝牙,关闭,扫描周围蓝牙设备,连接蓝牙,断开蓝牙设备等功能,具体功能细节类图或者代码都做了详细说明,BlueConnector接口主要是用来定义蓝牙设备连接,断开和重连等,BlueClient中的功能定义依赖BlueConnector接口的实现。
2,具体实现
定义BlueClient接口 主要用来打开蓝牙,连接蓝牙设备等功能
/**
*author : Frank
*e-mail : zhuhuitao_struggle@163.com
*date : 2021/1/15 14:40
*desc :
*version :
*/
interface BlueClient {
/**
* 根据传入状态 打开和关闭蓝牙
*
* @param state 蓝牙状态
*/
fun onBlueState(state: Int)
/**
* 开始扫描蓝牙设备
*
* @param callback 扫描结果回调
* @throws NotInitException 回调异常捕获
* @throws BlueScanException 蓝牙扫描异常捕获
*/
@Throws(NotInitException::class, BlueScanException::class)
fun onStartScan(callback: ScanCallback)
/**
* 停止或者终止扫描设备后回调
*
* @throws NotInitException 异常捕获
*/
@Throws(NotInitException::class)
fun onStopScan()
/**
* 停止或者终止扫描
*/
fun cancelScan()
/**
* 发现新设备
*
* @param device 发现的新设备
* @throws NotInitException 回调异常捕获
*/
@Throws(NotInitException::class)
fun onFind(device: BluetoothDevice?)
/**
* 连接一个设备
*
* @param device 所需要连接的设备
* @return 返回连接结果
*/
fun connect(device: BluetoothDevice, callback: BlueConnectCallback)
/**
* 指定mac地址连接一台蓝牙设备
*
* @param mac mac地址
* @param connectCallback 连接结果回调
*/
fun connect(mac: String, connectCallback: BlueConnectCallback)
/**
* 断开一个设备
*
* @param device 所需要断开的设备
* @return 返回断开结果
*/
fun disConnect(device: BluetoothDevice, callback: DisconnectCallback)
/**
* 设备主动断开后重连,此过程由于蓝牙设备间通信出现异常断开,不存在人为操作
*/
fun onReconnect()
/**
* 打开蓝牙
*/
fun openBlue(callback: BlueSwitchCallback)
/**
* 关闭蓝牙
*/
fun closeBlue(callback: BlueSwitchCallback)
/**
* 获取上下文
*
* @return
*/
fun getContext(): Context
/**
* 初始化相关工作
*/
fun init(context: Context): BlueClient
/**
* 成功连接了一台蓝牙设备
*
* @param device 连接的蓝牙设备
*/
fun onConnected(device: BluetoothDevice)
/**
* 成功断开一台设备
*
* @param device
*/
fun onDisconnect(device: BluetoothDevice)
/**
* 蓝牙配对状态
*
* @param device 蓝牙配对状态回调
*/
fun bondStatus(device: BluetoothDevice)
/**
* 获取已绑定的蓝牙设备
*
* @return
*/
fun getBondedDevices(): List<BluetoothDevice?>
/**
* 是否开启日志功能
* @param isDebug 日志开关
*/
fun enableDebug(isDebug:Boolean)
}
定义BlueClint具体实现类BlueClientImpl
/**
*author : Frank
*e-mail : zhuhuitao_struggle@163.com
*date : 2021/1/15 15:12
*desc :
*version :
*/
class BlueClientImpl : BlueClient {
private lateinit var mCtx: Context
//蓝牙适配器
private var mBlueAdapter: BluetoothAdapter? = null
//连接状态回调
private var mConnectCallback: BlueConnectCallback? = null
//断开状态回调
private var mDisconnectCallback: DisconnectCallback? = null
//开启关闭蓝牙状态回调
private var mBlueSwitchCallback: BlueSwitchCallback? = null
//扫描周围蓝牙设备回调
private var mScanCallback: ScanCallback? = null
//存放周围扫描到的蓝牙设备,已去除重复设备
private var mDeviceList: MutableList<BluetoothDevice>? = null
//蓝牙广播接收者
private var mBlueReceiver: BluetoothReceiver? = null
companion object {
//标识当前的蓝牙设备是否已连接
var connected = false
//用于标识蓝牙开关状态
var isOPen = false
//用于标识是否是人为断开
var isArtifical = false
//伴生对象
val instance = BlueClientImpl()
}
override fun onBlueState(state: Int) {
//通知UI层当前的蓝牙开启状态
if (state == BluetoothAdapter.STATE_ON) {
//已打开
mBlueSwitchCallback?.onStateChange(true)
}
if (state == BluetoothAdapter.STATE_OFF) {
//已关闭
mBlueSwitchCallback?.onStateChange(false)
//将连接状态置为false
connected = false
}
}
override fun onStartScan(callback: ScanCallback) {
//初始化callback对象
mScanCallback = callback
if (mBlueAdapter != null) {
//如果正在扫描周围的蓝牙设备,则返回
if (mBlueAdapter?.isDiscovering == true) {
return
}
//初始化集合,用于存放周围发现的蓝牙设备
if (mDeviceList == null) mDeviceList = arrayListOf()
//开始扫描
mBlueAdapter?.startDiscovery()
//通知ui层正在扫描
mScanCallback?.onStartScan(true)
}
}
override fun onStopScan() {
//当停止扫描后把当前的设备集合返回到UI层
mScanCallback?.onStopScan(mDeviceList!!)
}
override fun cancelScan() {
if (mBlueAdapter != null) {
//1,当用户点击ui连接设备时,调用此方法,停止扫描周围设备
//2,接收系统扫描的结束的通知,当然自己也可以做一个定时任务主动停止扫描
mBlueAdapter?.cancelDiscovery()
}
}
override fun onFind(device: BluetoothDevice?) {
//如果接收到的设备为null直接返回不做处理
if (device == null) return
//如果集合不为null直接遍历集合,
if (mDeviceList!!.isNotEmpty()) {
mDeviceList?.forEach {
//如果发现设备已存在则返回,不在继续加入集合
if (it.address == device.address) return
}
}
//回调发现的设备
mScanCallback?.onFindDevice(device)
//将设备加入集合列表 用于ui展示
this.mDeviceList?.add(device)
}
override fun connect(device: BluetoothDevice, callback: BlueConnectCallback) {
//初始化callback对象
mConnectCallback = callback
//连接一台指定的蓝牙设备
BlueConnectorImpl.instant.connect(mBlueAdapter, device, mConnectCallback)
}
override fun connect(mac: String, connectCallback: BlueConnectCallback) {
//初始化callback对象
mConnectCallback = connectCallback
//指定mac 连接一台蓝牙设备
BlueConnectorImpl.instant.connect(mBlueAdapter, mac, mConnectCallback)
}
override fun disConnect(device: BluetoothDevice, callback: DisconnectCallback) {
//初始化callback
mDisconnectCallback = callback
//断开蓝牙设备
BlueConnectorImpl.instant.disconnect()
}
override fun onReconnect() {
//调用BlueConnector实现类完成重连
BlueConnectorImpl.instant.reconnect()
}
override fun openBlue(callback: BlueSwitchCallback) {
//初始化switchCallback
mBlueSwitchCallback = callback
if (!supportBluetooth()) {
return
}
if (mBlueAdapter?.isEnabled == false) {
//开启蓝牙
mBlueAdapter?.enable()
} else {
mBlueSwitchCallback?.onStateChange(true)
}
}
override fun closeBlue(callback: BlueSwitchCallback) {
//关闭蓝牙,此处一般程序中用不到,若不支持蓝牙,不做任何操作
if (!supportBluetooth()) return
if (mBlueAdapter?.isEnabled == true) {
//调用蓝牙适配器关闭蓝牙
mBlueAdapter?.disable()
}
}
override fun getContext(): Context {
return mCtx
}
override fun init(context: Context): BlueClient {
mCtx = context
//获取蓝牙适配器
if (mBlueAdapter == null) {
mBlueAdapter = BluetoothAdapter.getDefaultAdapter()
}
//初始化蓝牙广播接收 BroadcastReceiver
if (mBlueReceiver == null) {
mBlueReceiver = BluetoothReceiver(mCtx, this)
}
return this
}
override fun onConnected(device: BluetoothDevice) {
//将连接状态置为true
connected = true
//f返回当前连接的设备以及当前的socket
mConnectCallback?.connectSuccess(BlueConnectorImpl.instant.getBlueSocket(), device)
}
override fun onDisconnect(device: BluetoothDevice) {
//将连接状态置为false
connected = false
//通知连接失败状态,这里断开分为两种状态,
// 1认为操作断开,2无人为干预自动断开(此中状态会触发自动重连)
mDisconnectCallback?.disConnectSuccess(device)
}
override fun bondStatus(device: BluetoothDevice) {
//返回当前配对蓝牙设备
mConnectCallback?.bondStatus(device)
}
override fun getBondedDevices(): MutableList<BluetoothDevice> {
//这里返回配对过的蓝牙设备,这里由于暂未使用此功能,直接返回了空的list集合
return arrayListOf()
}
/**
* 检查设备是否支持蓝牙功能
*/
private fun supportBluetooth(): Boolean {
if (mBlueAdapter == null) {
//通过判断蓝牙适配器是否为空,如果未空直接返回蓝牙关闭状态false
mBlueSwitchCallback?.onStateChange(false)
}
return true
}
}
定义BlueConector接口,连接指定设备或者mac蓝牙设备,断开蓝牙设备
/**
*author : Frank
*e-mail : zhuhuitao_struggle@163.com
*date : 2021/1/15 15:07
*desc :
*version :
*/
interface BlueConnector {
/**
* 尝试连接一个指定蓝牙设备
*
* @param device 所要连接的设备
* @param connectCallback 连接状态回调
* @param adapter 设备蓝牙适配器
*/
fun connect(
adapter: BluetoothAdapter?,
device: BluetoothDevice?,
connectCallback: BlueConnectCallback?
)
/**
* 尝试连接一个指定mac的蓝牙设备
*
* @param mac 要连接的蓝牙设备mac地址
* @param connectCallback 连接状态回调
* @param adapter 设备蓝牙适配器
*/
fun connect(
adapter: BluetoothAdapter?,
mac: String?,
connectCallback: BlueConnectCallback?
)
/**
* 设备重连,只适用于当前已连接的设备
*/
fun reconnect()
/**
* 尝试断开当前连接的蓝牙设备
*/
fun disconnect()
/**
* 获取当前连接的蓝牙设备socket
*
* @return
*/
fun getBlueSocket(): BluetoothSocket?
}
定义BlueConnector接口实现类BlueConnectorImpl
/**
*author : Frank
*e-mail : zhuhuitao_struggle@163.com
*date : 2021/1/15 15:34
*desc :
*version :
*/
class BlueConnectorImpl : BlueConnector {
//蓝牙适配器
private var mBlueAdapter: BluetoothAdapter? = null
//当前连接的蓝牙设备
private var mDevice: BluetoothDevice? = null
//socket
private var mBlueSocket: BluetoothSocket? = null
//连接状态回调
private var mBlueConnectCallback: BlueConnectCallback? = null
companion object {
//半生对象
val instant = BlueConnectorImpl()
val CBT_UUID: UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB")
}
/**
* 指定连接一台蓝牙设备
*/
override fun connect(
adapter: BluetoothAdapter?,
device: BluetoothDevice?,
connectCallback: BlueConnectCallback?
) {
if (device != null) {
if (mDevice?.address == device.address) {
reconnect()
return
}
}
//赋值
mDevice = device
if (adapter == null) return
mBlueAdapter = adapter
mBlueConnectCallback = connectCallback
//声明临时的socket对象
var temp: BluetoothSocket? = null
try {
val uuid = CBT_UUID ?: return
//获取蓝牙socket对象
temp = mDevice?.createRfcommSocketToServiceRecord(uuid)
} catch (e: Exception) {
mBlueConnectCallback?.connectFailed(e)
}
if (temp == null) {
mBlueConnectCallback?.connectFailed(IOException("Bluetooth connect failed..."))
return
}
mBlueSocket = temp
connect()
}
/**
* 根据指定的mac 连接一台蓝牙设备
*/
override fun connect(
adapter: BluetoothAdapter?,
mac: String?,
connectCallback: BlueConnectCallback?
) {
if (adapter == null) return
if (mac.isNullOrEmpty()) return
if (connectCallback == null) return
//赋值
mBlueAdapter = adapter
mBlueConnectCallback = connectCallback
mDevice = mBlueAdapter?.getRemoteDevice(mac) ?: return
var temp: BluetoothSocket? = null
try {
temp = mDevice?.createInsecureRfcommSocketToServiceRecord(CBT_UUID)
} catch (e: Exception) {
mBlueConnectCallback?.connectFailed(e)
}
if (temp == null) {
mBlueConnectCallback?.connectFailed(IOException("Bluetooth connect failed..."))
return
}
mBlueSocket = temp
connect()
}
/**
* 重连
*/
override fun reconnect() {
connect()
}
/**
* 断开蓝牙设备
*/
override fun disconnect() {
if (mBlueSocket == null) return
mBlueSocket?.close()
mBlueSocket = null
mBlueAdapter = null
mDevice = null
}
/**
* 获取当前连接的蓝牙socket对象
*/
override fun getBlueSocket(): BluetoothSocket? {
return mBlueSocket
}
private fun connect() {
LogUtil.debug("开始连接蓝牙设备...")
if (mBlueSocket == null) return
try {
//建立子任务,开始连接connect,不阻塞UI线程
GlobalScope.launch {
runCatching {
mBlueSocket?.connect()
}.onSuccess {
//这里可以回调成功,为了准确还可以以系统广播连接成功为准
mBlueConnectCallback?.connectSuccess(mBlueSocket!!, mDevice)
LogUtil.debug("蓝牙连接成功....")
}.onFailure {
//当抛出异常直接回调连接失败
mBlueConnectCallback?.connectFailed(it)
LogUtil.debug("蓝牙设备连接失败....")
}
}
} catch (e: Exception) {
//回调连接失败
mBlueConnectCallback?.connectFailed(e)
LogUtil.debug("蓝牙设备连接失败...")
}
}
}
定义系统广播接收者BluetoothReceiver extends BroadcastReceiver
/**
*author : Frank
*e-mail : zhuhuitao_struggle@163.com
*date : 2021/1/16 9:55
*desc :
*version :
*/
class BluetoothReceiver(ctx: Context, client: BlueClient) : BroadcastReceiver() {
private var mClient: BlueClient = client
init {
val filter = IntentFilter()
//蓝牙开关状态
filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED)
//蓝牙开始扫描状态
filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_STARTED)
//蓝牙扫描结束
filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED)
//蓝牙扫描发现新设备(未配对)
filter.addAction(BluetoothDevice.ACTION_FOUND)
//蓝牙配对状态改变
filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED)
//设备建立连接
filter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED)
//设备断开连接
filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED)
//蓝牙适配器连接状态改变
filter.addAction(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED)
//BluetoothA2dp连接状态
filter.addAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED)
filter.addAction("android.media.VOLUME_CHANGED_ACTION")
ctx.registerReceiver(this, filter)
//创建心跳机制 这里为了节省开销,可以在蓝牙设备连接成功后在建立心跳机制,这里为了简单直接在此初始化
createHeartBeat(mClient)
}
override fun onReceive(context: Context?, intent: Intent?) {
val action = intent?.action ?: return
when (action) {
BluetoothAdapter.ACTION_STATE_CHANGED -> {
val state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, 0)
BlueClientImpl.isOPen = (state == BluetoothAdapter.STATE_ON)
if (!BlueClientImpl.isOPen)
//当蓝牙设备为开启状态时回调给上层操作对象
mClient.onBlueState(state)
}
BluetoothAdapter.ACTION_DISCOVERY_STARTED -> {
//start scan bluetooth device
}
BluetoothAdapter.ACTION_DISCOVERY_FINISHED -> {
//接收到系统停止扫描设备广播
mClient.onStopScan()
}
BluetoothDevice.ACTION_FOUND -> {
//接收到系统扫描到周围的蓝牙设备
mClient.onFind(
intent.getParcelableExtra<BluetoothDevice>(BluetoothDevice.EXTRA_DEVICE)
?: return
)
}
BluetoothDevice.ACTION_BOND_STATE_CHANGED -> {
//蓝牙配对状态回调
val device =
intent.getParcelableExtra<BluetoothDevice>(BluetoothDevice.EXTRA_DEVICE)
?: return
mClient.bondStatus(device)
}
BluetoothDevice.ACTION_ACL_CONNECTED -> {
//蓝牙连接状态回到
val device =
intent.getParcelableExtra<BluetoothDevice>(BluetoothDevice.EXTRA_DEVICE)
?: return
if (device.bondState == 12) {
//如果绑定状态为12 则判断蓝牙连接成功,可以发起回调通知UI层,前面通过协程任务我们已经知道
//蓝牙连接是否成功,此处将不再回调,
// mClient.onConnected(device)
}
}
BluetoothDevice.ACTION_ACL_DISCONNECTED -> {
//蓝牙设备断开
val device =
intent.getParcelableExtra<BluetoothDevice>(BluetoothDevice.EXTRA_DEVICE)
?: return
//由于我们这里连接的是打印机,所以判断如果不是蓝牙打印机设备,不做处理,这里的设备类型,查看了源码,直接写成了
//1536 和 7936 具体类型可以前往系统源码Device查看
if (!isPrinter(device.bluetoothClass.majorDeviceClass)) return
//如果是人为,不做重连处理
if (isArtifical) {
//回调蓝牙断开状态
mClient.onDisconnect(device)
return
}
mClient.onReconnect()
}
}
}
}
使用
初始化BlueClint对象
BlueClientImpl.instance.init(context).enableDebug(true)
开始操作蓝牙
BlueClientImpl.instance.onStartScan(object : ScanCallback {
override fun onStartScan(isOn: Boolean) {
mViewModel.isScanning.set(true)
}
override fun onStopScan(deviceList: MutableList<BluetoothDevice>) {
mViewModel.isScanning.set(false)
}
override fun onFindDevice(device: BluetoothDevice) {
if (device.name == null) return
mSystemList.add(device)
mAdapter.addData(BluetoothSimpleDevice(false, device.name, device.address, "89"))
}
})
注意事项
在扫描周围蓝牙设备的时候一定要开启定位权限,不然无法获取到周围的蓝牙设备(特别注意),6.0以上设备需要动态申请定位权限,manifest清单文件申请蓝牙操作必要的权限:
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
总结
通过以上几个重要的步骤,我们基本实现了蓝牙的开启,扫描周围设备,连接一台蓝牙设备,断开蓝牙设备等常用功能,当然其中仍然存在许多优化的细节,大体思路和方向我觉得还是正确和行的通,毕竟在项目中已经使用了此方案,代码我会在写完第二部分上传到github,代码我也将会持续更新,由于我自身技术水平受限,可能由于思考问题不全面,如果觉得写的不好,请勿喷,也欢迎指教和issue。
针对蓝牙相关问题,我已做了相关lib,如果想了解,请查阅Android链接蓝牙打印机
网友评论