健康一贴灵,专注医药行业管理信息化

Android蓝牙打印(转)

https://www.jianshu.com/p/0fe3a7e06f57?tdsourcetag=s_pcqq_aiomsg

https://www.jianshu.com/p/c0b6d1a4823b

Android 蓝牙连接 ESC/POS 热敏打印机打印(蓝牙连接篇)

VitaminChenIP属地: 广东
0.941字数 1,943阅读 23,345

公司的一个手机端的 CRM 项目最近要增加小票打印的功能,就是我们点外卖的时候经常会见到的那种小票。这里主要涉及到两大块的知识:

  • 蓝牙连接及数据传输
  • ESC/POS 打印指令

蓝牙连接不用说了,太常见了,这篇主要介绍这部分的内容。但ESC/POS 打印指令是个什么鬼?简单说,我们常见的热敏小票打印机都支持这样一种指令,只要按照指令的格式向打印机发送指令,哪怕是不同型号品牌的打印机也会执行相同的动作。比如打印一行文本,换行,加粗等都有对应的指令,这部分内容放在下一篇介绍。

本篇主要基于官方文档,相比官方文档,省去了大段的说明,更加便于快速上手。
demo及打印指令讲解请看下篇

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_STATEEXTRA_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();
    <span class="token comment">// 当有设备被发现的时候会收到 action == BluetoothDevice.ACTION_FOUND 的广播</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>BluetoothDevice<span class="token punctuation">.</span>ACTION_FOUND<span class="token punctuation">.</span><span class="token function">equals</span><span class="token punctuation">(</span>action<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>

        <span class="token comment">//广播的 intent 里包含了一个 BluetoothDevice 对象</span>
        <span class="token class-name">BluetoothDevice</span> device <span class="token operator">=</span> intent<span class="token punctuation">.</span><span class="token function">getParcelableExtra</span><span class="token punctuation">(</span>BluetoothDevice<span class="token punctuation">.</span>EXTRA_DEVICE<span class="token punctuation">)</span><span class="token punctuation">;</span>

        <span class="token comment">//假设我们用一个 ListView 展示发现的设备,那么每收到一个广播,就添加一个设备到 adapter 里</span>
        mArrayAdapter<span class="token punctuation">.</span><span class="token keyword">add</span><span class="token punctuation">(</span>device<span class="token punctuation">.</span><span class="token function">getName</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token string">"\n"</span> <span class="token operator">+</span> device<span class="token punctuation">.</span><span class="token function">getAddress</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>

};
// 注册广播监听
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);
//设置可被发现的时间,300s
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 连接
  1. 首先需要获取一个 BluetoothDevice 对象。获取的方法前面其实已经介绍过了,可以通过调用 startDiscovery()并监听广播获得,也可以通过查询已配对设备获得。
  2. 通过 BluetoothDevice.createRfcommSocketToServiceRecord(UUID) 得到 BluetoothSocket 对象
  3. 通过BluetoothSocket.connect()建立连接
  4. 异常处理以及连接关闭

废话不多说,上代码:

private class ConnectThread extends Thread {
    private final BluetoothSocket mmSocket;
    private final BluetoothDevice mmDevice;
<span class="token keyword">public</span> <span class="token class-name">ConnectThread</span><span class="token punctuation">(</span><span class="token class-name">BluetoothDevice</span> device<span class="token punctuation">)</span> <span class="token punctuation">{</span>

    <span class="token class-name">BluetoothSocket</span> tmp <span class="token operator">=</span> <span class="token keyword">null</span><span class="token punctuation">;</span>
    mmDevice <span class="token operator">=</span> device<span class="token punctuation">;</span>
    <span class="token keyword">try</span> <span class="token punctuation">{</span>
        <span class="token comment">// 通过 BluetoothDevice 获得 BluetoothSocket 对象</span>
        tmp <span class="token operator">=</span> device<span class="token punctuation">.</span><span class="token function">createRfcommSocketToServiceRecord</span><span class="token punctuation">(</span>MY_UUID<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">IOException</span> e<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span>
    mmSocket <span class="token operator">=</span> tmp<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
 
<span class="token annotation punctuation">@Override</span>
<span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">run</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token comment">// 建立连接前记得取消设备发现</span>
    mBluetoothAdapter<span class="token punctuation">.</span><span class="token function">cancelDiscovery</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">try</span> <span class="token punctuation">{</span>
        <span class="token comment">// 耗时操作,所以必须在主线程之外进行</span>
        mmSocket<span class="token punctuation">.</span><span class="token function">connect</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">IOException</span> connectException<span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token comment">//处理连接建立失败的异常</span>
        <span class="token keyword">try</span> <span class="token punctuation">{</span>
            mmSocket<span class="token punctuation">.</span><span class="token function">close</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">IOException</span> closeException<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span>
        <span class="token keyword">return</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
    <span class="token function">doSomething</span><span class="token punctuation">(</span>mmSocket<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token comment">//关闭一个正在进行的连接</span>
<span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">cancel</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">try</span> <span class="token punctuation">{</span>
        mmSocket<span class="token punctuation">.</span><span class="token function">close</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">IOException</span> e<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span>
<span class="token punctuation">}</span>

}

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 一致即可。更多关于 UUID 的介绍可以参考这里

4.2.2 作为 Server 连接
  1. 通过BluetoothAdapter.listenUsingRfcommWithServiceRecord(String, UUID)获取一个 BluetoothServerSocket 对象。这里传入的第一个参数用来设置服务的名称,当其他设备扫描的时候就会显示这个名称。UUID 前面已经介绍过了。
  2. 调用BluetoothServerSocket.accept()开始监听连接请求。这是一个阻塞操作,所以当然也要放在主线程之外进行。当该操作成功执行,即有连接建立的时候,会返回一个BluetoothSocket 对象。
  3. 调用 BluetoothServerSocket.close() 会关闭监听连接的服务,但是当前已经建立的链接并不会受影响。

还是看代码吧:

private class AcceptThread extends Thread {
<span class="token keyword">private</span> <span class="token keyword">final</span> <span class="token class-name">BluetoothServerSocket</span> mmServerSocket<span class="token punctuation">;</span>

<span class="token keyword">public</span> <span class="token class-name">AcceptThread</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>

    <span class="token class-name">BluetoothServerSocket</span> tmp <span class="token operator">=</span> <span class="token keyword">null</span><span class="token punctuation">;</span>
    <span class="token keyword">try</span> <span class="token punctuation">{</span>
        <span class="token comment">// client 必须使用一样的 UUID !!!</span>
        tmp <span class="token operator">=</span> mBluetoothAdapter<span class="token punctuation">.</span><span class="token function">listenUsingRfcommWithServiceRecord</span><span class="token punctuation">(</span>NAME<span class="token punctuation">,</span> MY_UUID<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">IOException</span> e<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span>
    mmServerSocket <span class="token operator">=</span> tmp<span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token annotation punctuation">@Override</span>
<span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">run</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token class-name">BluetoothSocket</span> socket <span class="token operator">=</span> <span class="token keyword">null</span><span class="token punctuation">;</span>
    <span class="token comment">//阻塞操作</span>
    <span class="token keyword">while</span> <span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token keyword">try</span> <span class="token punctuation">{</span>
            socket <span class="token operator">=</span> mmServerSocket<span class="token punctuation">.</span><span class="token function">accept</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">IOException</span> e<span class="token punctuation">)</span> <span class="token punctuation">{</span>
            <span class="token keyword">break</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span>
        <span class="token comment">//直到有有连接建立,才跳出死循环</span>
        <span class="token keyword">if</span> <span class="token punctuation">(</span>socket <span class="token operator">!=</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
            <span class="token comment">//要在新开的线程执行,因为连接建立后,当前线程可能会关闭</span>
            <span class="token function">doSomething</span><span class="token punctuation">(</span>socket<span class="token punctuation">)</span><span class="token punctuation">;</span>
            mmServerSocket<span class="token punctuation">.</span><span class="token function">close</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
            <span class="token keyword">break</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>

<span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">cancel</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">try</span> <span class="token punctuation">{</span>
        mmServerSocket<span class="token punctuation">.</span><span class="token function">close</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">IOException</span> e<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span>
<span class="token punctuation">}</span>

}

5. 数据传输

终于经过了前面的4步,万事俱备只欠东风。而最后这一部分其实是最简单的,因为就只是简单的利用 InputStreamOutputStream进行数据的收发。
示例代码:

private class ConnectedThread extends Thread {
    private final BluetoothSocket mmSocket;
    private final InputStream mmInStream;
    private final OutputStream mmOutStream;
<span class="token keyword">public</span> <span class="token class-name">ConnectedThread</span><span class="token punctuation">(</span><span class="token class-name">BluetoothSocket</span> socket<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    mmSocket <span class="token operator">=</span> socket<span class="token punctuation">;</span>
    <span class="token class-name">InputStream</span> tmpIn <span class="token operator">=</span> <span class="token keyword">null</span><span class="token punctuation">;</span>
    <span class="token class-name">OutputStream</span> tmpOut <span class="token operator">=</span> <span class="token keyword">null</span><span class="token punctuation">;</span>
    <span class="token comment">//通过 socket 得到 InputStream 和 OutputStream</span>
    <span class="token keyword">try</span> <span class="token punctuation">{</span>
        tmpIn <span class="token operator">=</span> socket<span class="token punctuation">.</span><span class="token function">getInputStream</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        tmpOut <span class="token operator">=</span> socket<span class="token punctuation">.</span><span class="token function">getOutputStream</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">IOException</span> e<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span>

    mmInStream <span class="token operator">=</span> tmpIn<span class="token punctuation">;</span>
    mmOutStream <span class="token operator">=</span> tmpOut<span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">run</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">byte</span><span class="token punctuation">[</span><span class="token punctuation">]</span> buffer <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token keyword">byte</span><span class="token punctuation">[</span><span class="token number">1024</span><span class="token punctuation">]</span><span class="token punctuation">;</span>  <span class="token comment">// buffer store for the stream</span>
    <span class="token keyword">int</span> bytes<span class="token punctuation">;</span> <span class="token comment">// bytes returned from read()</span>

    <span class="token comment">//不断的从 InputStream 取数据</span>
    <span class="token keyword">while</span> <span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token keyword">try</span> <span class="token punctuation">{</span>
            bytes <span class="token operator">=</span> mmInStream<span class="token punctuation">.</span><span class="token function">read</span><span class="token punctuation">(</span>buffer<span class="token punctuation">)</span><span class="token punctuation">;</span>
            mHandler<span class="token punctuation">.</span><span class="token function">obtainMessage</span><span class="token punctuation">(</span>MESSAGE_READ<span class="token punctuation">,</span> bytes<span class="token punctuation">,</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">,</span> buffer<span class="token punctuation">)</span>
                    <span class="token punctuation">.</span><span class="token function">sendToTarget</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">IOException</span> e<span class="token punctuation">)</span> <span class="token punctuation">{</span>
            <span class="token keyword">break</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>

<span class="token comment">//向 Server 写入数据</span>
<span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">write</span><span class="token punctuation">(</span><span class="token keyword">byte</span><span class="token punctuation">[</span><span class="token punctuation">]</span> bytes<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">try</span> <span class="token punctuation">{</span>
        mmOutStream<span class="token punctuation">.</span><span class="token function">write</span><span class="token punctuation">(</span>bytes<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">IOException</span> e<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span>
<span class="token punctuation">}</span>

<span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">cancel</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">try</span> <span class="token punctuation">{</span>
        mmSocket<span class="token punctuation">.</span><span class="token function">close</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">IOException</span> e<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span>
<span class="token punctuation">}</span>

}

下一篇介绍通过手机操作热敏打印机打印的时候,还会用到这部分内容,所以这里就先不多讲了。

敬请期待下篇

最后编辑于 :
©著作权归作者所有,转载或内容合作请联系作者
68人点赞
更多精彩内容,就在简书APP
"小礼物走一走,来简书关注我"
还没有人赞赏,支持一下
总资产5共写了2.4W字获得401个赞共204个粉丝
posted @ 2024-04-12 15:55  一贴灵  阅读(72)  评论(0编辑  收藏  举报
学以致用,效率第一