
简介面向移动开发者的安卓POS机项目源码包演示如何在智能手机/平板上实现交易处理、收据打印等收银功能。项目覆盖支付SDK接入、HTTPS加密通信、蓝牙打印机通信、后台Service运行、权限配置、AndroidX兼容适配等关键环节适合学习移动支付与硬件交互的初中级开发者。压缩包共67个文件以26个Java源码、19个XML布局/配置文件为主另有Gradle构建脚本、ProGuard混淆规则、Git配置及启动脚本等整体仅392KB结构紧凑便于快速阅读核心代码。已有381人学习下载具备一定参考价值。通过阅读源码可了解POS类App的整体架构与AndroidManifest权限声明方式掌握支付回调处理、蓝牙打印流程及多屏幕适配思路也可作为课程设计或毕设的基础工程直接改造使用。 做安卓POS机这套东西也有三年多了从最早拿着安卓平板接一个蓝牙小票机捣鼓扫码收银到后来完整对接支付通道、串口钱箱、密码键盘踩过的坑能堆满一个购物车。这篇就围绕“Android代码-pos机安卓版本”这个主题把我实际开发中的架构思路、核心代码、排障经验一次性整理出来。大多数方案是基于常见安卓工控板和标准外设协议做的验证如果你正打算自己弄一套POS机安卓版本可以参考这套路子至少能少走好几个月的弯路。1. 项目需求与整体设计1.1 POS安卓版要解决的核心问题一台能收银的安卓设备最核心的不是界面多漂亮而是“稳定完成交易”。这个“稳定”包含很多层面外设不能掉线、交易状态要准确、异常断电后流水不能丢、打印不能乱码。很多刚接触POS开发的朋友会先做界面然后把打印、扫码一个个接起来结果一到真机联调就崩。原因很简单没把硬件驱动当成独立的一层来设计。还有一点容易被忽略安卓POS不是普通手机App。普通App主要跟系统和网络打交道而POS机要同时处理串口读卡器、蓝牙打印、USB扫码枪、密码键盘甚至钱箱控制。这些外设各有各的协议有的走串口有的走蓝牙SPP有的模拟键盘输入如果每个页面都直接调外设代码会乱成一团后面想加一个设备都要伤筋动骨。1.2 设备选型与硬件层抽象做商用POS安卓版硬件选型基本决定开发方式。我实际用过的有RK3288方案的工控板、带工业级串口的商用手持机、一部分型号还保留了一个USB Host口。系统版本从Android 6到Android 11都有常见的反而是Android 7和Android 9Android 12以上在部分老外设驱动上兼容性很差比如某些串口库在SELinux策略变更后会直接打不开设备节点。选型时要注意几点第一设备必须带有标准串口或USB Host光有一个Type-C充电口远远不够第二系统最好是精简过的不带一堆预装应用否则内存占用高收银台长时间挂着会卡第三要确认厂商是否愿意开放内核串口权限或提供root方案。如果没有root或SELinux放行后面串口这块会非常痛苦。硬件层抽象也是我在项目一开始就做的。所有外设操作集中到一个DeviceManager上层业务不关心具体走的是什么协议只调用open、write、read、close这几个方法。这样即使后换了一个不同方案的扫码枪也只需要在DeviceManager里替换实现不用改动任何界面代码。2. 技术架构与模块划分2.1 驱动层封装串口、USB与蓝牙驱动层是整个POS安卓版最靠近底层的部分也是最容易出问题的环节。串口设备我一般用开源串口库android-serialport-api改造重点是把波特率、数据位、停止位这些参数配置化并且封装一个串口连接池避免多个业务模块同时打开同一个串口导致资源冲突。USB扫码枪分两类一类是HID模拟键盘输入插上就能用系统把它当键盘另一类是厂商自定义的HID Bulk传输必须通过UsbManager做权限申请和端点通信。前者开发简单但有一个致命问题当界面弹出软键盘时扫码枪输入会被软键盘吃掉一部分尤其在中文输入法下偶尔会丢失数字导致扫码结果不完整。后者需要厂商提供协议文档开发量稍大但稳定性和安全性都要好很多适合收银场景。蓝牙小票机基本都是走SPP协议Android系统里用BluetoothAdapter拿到设备后通过uuid建立Socket连接。需要注意的地方是很多廉价小票机一次只能支持一个连接重连时必须先把旧连接彻底关闭否则会一直提示“设备忙”。2.2 业务层设计交易、流水、状态机交易是POS的核心我把它拆成几个状态初始化、建单、支付中、支付成功、支付失败、已撤销。无论对接微信、支付宝还是银行卡刷卡都要维护同一个状态机只是支付渠道不同而已。这样设计的好处是界面可以根据状态机统一刷新不会出现“钱扣了但界面还停在支付中”这种体验极差的问题。交易流水我建议用数据库存两份一份是持久化的SQLite流水表记录每笔交易的订单号、金额、渠道、状态和请求/响应原文另一份是内存中的当前交易上下文用于支付过程中快速查询。每当状态变化先更新数据库再通知界面刷新。这里顺序千万不能反先改界面再写库的话一旦进程被系统杀掉对账时就会对不上。另外一个容易被忽略的点是金额精度。POS涉及的金额计算必须用long类型以“分”为单位存储禁止用float或double。我见过因为用double算金额导致0.58变成0.57999999的奇葩问题看起来只差一点点但日终对账的时候就是过不去。2.3 UI与交互的收银场景适配POS机的屏幕通常比手机小而且使用场景是店员单手握持的同时还要操作扫码、刷卡所以UI要着重做两点按钮尽量大关键操作有二次确认。比如“撤销交易”和“退款”这两个操作误触了很麻烦必须弹窗让操作人再次确认并且记录操作人ID。还有一个交互细节是输入法。POS设备经常用扫描抢输入条码如果扫码枪是HID模拟键盘界面上就不应该允许软键盘弹出否则会抢焦点导致扫码串数据。推荐在扫描输入界面设置android:windowSoftInputModestateAlwaysHidden并用TextWatcher监听输入结果连续扫码间隔很短时还要做一个去抖逻辑。3. 关键代码实现与踩坑记录3.1 串口通信封装代码串口这块我用的是android-serialport-api的底子核心是把配置参数和读写流程封装好。一个基本可用的串口操作类大概是这个样子public class SerialPortManager { private static final String TAG SerialPortManager; private SerialPort mSerialPort; private OutputStream mOutputStream; private InputStream mInputStream; private ExecutorService mThreadPool Executors.newSingleThreadExecutor(); public boolean open(String devicePath, int baudRate) { try { mSerialPort new SerialPort(new File(devicePath), baudRate, 0); mOutputStream mSerialPort.getOutputStream(); mInputStream mSerialPort.getInputStream(); return true; } catch (IOException e) { Log.e(TAG, open failed: devicePath); return false; } } public void send(byte[] data) { mThreadPool.execute(() - { try { mOutputStream.write(data); mOutputStream.flush(); } catch (IOException e) { Log.e(TAG, send failed); } }); } }这里有个关键点串口读写必须放到子线程。有些设备的数据量很小但如果报文比较大或者波特率低主线程直接写可能卡住界面而且频繁读写还会导致ANR。另外串口打开失败不等于设备坏了很大概率是权限问题。常见做法是在mainfest里注册设备节点路径或使用运行时申请方式让系统授予访问/ dev/ttySx的权限。实际操作中我发现部分RK方案板子在系统启动后串口设备节点需要等一小段时间才出现如果应用是开机自启动直接打开会返回FileNotFoundException。解决办法是重试机制比如每隔500ms尝试一次总共重试10次。3.2 蓝牙小票打印的编码处理蓝牙小票机的坑基本都集中在字符编码和指令协议上。市面主流小票机都支持ESC/POS指令打印中文前需要把字符转成GBK编码而不是直接用默认的UTF-8。private void printText(String text) { try { byte[] data text.getBytes(GBK); OutputStream out mBluetoothSocket.getOutputStream(); out.write(data); out.flush(); } catch (Exception e) { Log.e(Print, print error, e); } }如果直接用UTF-8编码中文会打出乱码。有些设备在出厂时固件把编码写死成GB18030那就要按GB18030转换具体看厂商协议。通常买设备时问一句“支持哪种中文字符集”比在代码里瞎试要快得多。还有一点小票打印的排版不要指望在小票机端做必须在端上先把格式算好。比如打印一张80mm宽的小票一行能放的中文字数大约是16个取决于字符宽度需要自己按字数做换行处理。我写过一个简单算法按字节长度截断字符串超过行宽就拆成多行否则不管多长都一行打印小票难看不说还浪费纸。3.3 扫码枪输入法冲突与拦截扫码枪模拟键盘输入时最经典的问题就是输入法抢占导致丢字符。有些扫码枪默认会在扫描成功后加上回车键所以监听EditText的EditorInfo.IME_ACTION_DONE就能拿到完整条码。为了彻底避开输入法问题我后来选择了直接读USB设备节点的方案。先用UsbManager拿到权限然后开一个线程循环读endpoint数据把扫描结果拼接后通过广播或回调抛给业务层。代价是每换一种扫码枪都要适配一次但收益非常明显不会再出现输入法干扰扫描速度也更快。如果初期先用HID方案快速验证流程有个折中处理在扫码输入Activity里禁用软键盘弹出并且设置EditText为不可聚焦用布局里一个全屏透明按钮承接焦点这样输入法不会主动弹出来。这个方案不完美因为有些用户还是会手动点击输入框但至少90%的扫码场景是稳的。3.4 交易流水与异常恢复POS机在真实环境里经常遇到断电、网络断开、支付超时等异常情况所以交易流水必须做到进程被杀后还能恢复到最近状态。我采用的方式是每次状态流转都记录日志void updateTransactionStatus(String orderId, int newStatus, String extra) { ContentValues values new ContentValues(); values.put(status, newStatus); values.put(extra, extra); values.put(update_time, System.currentTimeMillis()); mDb.update(transactions, values, order_id ?, new String[]{orderId}); }这里有个细节支付成功回调回来后要立即把数据库状态改成“成功”然后再去选择打印小票。如果先打印再更新状态刚好在打印时断电流水状态还是“支付中”恢复后就没法自动补打小票容易引发纠纷。对于脱机交易我采用本地缓存队列网络恢复后按时间顺序批量上送上送成功的记录标记“已同步”。但要注意本地缓存队列不能无限增长否则SQLite会越来越大启动时加载会变慢。我一般设置队列上限1000笔超过后强制提示“请尽快联网对账”。4. 常见问题排查与优化建议4.1 外设连接的稳定性问题外设连接不稳定是POS安卓版最常见的问题。蓝牙设备今天能连明天连不上多半是上一次连接没有完全释放。排查思路是先看系统蓝牙连接列表如果设备仍显示“已配对”但不是“已连接”就得在代码里先把旧的Socket关闭并调用refresh()清除缓存再重新连接。串口连接不稳定的原因则要复杂一些。有的设备节点虽然存在但读写时直接报I/O错误多数是波特率或数据位配置与硬件不匹配。例如钱箱控制通常是9600、8N1而密码键盘可能是19200、8N1。配置错了设备不会主动报错只是收不到正确数据。USB HID扫码枪偶尔不识别优先检查系统是否已把设备识别为键盘。在系统设置里看“键盘和输入法”是否能列出外接键盘如果识别不到大概率是线材供电不足。我遇到过一个案例用USB一分多线接扫码枪和密码键盘结果两个设备都时好时坏换成一个独立供电的HUB就正常了。4.2 打印与格式问题打印乱码和格式错乱是售后反馈的重灾区。乱码的原因基本是编码问题前面说了用GBK。格式错乱则很多是因为小票机设置了不同的字符宽度或行距指令没生效。统一在打印前先发送初始化指令ESC 把打印机恢复到默认状态再设置字符大小和行距。这条在我调试过的几十款小票机里通吃。还有一个容易被忽视的问题个别打印机会在每行末尾自动加一个换行导致打印内容比预期多出很多空行。这时候可以在初始化指令后根据厂商协议关闭自动换行。如果协议文档没写就试试指令ESC 2把行间距调整为默认值很多设备调整后空行问题会缓解。4.3 崩溃、性能与内存问题长时间挂机不重启是POS设备的特点所以内存泄漏比普通App更严重。我常用LeakCanary做监控线上则用一个小脚本定时记录内存占用。最常见的泄漏源是蓝牙回调持有Activity的Context改成用ApplicationContext或者弱引用之后内存曲线立刻平稳很多。性能方面收银App的卡顿主要来自数据库操作和主线程打印。流水查询、汇总报表这类耗时操作要放到子线程打印小票更要注意蓝牙写入是阻塞式的收到大订单时小票内容可能有几百行如果直接在主线程write明显能感觉到点击卡顿。我都是把整张小票数据拼好放到一个独立打印线程统一处理。崩溃方面最常见的还有SerialPort这块的地址访问冲突。多个模块同时持有串口引用GC时串口关闭另一个模块还在写就会抛出UnsatisfiedLinkError或IOException。解决方式是做一个串口引用计数close的时候确保没有其他模块在使用。4.4 安全合规与设备配置POS涉及交易数据和用户敏感信息安全不能只靠后端。设备本地日志不要打印完整的银行卡号或身份证号只保留后四位。数据库敏感字段要做加密比较实用的方式是SQLCipher整体替换成本不算高。另外发给后端的数据接口不能裸奔至少要加签名和时间戳防止请求被篡改或重放。系统层面很多OEM设备会预装无用应用最好找厂商定制一个精简ROM只保留系统服务和POS应用。如果设备已经发布出去可以通过系统拉起一个管理策略把不相关的包禁用降低后台耗电和网络泄密风险。设备配置这块我的建议是做一个初始化引导页首次开机设置好店铺号、商户号、打印纸宽度、外设波特率等参数然后保存到本地配置文件。这样换机的时候只要跑一遍初始化不需要开发人员到场改代码。我个人在实际操作中体会最深的一点是POS安卓版的项目最花时间的不是界面不是业务逻辑而是跟各种外设“脾气”打交道。同一个型号的打印机会因为出厂固件批次不同有完全不一样的行为同一个扫码枪在Android 7和Android 11上的表现也不同。所以在项目排期里一定要留出足够的外设联调时间并且把每一类外设单独做成可插拔模块一旦现场出问题就能最快定位到具体层。最后再分享一个小技巧开发阶段别只用模拟器调UI真机加外设才是主战场。模拟器里再好用的布局放到7寸商用屏上可能完全不是一回事。有条件的话把市面上主流的几种小票机、扫码枪、串口读卡器都买一台放在办公室里形成一个“外设货架”什么问题都能先在自己桌上复现就不用每次都跑现场了。本文还有配套的精品资源点击获取