背景
做的是物流相关的业务,蓝牙打印属于核心环节。主要涉及蓝牙连接打印设备,构建打印模板,构建打印队列,打印配置。
基础依赖
开始引入的基础依赖组件是bluetooth_print
,发现业务场景上不能完全满足,下载到本地自己维护二次开发。涉及Android、IOS相关原生开发,从问题上来看,主要涉及权限、配置、打印方式、ios兼容等多方面处理。
经过多次修正引入业务项目,-_-||
yaml
dependencies:
bluetooth_print:
path: ../packages/logistic-btprinter-plugin
沉浸式开发
蓝牙连接
回到设备连接上,怎么搜索到、怎么连接、怎么快捷连接?为啥会有这么多问题,因为业务员不关心连接,只想打印。直接参考手机上蓝牙连接的方式。
对于业务员来说,设备基本固定很少变更,一次连接后记忆,打开打印页面直接自动连接。连接过的设备信息记录在手机存储中,根据连接频率自动尝试连接使用最频繁的蓝牙设备。本来还想进一步区分蓝牙设备的种类,如下图,然后搜出来的设备信息根本无法区分种类,遂放弃。
具体在连接上还有很多细节,很容易遇到连接卡半天、频繁出错等问题,处理好这些才能提升用户体验。
- 状态变化的约束
- 连接失败的重试
- 连接成功的记忆
- 连接异常后的处理
js
// status ---> not_connect | connecting | connected | disconnecting
Future<void> _connectDevice(BluetoothDeviceModel d, {bool? setPrefs}) async {
await bluetoothPrint.stopScan();
// 断开已有链接
await _disconnectDevice();
if (d.address != null) {
if (d.status == 'connected') return;
// 对接蓝牙设备
setState(() {
d.status = 'connecting';
});
G.print.jsonStr(d, '连接设备');
await bluetoothPrint.connect(d);
bluetoothPrint.state.listen((state) {
switch (state) {
case BluetoothPrint.CONNECTED:
if (mounted) {
setState(() {
_connected = true;
d.status = 'connected';
_connectedDevice = d;
// 移入存储
if (setPrefs == true) {
_setDevicePrefs(_connectedDevice);
}
});
}
break;
default:
break;
}
}, onError: (object) {
G.print.info('蓝牙连接-${object.toString()}', '连接状态Error');
}, onDone: () {});
int retryTimes = 20;
while (retryTimes > 0) {
retryTimes--;
bool? isConnected = await bluetoothPrint.isConnected;
if (isConnected == null || !isConnected) {
sleep(Duration(milliseconds: 500));
continue;
} else {
_connected = true;
// 移入存储
if (setPrefs == true) {
_setDevicePrefs(_connectedDevice);
}
setState(() {
d.status = 'connected';
_connectedDevice = d;
});
G.toast('蓝牙设备连接成功');
break;
}
}
if (!_connected && mounted) {
setState(() {
d.status = 'not_connect';
});
G.toast('蓝牙设备连接失败');
}
if (!mounted) return;
} else {
setState(() {
tips = '请选择打印设备';
d.status = 'not_connect';
});
G.toast('请选择打印设备');
}
}
构建打印模板
打印模板完全是定制化的,不可能随便拿个邮政、顺风物流等打印模板就能用。打印模板对于代码来说就是一段字符串,不过遵循打印机CPCL编程,参考语法开发就行。涉及文本、位置、一维码、二维码、图片、线条等元素组合,还是需要研究一下的。
打印模板最好不要放置在项目代码中,放置到服务端存储成配置项。根据不同的业务场景可能随时可能替换,就比如当前应用对接一家业务公司就得定制化一套他们觉得OK的打印模板,其实他们就是想换LOGO而已。
构建打印队列
这个点也是很关键,因为一个订单下可能有很多运单,一个运单可能要打印两张,打印过程本身是异步过程。一个异步队列的必要条件集齐了。
维护队列的顺序、上限,因为是按需打印,需要同步维护打印状态,保证不遗漏一个单,不冗余一个单ヾ(◍°∇°◍)ノ゙。
js
// 判断设备连接情况
if (_connectedDevice.status != 'connected') return;
G.loading.show(context, text: '打印中...');
if (_waybills.length > 0) {
// 构建打印队列,维护顺序
TaskQueueUtils taskQueueUtils = TaskQueueUtils.instance;
taskQueueUtils.addListener(() {
setState(() {
_isPrinting = taskQueueUtils.isTaskRunning;
});
});
_waybills.forEach((v) {
taskQueueUtils.addTask(_printWaybill(v));
});
G.loading.hide(context);
}
打印配置
打印配置分两个场景
打印模板相关配置
单联打印、双联打印
就需要在打印模板上配置- 打印模板的宽高适配打印纸也需在打印模板上设置
自定义配置
一次的打印上限
控制队列上限即可
后记
Flutter在蓝牙打印上有多个第三方插件,结合自身业务场景慎重选择。打印细节上有优化空间,多测试多优化才能把功能做好。ヾ(◍°∇°◍)ノ゙