最近在参与公司的餐厅点餐系统开发,涉及到打印小票功能时,公司希望实现小票后端配置模板方式,最后采取如下方式实现
目前获取打印信息是采用极光推送(这种方式其实有很多隐患)
用户下单-->极光-->主设备-->打印
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mVm = obtainViewModel(this, PrinterViewModel.class);
mVm.getPrimaryPrinter();//1:进入主页,先拉取主设备id,判断当前设备是否是主设备
observeData();
JPushReceiver.NotificationHandler.execPendingTask();
appUpdateController = new AppUpdateController(this);
appUpdateController.checkUpdate();
mMediaPlayer = MediaPlayer.create(BaseApplication.getInstance(), R.raw.new_order);
}
private void observeData() {
mVm.loading.observe(this, aBoolean -> {
if (aBoolean != null) {
if (aBoolean)
showLoading("");
else
hideLoading();
}
});
mVm.isPrimaryDevice.observe(this, aBoolean -> {
if (aBoolean != null) {
if (aBoolean) {
//2:如果是主设备,再获取打印的模板信息,和打印机地址
mVm.getTemplateList();
mVm.getAllPrinters();
}
}
});
mVm.allPrintList.observe(this, printers -> {
UserModelManager.getInstance().setPrinterInfo(printers);
//auto connect when app start up
if (checkNonNull(printers)) {
//3:当拿到打印机时,如果打印机是未连接状态,先主动连接
for (Printer printer : printers) {
if (printer.getType() == Constants.PRINTER_BLE) {
if (!printer.isConnected()) {
PrinterManager.getInstance().connectBle(printer.getSign(), printer.getName());
}
}
else if (printer.getType() == Constants.PRINTER_NET) {
DeviceConnFactoryManager deviceManager = PrinterManager.getInstance().getNetManager(printer.getSign());
if (deviceManager == null || !deviceManager.getConnState()) {
new DeviceConnFactoryManager.Build().setId(printer.getSign())
.setConnMethod(DeviceConnFactoryManager.CONN_METHOD.WIFI)
.setIp(printer.getSign())
.setPort(9100)
.setName("网络打印机")
.build();
}
}
}
} else {
Log.e(TAG, "getAllPrinters is empty ");
}
});
mVm.template.observe(this, template -> {
if (checkNonNull(template)) {
UserModelManager.getInstance().setTemplateList(template.getTpl());
//4:当拿到模板信息时,将模板的版本和sp中存的version进行对比
int jsVersion = SharedPreferencesUtil.getInt(this, Constants.KEY_JS_VERSION, 0);
//5:如果网络version大于本地的version,下载新模板(也就是js文件)
if (template.getJs() != null && template.getJs().getVersion() > jsVersion) {
Data data = new Data.Builder().putString(Constants.KEY_JS_URL, template.getJs().getUrl()).build();
WorkRequest downloadJs = new OneTimeWorkRequest.Builder(DowloadJsWorker.class).setInputData(data).build();
WorkManager.getInstance().enqueue(downloadJs);
WorkManager.getInstance().getWorkInfoByIdLiveData(downloadJs.getId())
.observe(this, workInfo -> {
//6:js文件下载成功后,更新本地version号
if (workInfo!=null&&workInfo.getState() == WorkInfo.State.SUCCEEDED) {
SharedPreferencesUtil.putInt(this,Constants.KEY_JS_VERSION,template.getJs().getVersion());
}
});
}
}
});
}
接下来就是实现打印操作
- 接收到推送时,判断是通知还是具体的打印数据
String data = bundle.getString(JPushInterface.EXTRA_MESSAGE);//自定义消息中的推送内容
try {
NotificationInfo notificationInfo = new Gson().fromJson(data
, NotificationInfo.class);
LogUtils.e("Notification data is :\n" + data);
if (APP_NOTICE.equals(notificationInfo.channel)) {
NotificationHandler.notificationDatas.put(msgId, notificationInfo);
EventUtils.post(new NotificationEvent(msgId));
} else if (PRINT_NOTICE.equals(notificationInfo.channel)) {
NotificationHandler.print(notificationInfo);
}
} catch (Exception e) {
LogUtils.e(e);
}
根据指示,判断打印什么单据
public static void print(NotificationInfo notificationInfo) {
JsonElement data = notificationInfo.msg.get("data");
PrintData printData = new Gson().fromJson(data, PrintData.class);
//并没有将打印数据解析成一个类,而是用String来接收打印数据,这样的好处是,无所谓后端如何更改打印数据的类型,前端不关心,直接丢给js解析成前端需要的格式就好了!
String body = data.getAsJsonObject().get("body").toString();
//supportRules是个int数据,1代表客看单,2 结账单,3制作单 可以动态配置打什么单据!
JsonArray supportRules = notificationInfo.msg.get("supportRules").getAsJsonArray();
for (JsonElement rule : supportRules) {
PrintTools.PrintData pt=new PrintTools.PrintData();
pt.rule=rule.getAsInt();
pt.printer_id=printData.printer_id;
pt.body=body;
pt.store_name=printData.store_name;
pt.u_t=printData.u_t;
//打印操作
PrintTools.print(pt);
}
}
}
private class NotificationInfo {
private String channel;
private String title;
private Map<String, JsonElement> msg;
}
打印操作
public static void print(@NonNull PrintData printData) {
Observable.create((ObservableOnSubscribe<String>) emitter -> {
//1:读取本地js文件
String jsContent = getFileContent(Constants.JS_FILE_PATH);
if (TextUtils.isEmpty(jsContent)) {
emitter.onError(new Exception("读取js文件出错或Js文件内容为空!"));
}
emitter.onNext(jsContent);
emitter.onComplete();
})
.flatMap((Function<String, ObservableSource<Wrapper>>) jsContent -> {
//2:读取成功后,获取打印机信息
Printer printer = UserModelManager.getInstance().getPrinter(printData.printer_id);
if (printer != null) {
//使用J2V8引擎来运行js
V8 v8 = V8.createV8Runtime();
v8.executeScript(jsContent);
//3:给js提供android日志回调,方便发现问题
v8.registerJavaMethod(new JavaCallback(), "log", "log", new Class<?>[]{String.class});
int paperWidth = UserModelManager.getInstance().getPrinter(printData.printer_id).getPaper_width();
//设置一行打印的字节数
JSONObject jo = new JSONObject();
if (paperWidth == 58) {
jo.put("pageWidth", LINE_BYTE_SIZE_58);
} else {
jo.put("pageWidth", LINE_BYTE_SIZE_88);
}
//打印数据
String data = printData.body;
//模板
String template = "";
List<PrintTemplateBean.TemplateBean> templateList = UserModelManager.getInstance().getTemplateList();
if (!checkNonNull(templateList)) {
Log.e(TAG, "打印模板不存在!");
} else {
for (PrintTemplateBean.TemplateBean bean : templateList) {
if (bean.getType() == printData.rule) {
template = bean.getLayouts();
break;
}
}
}
if (!checkNonNull(template)) {
Log.e(TAG, "template error: templateData is empty");
}
//4:得到js解析后的打印数据
String printStr = (String) v8.executeJSFunction("run", jo.toString(), data, template);
Log.e(TAG, "print: \n" + printStr);
v8.release();
//5:将打印数据解析成PrintLineData数组,一个PrintLineData就是一行!
List<PrintLineData> datas = GsonUtils.gsonToList(printStr, PrintLineData.class);
Wrapper wrapper = new Wrapper();
wrapper.printer = printer;
wrapper.data = datas;
return Observable.just(wrapper);
} else {
Log.e(TAG, "未找到 id为" + printData.printer_id + "的打印机!");
}
return Observable.just(new Wrapper());
})
.subscribeOn(Schedulers.io())
.subscribe(wrapper -> {
if (checkNonNull(wrapper.data)) {
//6:开始连接打印机,进行打印
print(wrapper);
}
}, throwable -> Log.e(TAG, "print error ", throwable));
}
//打印!!!
private static void print(Wrapper wrapper) {
EscCommand esc = new EscCommand();
esc.addInitializePrinter();
for (PrintLineData line : wrapper.data) {
if (checkNonNull(line.getColumns())) {
if (line.getColumns().size() > 1) {//多列
for (PrintLineData.ColumnsBean column : line.getColumns()) {
//加粗
boolean isBold = column.getBold() == 1;
esc.addSelectPrintModes(EscCommand.FONT.FONTA, isBold ? EscCommand.ENABLE.ON : EscCommand.ENABLE.OFF, EscCommand.ENABLE.OFF, EscCommand.ENABLE.OFF, EscCommand.ENABLE.OFF);
//倍高倍宽
boolean isLarge = column.getFontSize().equals("large");
esc.addSelectPrintModes(EscCommand.FONT.FONTA, EscCommand.ENABLE.OFF, isLarge ? EscCommand.ENABLE.ON : EscCommand.ENABLE.OFF, isLarge ? EscCommand.ENABLE.ON : EscCommand.ENABLE.OFF, EscCommand.ENABLE.OFF);
esc.addSelectJustification(column.getTextAlignment());
esc.addText(column.getContent());
}
esc.addText("\n");
} else {
PrintLineData.ColumnsBean column = line.getColumns().get(0);
//加粗
boolean isBold = column.getBold() == 1;
esc.addSelectPrintModes(EscCommand.FONT.FONTA, isBold ? EscCommand.ENABLE.ON : EscCommand.ENABLE.OFF, EscCommand.ENABLE.OFF, EscCommand.ENABLE.OFF, EscCommand.ENABLE.OFF);
//倍高倍宽
boolean isLarge = column.getFontSize().equals("large");
esc.addSelectPrintModes(EscCommand.FONT.FONTA, EscCommand.ENABLE.OFF, isLarge ? EscCommand.ENABLE.ON : EscCommand.ENABLE.OFF, isLarge ? EscCommand.ENABLE.ON : EscCommand.ENABLE.OFF, EscCommand.ENABLE.OFF);
esc.addSelectJustification(column.getTextAlignment());
esc.addText(column.getContent() + "\n");
}
}
}
esc.addPrintAndFeedLines((byte) 1);
esc.addCutPaper();
Vector<Byte> datas = esc.getCommand();
// 发送数据
DeviceConnFactoryManager manager = PrinterManager.getInstance().getManager(wrapper.printer);
if (manager != null) {
manager.openPort();
manager.sendDataImmediately(datas);
}
}
public static class JavaCallback {
public void log(String error) {
Log.e(TAG, "print lllllll ," + error);
}
}
public static class PrintData {
public String printer_id;
public String store_name;
public String operator_name;
public int rule;
public String body;
/**
* 打印时间:【2018-09-23 17:55:09】
*/
public String u_t;
}
这是最开始的打印功能实现,还有很多bug和问题,之后会慢慢记录出来!