Android 蓝牙连接 ESC/POS 热敏打印机打印实例(蓝牙连接篇)
发布时间 - 2026-01-11 00:37:23 点击率:次公司的一个手机端的 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) { }
}
}
下一篇介绍通过手机操作热敏打印机打印的时候,还会用到这部分内容,所以这里就先不多讲了。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
# android
# esc
# pos
# android蓝牙连接pos机
# Android实现PDF预览打印功能
# Android gradle插件打印时间戳的方法详解
# Android编程实现计算两个日期之间天数并打印所有日期的方法
# Android中如何安全地打印日志详解
# Mac 下 Android Studio 不打印日志的解决办法
# Android jni调试打印char阵列的实例详解
# Android下的POS打印机调用的简单实现
# Android 蓝牙连接 ESC/POS 热敏打印机打印实例(ESC/POS指令篇)
# Android打印机--小票打印格式及模板设置实例代码
# Android进阶——安卓调用ESC/POS打印机打印实例
# Android手机通过蓝牙连接佳博打印机的实例代码
# Android实现系统打印功能
# 热敏
# 就会
# 是一个
# 设为
# 这部
# 弹出
# 下一篇
# 放在
# 过了
# 要在
# 这是一个
# 可以通过
# 很简单
# 小票
# 很清楚
# 自己的
# 都是
# 这是
# 这一
# 是个
相关栏目:
【
网站优化151355 】
【
网络推广146373 】
【
网络技术251813 】
【
AI营销90571 】
相关推荐:
b2c电商网站制作流程,b2c水平综合的电商平台?
JS中对数组元素进行增删改移的方法总结
简历没回改:利用AI润色让你的文字更专业
Zeus浏览器网页版官网入口 宙斯浏览器官网在线通道
Laravel如何实现数据导出到CSV文件_Laravel原生流式输出大数据量CSV【方案】
Microsoft Edge如何解决网页加载问题 Edge浏览器加载问题修复
Laravel如何使用集合(Collections)进行数据处理_Laravel Collection常用方法与技巧
html5如何实现懒加载图片_ intersectionobserver api用法【教程】
南京网站制作费用,南京远驱官方网站?
Win11怎么修改DNS服务器 Win11设置DNS加速网络【指南】
手机网站制作与建设方案,手机网站如何建设?
如何撰写建站申请书?关键要点有哪些?
Laravel如何使用Contracts(契约)进行编程_Laravel契约接口与依赖反转
如何正确下载安装西数主机建站助手?
Laravel的辅助函数有哪些_Laravel常用Helpers函数提高开发效率
如何在IIS7中新建站点?详细步骤解析
详解CentOS6.5 安装 MySQL5.1.71的方法
如何用wdcp快速搭建高效网站?
Laravel如何配置中间件Middleware_Laravel自定义中间件拦截请求与权限校验【步骤】
动图在线制作网站有哪些,滑动动图图集怎么做?
laravel怎么为应用开启和关闭维护模式_laravel应用维护模式开启与关闭方法
桂林网站制作公司有哪些,桂林马拉松怎么报名?
Laravel的路由模型绑定怎么用_Laravel Route Model Binding简化控制器逻辑
潮流网站制作头像软件下载,适合母子的网名有哪些?
Laravel怎么集成Log日志记录_Laravel单文件与每日日志配置及自定义通道【详解】
Laravel路由Route怎么设置_Laravel基础路由定义与参数传递规则【详解】
Laravel如何发送系统通知_Laravel Notifications实现多渠道消息通知
Laravel如何编写单元测试和功能测试?(PHPUnit示例)
JS碰撞运动实现方法详解
浅析上传头像示例及其注意事项
HTML透明颜色代码怎么让图片透明_给img元素加透明色的技巧【方法】
Laravel中Service Container是做什么的_Laravel服务容器与依赖注入核心概念解析
香港服务器网站生成指南:免费资源整合与高速稳定配置方案
悟空识字如何进行跟读录音_悟空识字开启麦克风权限与录音
Laravel Livewire是什么_使用Laravel Livewire构建动态前端界面
HTML5打空格有哪些误区_新手常犯的空格使用错误【技巧】
INTERNET浏览器怎样恢复关闭标签页_INTERNET浏览器标签恢复快捷键与方法【指南】
新三国志曹操传主线渭水交兵攻略
如何在IIS服务器上快速部署高效网站?
Python企业级消息系统教程_KafkaRabbitMQ高并发应用
rsync同步时出现rsync: failed to set times on “xxxx”: Operation not permitted
Laravel如何创建自定义Facades?(详细步骤)
如何在IIS中新建站点并解决端口绑定冲突?
详解ASP.NET 生成二维码实例(采用ThoughtWorks.QRCode和QrCode.Net两种方式)
JS中使用new Date(str)创建时间对象不兼容firefox和ie的解决方法(两种)
html5源代码发行怎么设置权限_访问权限控制方法与实践【指南】
购物网站制作费用多少,开办网上购物网站,需要办理哪些手续?
Laravel如何发送系统通知?(Notification渠道示例)
Win11搜索不到蓝牙耳机怎么办 Win11蓝牙驱动更新修复【详解】
Python面向对象测试方法_mock解析【教程】

