Android 蓝牙连接 ESC/POS 热敏打印机打印实例(蓝牙连接篇)
公司的一个手机端的 crm 项目最近要增加小票打印的功能,就是我们点外卖的时候经常会见到的那种小票。这里主要涉及到两大块的知识:
- 蓝牙连接及数据传输
- esc/pos 打印指令
蓝牙连接不用说了,太常见了,这篇主要介绍这部分的内容。但esc/pos 打印指令是个什么鬼?简单说,我们常见的热敏小票打印机都支持这样一种指令,只要按照指令的格式向打印机发送指令,哪怕是不同型号品牌的打印机也会执行相同的动作。比如打印一行文本,换行,加粗等都有对应的指令,这部分内容放在下一篇介绍。
本篇主要基于,相比官方文档,省去了大段的说明,更加便于快速上手。
1. 蓝牙权限
想要使用蓝牙功能,首先要在 androidmanifest 配置文件中声明蓝牙权限:
<manifest> <uses-permission android:name="android.permission.bluetooth" /> <uses-permission android:name="android.permission.bluetooth_admin" /> ... </manifest>
bluetooth
权限只允许建立蓝牙连接以及传输数据,但是如果要进行蓝牙设备发现等操作的话,还需要申请 bluetooth_admin
权限。
2. 初始配置
这里主要用到一个类bluetoothadapter。用法很简单,直接看代码:
bluetoothadapter mbluetoothadapter = bluetoothadapter.getdefaultadapter(); if (mbluetoothadapter == null) { // device does not support bluetooth }
单例模式,全局只有一个实例,只要为 null,就代表设备不支持蓝牙,那么需要有相应的处理。
如果设备支持蓝牙,那么接着检查蓝牙是否打开:
if (!mbluetoothadapter.isenabled()) { intent intent = new intent(bluetoothadapter.action_request_enable); startactivityforresult(intent, request_enable_bt); }
如果蓝牙未打开,那么执行 startactivityforresult()
后,会弹出一个对话框询问是否要打开蓝牙,点击`是`之后就会自动打开蓝牙。成功打开蓝牙后就会回调到 onactivityresult()。
除了主动的打开蓝牙,还可以监听 bluetoothadapter.action_state_changed
广播,包含extra_state
和extra_previous_state
两个 extra 字段,可能的取值包括 state_turning_on
, state_on, state_turning_off
, and state_off
。含义很清楚了,不解释。
3. 发现设备
初始化完成之后,蓝牙打开了,接下来就是扫描附近的设备,只需要一句话:
mbluetoothadapter.startdiscovery();
不过这样只是开始执行设备发现,这肯定是一个异步的过程,我们需要注册一个广播,监听发现设备的广播,直接上代码:
private final broadcastreceiver mreceiver = new broadcastreceiver() { public void onreceive(context context, intent intent) { string action = intent.getaction(); // 当有设备被发现的时候会收到 action == bluetoothdevice.action_found 的广播 if (bluetoothdevice.action_found.equals(action)) { //广播的 intent 里包含了一个 bluetoothdevice 对象 bluetoothdevice device = intent.getparcelableextra(bluetoothdevice.extra_device); //假设我们用一个 listview 展示发现的设备,那么每收到一个广播,就添加一个设备到 adapter 里 marrayadapter.add(device.getname() + "\n" + device.getaddress()); } } }; // 注册广播监听 intentfilter filter = new intentfilter(bluetoothdevice.action_found); registerreceiver(mreceiver, filter); // don't forget to unregister during ondestroy
注释已经写的很清楚了,除了 bluetoothdevice.extra_device
之外,还有一个 extra 字段 bluetoothdevice.extra_class
, 可以得到一个 bluetoothclass 对象,主要用来保存设备的一些额外的描述信息,比如可以知道这是否是一个音频设备。
关于设备发现,有两点需要注意:
startdiscovery()
只能扫描到那些状态被设为 可发现 的设备。安卓设备默认是不可发现的,要改变设备为可发现的状态,需要如下操作:
intent intent = new intent(bluetoothadapter.action_request_discoverable); //设置可被发现的时间,00s intent.putextra(bluetoothadapter.extra_discoverable_duration, 300); startactivity(intent);
执行之后会弹出对话窗询问是否允许设备被设为可发现的状态,点击`是`之后设备即被设为可发现的状态。
startdiscovery()
是一个十分耗费资源的操作,所以需要及时的调用canceldiscovery()
来释放资源。比如在进行设备连接之前,一定要先调用canceldiscovery()
4. 设备配对与连接
4.1 配对
当与一个设备第一次进行连接操作的时候,屏幕会弹出提示框询问是否允许配对,只有配对成功之后,才能建立连接。
系统会保存所有的曾经成功配对过的设备信息。所以在执行startdiscovery()
之前,可以先尝试查找已配对设备,因为这是一个本地信息读取的过程,所以比startdiscovery()
要快得多,也避免占用过多资源。如果设备在蓝牙信号的覆盖范围内,就可以直接发起连接了。
查找配对设备的代码如下:
set<bluetoothdevice> paireddevices = mbluetoothadapter.getbondeddevices(); if (paireddevices.size() > 0) { for (bluetoothdevice device : paireddevices) { marrayadapter.add(device.getname() + "\n" + device.getaddress()); } }
代码很简单,不解释了,就是调用bluetoothadapter.getbondeddevices()
得到一个 set<bluetoothdevice>
并遍历取得已配对的设备信息。
4.2 连接
蓝牙设备的连接和网络连接的模型十分相似,都是client-server 模式,都通过一个 socket 来进行数据传输。那么作为一个 android 设备,就存在三种情况:
- 只作为 client 端发起连接
- 只作为 server 端等待别人发起建立连接的请求
- 同时作为 client 和 server
因为是为了下一篇介绍连接热敏打印机打印做铺垫,所以这里先讲 android 设备作为 client 建立连接的情况。因为打印机是不可能主动跟 android 设备建立连接的,所以打印机必然是作为 server 被连接。
4.2.1 作为 client 连接
- 首先需要获取一个 bluetoothdevice 对象。获取的方法前面其实已经介绍过了,可以通过调用 startdiscovery()并监听广播获得,也可以通过查询已配对设备获得。
- 通过 bluetoothdevice.createrfcommsockettoservicerecord(uuid) 得到bluetoothsocket 对象
- 通过bluetoothsocket.connect()建立连接
- 异常处理以及连接关闭
废话不多说,上代码:
private class connectthread extends thread { private final bluetoothsocket mmsocket; private final bluetoothdevice mmdevice; public connectthread(bluetoothdevice device) { bluetoothsocket tmp = null; mmdevice = device; try { // 通过 bluetoothdevice 获得 bluetoothsocket 对象 tmp = device.createrfcommsockettoservicerecord(my_uuid); } catch (ioexception e) { } mmsocket = tmp; } @override public void run() { // 建立连接前记得取消设备发现 mbluetoothadapter.canceldiscovery(); try { // 耗时操作,所以必须在主线程之外进行 mmsocket.connect(); } catch (ioexception connectexception) { //处理连接建立失败的异常 try { mmsocket.close(); } catch (ioexception closeexception) { } return; } dosomething(mmsocket); } //关闭一个正在进行的连接 public void cancel() { try { mmsocket.close(); } catch (ioexception e) { } } }
device.createrfcommsockettoservicerecord(my_uuid)
这里需要传入一个 uuid
,这个uuid
需要格外注意一下。简单的理解,它是一串约定格式的字符串,用来唯一的标识一种蓝牙服务。
client 发起连接时传入的 uuid 必须要和 server 端设置的一样!否则就会报错!
如果是连接热敏打印机这种情况,不知道 server 端设置的 uuid 是什么怎么办?
不用担心,因为一些常见的蓝牙服务协议已经有约定的 uuid。比如我们连接热敏打印机是基于 spp 串口通信协议,其对应的 uuid 是 "00001101-0000-1000-8000-00805f9b34fb",所以实际的调用是这样:
device.createrfcommsockettoservicerecord(uuid.fromstring("00001101-0000-1000-8000-00805f9b34fb"))
其他常见的蓝牙服务的uuid大家可以自行搜索。如果只是用于自己的应用之间的通信的话,那么理论上可以随便定义一个 uuid,只要 server 和 client 两边使用的 uuid 一致即可。
4.2.2 作为 server 连接
- 通过
bluetoothadapter.listenusingrfcommwithservicerecord(string, uuid)
获取一个 bluetoothserversocket 对象。这里传入的第一个参数用来设置服务的名称,当其他设备扫描的时候就会显示这个名称。uuid 前面已经介绍过了。 - 调用
bluetoothserversocket.accept()
开始监听连接请求。这是一个阻塞操作,所以当然也要放在主线程之外进行。当该操作成功执行,即有连接建立的时候,会返回一个bluetoothsocket 对象。 - 调用
bluetoothserversocket.close()
会关闭监听连接的服务,但是当前已经建立的链接并不会受影响。
还是看代码吧:
private class acceptthread extends thread { private final bluetoothserversocket mmserversocket; public acceptthread() { bluetoothserversocket tmp = null; try { // client 必须使用一样的 uuid !!! tmp = mbluetoothadapter.listenusingrfcommwithservicerecord(name, my_uuid); } catch (ioexception e) { } mmserversocket = tmp; } @override public void run() { bluetoothsocket socket = null; //阻塞操作 while (true) { try { socket = mmserversocket.accept(); } catch (ioexception e) { break; } //直到有有连接建立,才跳出死循环 if (socket != null) { //要在新开的线程执行,因为连接建立后,当前线程可能会关闭 dosomething(socket); mmserversocket.close(); break; } } } public void cancel() { try { mmserversocket.close(); } catch (ioexception e) { } } }
5. 数据传输
终于经过了前面的4步,万事俱备只欠东风。而最后这一部分其实是最简单的,因为就只是简单的利用 inputstream
和outputstream
进行数据的收发。
示例代码:
private class connectedthread extends thread { private final bluetoothsocket mmsocket; private final inputstream mminstream; private final outputstream mmoutstream; public connectedthread(bluetoothsocket socket) { mmsocket = socket; inputstream tmpin = null; outputstream tmpout = null; //通过 socket 得到 inputstream 和 outputstream try { tmpin = socket.getinputstream(); tmpout = socket.getoutputstream(); } catch (ioexception e) { } mminstream = tmpin; mmoutstream = tmpout; } public void run() { byte[] buffer = new byte[1024]; // buffer store for the stream int bytes; // bytes returned from read() //不断的从 inputstream 取数据 while (true) { try { bytes = mminstream.read(buffer); mhandler.obtainmessage(message_read, bytes, -1, buffer) .sendtotarget(); } catch (ioexception e) { break; } } } //向 server 写入数据 public void write(byte[] bytes) { try { mmoutstream.write(bytes); } catch (ioexception e) { } } public void cancel() { try { mmsocket.close(); } catch (ioexception e) { } } }
下一篇介绍通过手机操作热敏打印机打印的时候,还会用到这部分内容,所以这里就先不多讲了。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。