Android外卖打印机的控制与封装

1,042 阅读7分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

Android如何控制打印机

项目背景

疫情下,大部分市民都拒绝隔离,国内外卖送单更流行,公司随大流开展外卖模块,手机点单,商铺接单,打印,骑手接单导航等一系列app倒是一个不差

哎,东南亚小国,大家还是不太习惯外卖,更喜欢堂食。项目大概运营了半年? 目前已经死翘翘了,只剩下骑手还在苟延残喘,要不然也轮不到我在此分享了。

这里先讲一下商铺端 打印机的控制,一般这种打印外卖订单的打印机 分为一体机和蓝牙打印机。

一体机,内部自带的打印机,内部使用串口通信,传输byte[]数据,然后控制打印。

蓝牙设备,需要Android设备连接蓝牙,使用蓝牙通信,传输byte[]数据,然后控制打印。

一般厂商会提供控制打印的SDK,如我们购买的Sunmi的打印机。

   implementation 'com.sunmi:printerlibrary:1.0.7'

SDK内部有串口通信的逻辑,传输数据的逻辑是一样的。这里先看看蓝牙连接的代码:

public class BluetoothUtil {

    private static final UUID PRINTER_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");

    private static final String Innerprinter_Address = "00:11:22:33:44:55";

    public static boolean isBlueToothPrinter = false;

    private static BluetoothSocket bluetoothSocket;

    private static BluetoothAdapter getBTAdapter() {
        return BluetoothAdapter.getDefaultAdapter();
    }

    private static BluetoothDevice getDevice(BluetoothAdapter bluetoothAdapter) {
        BluetoothDevice innerprinter_device = null;
        Set<BluetoothDevice> devices = bluetoothAdapter.getBondedDevices();
        for (BluetoothDevice device : devices) {
            if (device.getAddress().equals(Innerprinter_Address)) {
                innerprinter_device = device;
                break;
            }
        }
        return innerprinter_device;
    }

    private static BluetoothSocket getSocket(BluetoothDevice device) throws IOException {
        BluetoothSocket socket;
        socket = device.createRfcommSocketToServiceRecord(PRINTER_UUID);
        socket.connect();
        return  socket;
    }

    /**
     * connect bluetooth
     */
    public static boolean connectBlueTooth(Context context) {
        if (bluetoothSocket == null) {
            if (getBTAdapter() == null) {
                Toast.makeText(context,  R.string.toast_3, Toast.LENGTH_SHORT).show();
                return false;
            }
            if (!getBTAdapter().isEnabled()) {
                Toast.makeText(context, R.string.toast_4, Toast.LENGTH_SHORT).show();
                return false;
            }
            BluetoothDevice device;
            if ((device = getDevice(getBTAdapter())) == null) {
                Toast.makeText(context, R.string.toast_5, Toast.LENGTH_SHORT).show();
                return false;
            }

            try {
                bluetoothSocket = getSocket(device);
            } catch (IOException e) {
                Toast.makeText(context, R.string.toast_6, Toast.LENGTH_SHORT).show();
                return false;
            }
        }
        return true;
    }

    /**
     * disconnect bluethooth
     */
    public static void disconnectBlueTooth(Context context) {
        if (bluetoothSocket != null) {
            try {
                OutputStream out = bluetoothSocket.getOutputStream();
                out.close();
                bluetoothSocket.close();
                bluetoothSocket = null;
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     *  send esc cmd
     */
    public static void sendData(byte[] bytes) {
        if (bluetoothSocket != null) {
            OutputStream out = null;
            try {
                out = bluetoothSocket.getOutputStream();
                out.write(bytes, 0, bytes.length);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }else{
    
        }
    }
}

打印服务Byte数据处理工具类:

public class BytesUtil {

	//字节流转16进制字符串
	public static String getHexStringFromBytes(byte[] data) {
		if (data == null || data.length <= 0) {
			return null;
		}
		String hexString = "0123456789ABCDEF";
		int size = data.length * 2;
		StringBuilder sb = new StringBuilder(size);
		for (int i = 0; i < data.length; i++) {
			sb.append(hexString.charAt((data[i] & 0xF0) >> 4));
			sb.append(hexString.charAt((data[i] & 0x0F) >> 0));
		}
		return sb.toString();
	}

	//单字符转字节
	private static byte charToByte(char c) {
		return (byte) "0123456789ABCDEF".indexOf(c);
	}

	//16进制字符串转字节数组
	@SuppressLint("DefaultLocale")
	public static byte[] getBytesFromHexString(String hexstring){
		if(hexstring == null || hexstring.equals("")){
			return null;
		}
		hexstring = hexstring.replace(" ", "");
		hexstring = hexstring.toUpperCase();
		int size = hexstring.length()/2;
		char[] hexarray = hexstring.toCharArray();
		byte[] rv = new byte[size];
		for(int i=0; i<size; i++){
			int pos = i * 2;
			rv[i] = (byte) (charToByte(hexarray[pos]) << 4 | charToByte(hexarray[pos + 1]));
		}
		return rv;
	}

	//十进制字符串转字节数组
	@SuppressLint("DefaultLocale")
	public static byte[] getBytesFromDecString(String decstring){
		if(decstring == null || decstring.equals("")){
			return null;
		}
		decstring = decstring.replace(" ", "");
		int size = decstring.length()/2;
		char[] decarray = decstring.toCharArray();
		byte[] rv = new byte[size];
		for(int i=0; i<size; i++){
			int pos = i * 2;
			rv[i] = (byte) (charToByte(decarray[pos])*10 + charToByte(decarray[pos + 1]));
		}
		return rv;
	}

	//字节数组组合操作1
	public static byte[] byteMerger(byte[] byte_1, byte[] byte_2) {
		byte[] byte_3 = new byte[byte_1.length + byte_2.length];
		System.arraycopy(byte_1, 0, byte_3, 0, byte_1.length);
		System.arraycopy(byte_2, 0, byte_3, byte_1.length, byte_2.length);
		return byte_3;
	}

	//字节数组组合操作2
	public static byte[] byteMerger(byte[][] byteList) {

		int length = 0;
		for (int i = 0; i < byteList.length; i++) {
			length += byteList[i].length;
		}
		byte[] result = new byte[length];

		int index = 0;
		for (int i = 0; i < byteList.length; i++) {
			byte[] nowByte = byteList[i];
			for (int k = 0; k < byteList[i].length; k++) {
				result[index] = nowByte[k];
				index++;
			}
		}
		for (int i = 0; i < index; i++) {
			// CommonUtils.LogWuwei("", "result[" + i + "] is " + result[i]);
		}
		return result;
	}

	//生成表格字节流
	public static byte[] initTable(int h, int w){
		int hh = h * 32;
		int ww = w * 4;

		byte[] data = new byte[ hh * ww + 5];


		data[0] = (byte)ww;//xL
		data[1] = (byte)(ww >> 8);//xH
		data[2] = (byte)hh;
		data[3] = (byte)(hh >> 8);

		int k = 4;
		int m = 31;
		for(int i=0; i<h; i++){
			for(int j=0; j<w; j++){
				data[k++] = (byte)0xFF;
				data[k++] = (byte)0xFF;
				data[k++] = (byte)0xFF;
				data[k++] = (byte)0xFF;
			}
			if(i == h-1) m =30;
			for(int t=0; t< m; t++){
				for(int j=0; j<w-1; j++){
					data[k++] = (byte)0x80;
					data[k++] = (byte)0;
					data[k++] = (byte)0;
					data[k++] = (byte)0;
				}
				data[k++] = (byte)0x80;
				data[k++] = (byte)0;
				data[k++] = (byte)0;
				data[k++] = (byte)0x01;
			}
		}
		for(int j=0; j<w; j++){
			data[k++] = (byte)0xFF;
			data[k++] = (byte)0xFF;
			data[k++] = (byte)0xFF;
			data[k++] = (byte)0xFF;
		}
		data[k++] = 0x0A;
		return data;
	}

	/**
	 * 将bitmap图转换为头四位有宽高的光栅位图
	 */
	public static byte[] getBytesFromBitMap(Bitmap bitmap) {
		int width = bitmap.getWidth();
		int height = bitmap.getHeight();
		int bw = (width - 1) / 8 + 1;

		byte[] rv = new byte[height * bw + 4];
		rv[0] = (byte) bw;//xL
		rv[1] = (byte) (bw >> 8);//xH
		rv[2] = (byte) height;
		rv[3] = (byte) (height >> 8);

		int[] pixels = new int[width * height];
		bitmap.getPixels(pixels, 0, width, 0, 0, width, height);

		for (int i = 0; i < height; i++) {
			for (int j = 0; j < width; j++) {
				int clr = pixels[width * i + j];
				int red = (clr & 0x00ff0000) >> 16;
				int green = (clr & 0x0000ff00) >> 8;
				int blue = clr & 0x000000ff;
				byte gray = (RGB2Gray(red, green, blue));
				rv[bw*i + j/8 + 4] = (byte) (rv[bw*i + j/8 + 4] | (gray << (7 - j % 8)));
			}
		}

		return rv;
	}

	/**
	 * 将bitmap转成按mode指定的N点行数据
	 */
	public static byte[] getBytesFromBitMap(Bitmap bitmap, int mode) {
		int width = bitmap.getWidth();
		int height = bitmap.getHeight();
		int[] pixels = new int[width*height];
		if(mode == 0 || mode == 1){
			byte[] res = new byte[width*height/8 + 5*height/8];
			bitmap.getPixels(pixels, 0, width, 0, 0, width, height);
			for(int i = 0; i < height/8; i++){
				res[0 + i*(width+5)] = 0x1b;
				res[1 + i*(width+5)] = 0x2a;
				res[2 + i*(width+5)] = (byte) mode;
				res[3 + i*(width+5)] = (byte) (width%256);
				res[4 + i*(width+5)] = (byte) (width/256);
				for(int j = 0; j < width; j++){
					byte gray = 0;
					for(int m = 0; m < 8; m++){
						int clr = pixels[j + width*(i*8+m)];
						int red = (clr & 0x00ff0000) >> 16;
						int green = (clr & 0x0000ff00) >> 8;
						int blue = clr & 0x000000ff;
						gray = (byte) ((RGB2Gray(red, green, blue)<<(7-m))|gray);
					}
					res[5 + j + i*(width+5)] = gray;
				}
			}
			return res;
		}else if(mode == 32 || mode == 33){
			byte[] res = new byte[width*height/8 + 5*height/24];
			bitmap.getPixels(pixels, 0, width, 0, 0, width, height);
			for(int i = 0; i < height/24; i++){
				res[0 + i*(width*3+5)] = 0x1b;
				res[1 + i*(width*3+5)] = 0x2a;
				res[2 + i*(width*3+5)] = (byte) mode;
				res[3 + i*(width*3+5)] = (byte) (width%256);
				res[4 + i*(width*3+5)] = (byte) (width/256);
				for(int j = 0; j < width; j++){
					for(int n = 0; n < 3; n++){
						byte gray = 0;
						for(int m = 0; m < 8; m++){
							int clr = pixels[j + width*(i*24 + m + n*8)];
							int red = (clr & 0x00ff0000) >> 16;
							int green = (clr & 0x0000ff00) >> 8;
							int blue = clr & 0x000000ff;
							gray = (byte) ((RGB2Gray(red, green, blue)<<(7-m))|gray);
						}
						res[5 + j*3 + i*(width*3+5) + n] = gray;
					}
				}
			}
			return res;
		}else{
			return new byte[]{0x0A};
		}

	}


	private static byte RGB2Gray(int r, int g, int b) {
		return (false ? ((int) (0.29900 * r + 0.58700 * g + 0.11400 * b) > 200)
				: ((int) (0.29900 * r + 0.58700 * g + 0.11400 * b) < 200)) ? (byte) 1 : (byte) 0;
	}

	/**
	 * 生成间断性黑块数据
	 * @param w : 打印纸宽度, 单位点
	 * @return
	 */
	public static byte[] initBlackBlock(int w){
		int ww = (w + 7)/8 ;
		int n = (ww + 11)/12;
		int hh = n * 24;
		byte[] data = new byte[ hh * ww + 5];

		data[0] = (byte)ww;//xL
		data[1] = (byte)(ww >> 8);//xH
		data[2] = (byte)hh;
		data[3] = (byte)(hh >> 8);

		int k = 4;
		for(int i=0; i < n; i++){
			for(int j=0; j<24; j++){
				for(int m =0; m<ww; m++){
					if(m/12 == i){
						data[k++] = (byte)0xFF;
					}else{
						data[k++] = 0;
					}
				}
			}
		}
		data[k++] = 0x0A;
		return data;
	}

	/**
	 * 生成一大块黑块数据
	 * @param h : 黑块高度, 单位点
	 * @param w : 黑块宽度, 单位点, 8的倍数
	 * @return
	 */
	public static byte[] initBlackBlock(int h, int w){
		int hh = h;
		int ww = (w - 1)/8 + 1;
		byte[] data = new byte[ hh * ww + 6];

		data[0] = (byte)ww;//xL
		data[1] = (byte)(ww >> 8);//xH
		data[2] = (byte)hh;
		data[3] = (byte)(hh >> 8);

		int k = 4;
		for(int i=0; i<hh; i++){
			for(int j=0; j<ww; j++){
				data[k++] = (byte)0xFF;
			}
		}
		data[k++] = 0x00;data[k++] = 0x00;
		return data;
	}
}

打印服务的入口与控制类:

public class SunmiPrintHelper {

    public static int NoSunmiPrinter = 0x00000000;
    public static int CheckSunmiPrinter = 0x00000001;
    public static int FoundSunmiPrinter = 0x00000002;
    public static int LostSunmiPrinter = 0x00000003;

    /**
     * sunmiPrinter means checking the printer connection status
     */
    public int sunmiPrinter = CheckSunmiPrinter;
    /**
     * SunmiPrinterService for API
     */
    private SunmiPrinterService sunmiPrinterService;

    private static SunmiPrintHelper helper = new SunmiPrintHelper();

    private SunmiPrintHelper() {
    }

    public static SunmiPrintHelper getInstance() {
        return helper;
    }

    private InnerPrinterCallback innerPrinterCallback = new InnerPrinterCallback() {
        @Override
        protected void onConnected(SunmiPrinterService service) {
            sunmiPrinterService = service;
            checkSunmiPrinterService(service);
        }

        @Override
        protected void onDisconnected() {
            sunmiPrinterService = null;
            sunmiPrinter = LostSunmiPrinter;
        }
    };

    /**
     * 打印机服务初始化-在Application初始化
     * init sunmi print service
     */
    public void initSunmiPrinterService(Context context) {
        try {
            boolean ret = InnerPrinterManager.getInstance().bindService(context,
                    innerPrinterCallback);
            if (!ret) {
                sunmiPrinter = NoSunmiPrinter;
            }
        } catch (InnerPrinterException e) {
            e.printStackTrace();
        }
    }

    /**
     * 延时打印机服务初始化
     * deInit sunmi print service
     */
    public void deInitSunmiPrinterService(Context context) {
        try {
            if (sunmiPrinterService != null) {
                InnerPrinterManager.getInstance().unBindService(context, innerPrinterCallback);
                sunmiPrinterService = null;
                sunmiPrinter = LostSunmiPrinter;
            }
        } catch (InnerPrinterException e) {
            e.printStackTrace();
        }
    }

    /**
     * 检测打印机的连接
     * Check the printer connection,
     * like some devices do not have a printer but need to be connected to the cash drawer through a print service
     */
    private void checkSunmiPrinterService(SunmiPrinterService service) {
        boolean ret = false;
        try {
            ret = InnerPrinterManager.getInstance().hasPrinter(service);
        } catch (InnerPrinterException e) {
            e.printStackTrace();
        }
        sunmiPrinter = ret ? FoundSunmiPrinter : NoSunmiPrinter;
    }

    /**
     * Some conditions can cause interface calls to fail
     * For example: the version is too low、device does not support
     * You can see {@link ExceptionConst}
     * So you have to handle these exceptions
     */
    private void handleRemoteException(RemoteException e) {
        //TODO process when get one exception
    }

    /**
     * 发送指令打印
     * send esc cmd
     */
    public void sendRawData(byte[] data) {
        if (sunmiPrinterService == null) {
            //TODO Service disconnection processing
            return;
        }
        try {
            sunmiPrinterService.sendRAWData(data, null);
        } catch (RemoteException e) {
            handleRemoteException(e);
        }
    }

    /**
     * 打印机在没有切纸器的机器上切纸并抛出异常
     * Printer cuts paper and throws exception on machines without a cutter
     */
    public void cutpaper() {
        if (sunmiPrinterService == null) {
            //TODO Service disconnection processing
            return;
        }
        try {
            sunmiPrinterService.cutPaper(null);
        } catch (RemoteException e) {
            handleRemoteException(e);
        }
    }

    /**
     * 初始化打印机
     * Initialize the printer
     * All style settings will be restored to default
     */
    public void initPrinter() {
        if (sunmiPrinterService == null) {
            //TODO Service disconnection processing
            return;
        }
        try {
            sunmiPrinterService.printerInit(null);
        } catch (RemoteException e) {
            handleRemoteException(e);
        }
    }

    /**
     * *送纸三行
     * *当行距设置为0时未禁用
     */
    public void print3Line() {
        if (sunmiPrinterService == null) {
            //TODO Service disconnection processing
            return;
        }

        try {
            sunmiPrinterService.lineWrap(3, null);
        } catch (RemoteException e) {
            handleRemoteException(e);
        }
    }

    /**
     * 获取打印机序列号
     */
    public String getPrinterSerialNo() {
        if (sunmiPrinterService == null) {
            //TODO Service disconnection processing
            return "";
        }
        try {
            return sunmiPrinterService.getPrinterSerialNo();
        } catch (RemoteException e) {
            handleRemoteException(e);
            return "";
        }
    }

    /**
     * 获取设备模型
     */
    public String getDeviceModel() {
        if (sunmiPrinterService == null) {
            //TODO Service disconnection processing
            return "";
        }
        try {
            return sunmiPrinterService.getPrinterModal();
        } catch (RemoteException e) {
            handleRemoteException(e);
            return "";
        }
    }

    /**
     * 获取固件版本
     */
    public String getPrinterVersion() {
        if (sunmiPrinterService == null) {
            //TODO Service disconnection processing
            return "";
        }
        try {
            return sunmiPrinterService.getPrinterVersion();
        } catch (RemoteException e) {
            handleRemoteException(e);
            return "";
        }
    }

    /**
     * 获取纸张规格
     */
    public String getPrinterPaper() {
        if (sunmiPrinterService == null) {
            //TODO Service disconnection processing
            return "";
        }
        try {
            return sunmiPrinterService.getPrinterPaper() == 1 ? "58mm" : "80mm";
        } catch (RemoteException e) {
            handleRemoteException(e);
            return "";
        }
    }

    /**
     * G获取纸张规格
     */
    public void getPrinterHead(InnerResultCallbcak callbcak) {
        if (sunmiPrinterService == null) {
            //TODO Service disconnection processing
            return;
        }
        try {
            sunmiPrinterService.getPrinterFactory(callbcak);
        } catch (RemoteException e) {
            handleRemoteException(e);
        }
    }

    /**
     * 获取启动后的打印距离
     * 有些设备需要异步获取
     */
    public String getPrinterDistance(InnerResultCallbcak callback) {
        if (sunmiPrinterService == null) {
            //TODO Service disconnection processing
            return "";
        }
        try {
            return sunmiPrinterService.getPrintedLength(callback) + "mm";
        } catch (RemoteException e) {
            handleRemoteException(e);
            return "";
        }
    }

    /**
     * 设置打印机校准
     */
    public void setAlign(int align) {
        if (sunmiPrinterService == null) {
            //TODO Service disconnection processing
            return;
        }
        try {
            sunmiPrinterService.setAlignment(align, null);
        } catch (RemoteException e) {
            handleRemoteException(e);
        }
    }

    /**
     * 由于纸张舱口和打印头之间的距离,
     * 纸张需要自动送出
     * 但是如果Api不支持它,它将被打印三行来代替
     */
    public void feedPaper() {
        if (sunmiPrinterService == null) {
            //TODO Service disconnection processing
            return;
        }

        try {
            sunmiPrinterService.autoOutPaper(null);
        } catch (RemoteException e) {
            print3Line();
        }
    }

    /**
     * 打印文本
     * print text
     * setPrinterStyle Api require V4.2.22 or later, So use esc cmd instead when not supported
     * More settings reference documentation {@link WoyouConsts}
     */
    public void printText(String content, float size, boolean isBold, boolean isUnderLine) {
        if (sunmiPrinterService == null) {
            //TODO Service disconnection processing
            return;
        }

        try {
            try {
                sunmiPrinterService.setPrinterStyle(WoyouConsts.ENABLE_BOLD, isBold ?
                        WoyouConsts.ENABLE : WoyouConsts.DISABLE);
            } catch (RemoteException e) {
                if (isBold) {
                    sunmiPrinterService.sendRAWData(ESCUtil.boldOn(), null);
                } else {
                    sunmiPrinterService.sendRAWData(ESCUtil.boldOff(), null);
                }
            }
            try {
                sunmiPrinterService.setPrinterStyle(WoyouConsts.ENABLE_UNDERLINE, isUnderLine ?
                        WoyouConsts.ENABLE : WoyouConsts.DISABLE);
            } catch (RemoteException e) {
                if (isUnderLine) {
                    sunmiPrinterService.sendRAWData(ESCUtil.underlineWithOneDotWidthOn(), null);
                } else {
                    sunmiPrinterService.sendRAWData(ESCUtil.underlineOff(), null);
                }
            }
            sunmiPrinterService.printTextWithFont(content, null, size, null);
        } catch (RemoteException e) {
            e.printStackTrace();
        }

    }

    /**
     * 打印条形码
     * print Bar Code
     */
    public void printBarCode(String data, int symbology, int height, int width, int textposition) {
        if (sunmiPrinterService == null) {
            //TODO Service disconnection processing
            return;
        }

        try {
            sunmiPrinterService.printBarCode(data, symbology, height, width, textposition, null);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    /**
     * 打印二维码
     * print Qr Code
     */
    public void printQr(String data, int modulesize, int errorlevel) {
        if (sunmiPrinterService == null) {
            //TODO Service disconnection processing
            return;
        }

        try {
            sunmiPrinterService.printQRCode(data, modulesize, errorlevel, null);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    /**
     * 打印表格
     * Print a row of a table
     */
    public void printTable(String[] txts, int[] width, int[] align) {
        if (sunmiPrinterService == null) {
            //TODO Service disconnection processing
            return;
        }

        try {
            sunmiPrinterService.printColumnsString(txts, width, align, null);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    /**
     * 打印图片
     * Print pictures and text in the specified orde
     * After the picture is printed,
     * the line feed output needs to be called,
     * otherwise it will be saved in the cache
     * In this example, the image will be printed because the print text content is added
     */
    public void printBitmap(Bitmap bitmap, int orientation) {
        if (sunmiPrinterService == null) {
            //TODO Service disconnection processing
            return;
        }

        try {
            if (orientation == 0) {
                sunmiPrinterService.printBitmap(bitmap, null);
                sunmiPrinterService.printText("横向排列\n", null);
                sunmiPrinterService.printBitmap(bitmap, null);
                sunmiPrinterService.printText("横向排列\n", null);
            } else {
                sunmiPrinterService.printBitmap(bitmap, null);
                sunmiPrinterService.printText("\n纵向排列\n", null);
                sunmiPrinterService.printBitmap(bitmap, null);
                sunmiPrinterService.printText("\n纵向排列\n", null);
            }
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取当前打印机是否处于黑色标记模式
     * Gets whether the current printer is in black mark mode
     */
    public boolean isBlackLabelMode() {
        if (sunmiPrinterService == null) {
            //TODO Service disconnection processing
            return false;
        }
        try {
            return sunmiPrinterService.getPrinterMode() == 1;
        } catch (RemoteException e) {
            return false;
        }
    }

    /**
     * 事务打印:
     * Transaction printing:
     * enter->print->exit(get result) or
     * enter->first print->commit(get result)->twice print->commit(get result)->exit(don't care
     * result)
     */
    public void printTrans(Context context, InnerResultCallbcak callbcak) {
        if (sunmiPrinterService == null) {
            //TODO Service disconnection processing
            return;
        }

        try {
            sunmiPrinterService.enterPrinterBuffer(true);
            printExample();
            sunmiPrinterService.exitPrinterBufferWithCallback(true, callbcak);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }


    // ============================  其他的设备支持 begin ↓  =============================

    /**
     * Open cash box
     * This method can be used on Sunmi devices with a cash drawer interface
     * If there is no cash box (such as V1、P1) or the call fails, an exception will be thrown
     * <p>
     * Reference to https://docs.sunmi.com/general-function-modules/external-device-debug/cash-box-driver/}
     */
    public void openCashBox() {
        if (sunmiPrinterService == null) {
            //TODO Service disconnection processing
            return;
        }

        try {
            sunmiPrinterService.openDrawer(null);
        } catch (RemoteException e) {
            handleRemoteException(e);
        }
    }

    /**
     * LCD screen control
     *
     * @param flag 1 —— Initialization
     *             2 —— Light up screen
     *             3 —— Extinguish screen
     *             4 —— Clear screen contents
     */
    public void controlLcd(int flag) {
        if (sunmiPrinterService == null) {
            //TODO Service disconnection processing
            return;
        }

        try {
            sunmiPrinterService.sendLCDCommand(flag);
        } catch (RemoteException e) {
            handleRemoteException(e);
        }
    }

    /**
     * Display text SUNMI,font size is 16 and format is fill
     * sendLCDFillString(txt, size, fill, callback)
     * Since the screen pixel height is 40, the font should not exceed 40
     */
    public void sendTextToLcd() {
        if (sunmiPrinterService == null) {
            //TODO Service disconnection processing
            return;
        }

        try {
            sunmiPrinterService.sendLCDFillString("SUNMI", 16, true, new InnerLcdCallback() {
                @Override
                public void onRunResult(boolean show) throws RemoteException {
                    //TODO handle result
                }
            });
        } catch (RemoteException e) {
            e.printStackTrace();
        }

    }

    /**
     * Display two lines and one empty line in the middle
     */
    public void sendTextsToLcd() {
        if (sunmiPrinterService == null) {
            //TODO Service disconnection processing
            return;
        }

        try {
            String[] texts = {"SUNMI", null, "SUNMI"};
            int[] align = {2, 1, 2};
            sunmiPrinterService.sendLCDMultiString(texts, align, new InnerLcdCallback() {
                @Override
                public void onRunResult(boolean show) throws RemoteException {
                    //TODO handle result
                }
            });
        } catch (RemoteException e) {
            e.printStackTrace();
        }

    }

    /**
     * Display one 128x40 pixels and opaque picture
     */
    public void sendPicToLcd(Bitmap pic) {
        if (sunmiPrinterService == null) {
            //TODO Service disconnection processing
            return;
        }

        try {
            sunmiPrinterService.sendLCDBitmap(pic, new InnerLcdCallback() {
                @Override
                public void onRunResult(boolean show) throws RemoteException {
                    //TODO handle result
                }
            });
        } catch (RemoteException e) {
            e.printStackTrace();
        }

    }
}

拿到这个入口控制类,我们就能打印自定义格式的小票:

 public void printExample() {

        if (sunmiPrinterService == null) {
            return;
        }

        try {
            int paper = sunmiPrinterService.getPrinterPaper();

            sunmiPrinterService.printerInit(null);
            sunmiPrinterService.setAlignment(1, null);  //1代表居中打印
            sunmiPrinterService.printText("测试样张\n", null);

            sunmiPrinterService.lineWrap(1, null);   //走纸一行
            sunmiPrinterService.setAlignment(0, null);  //0代表左侧打印

            try {
                sunmiPrinterService.setPrinterStyle(WoyouConsts.SET_LINE_SPACING, 0);   //设置文本默认行间距
            } catch (RemoteException e) {
                sunmiPrinterService.sendRAWData(new byte[]{0x1B, 0x33, 0x00}, null);
            }

            //打印自定义的宽度
            sunmiPrinterService.printTextWithFont("说明:这是一个自定义的小票样式例子,开发者可以仿照此进行自己的构建\n", null, 12, null);

            //打印横线
            if (paper == 1) {
                sunmiPrinterService.printText("--------------------------------\n", null);
            } else {
                sunmiPrinterService.printText("------------------------------------------------\n", null);
            }

            try {
                sunmiPrinterService.setPrinterStyle(WoyouConsts.ENABLE_BOLD, WoyouConsts.ENABLE);  //设置文本加粗
            } catch (RemoteException e) {
                sunmiPrinterService.sendRAWData(ESCUtil.boldOn(), null);
            }

            String txts[] = new String[]{"商品", "数量", "价格"};
            int width[] = new int[]{3, 1, 1};  //代表权重
            int align[] = new int[]{0, 1, 2};
            sunmiPrinterService.printColumnsString(txts, width, align, null);
            try {
                sunmiPrinterService.setPrinterStyle(WoyouConsts.ENABLE_BOLD, WoyouConsts.DISABLE);  //设置取消文字加粗
            } catch (RemoteException e) {
                sunmiPrinterService.sendRAWData(ESCUtil.boldOff(), null);
            }

            //打印横线
            if (paper == 1) {
                sunmiPrinterService.printText("--------------------------------\n", null);
            } else {
                sunmiPrinterService.printText("------------------------------------------------\n", null);
            }

            //开始打印商品
            txts[0] = "汉堡";
            txts[1] = "X2";
            txts[2] = "17¥";
            sunmiPrinterService.printColumnsString(txts, width, align, null);
            txts[0] = "可乐";
            txts[1] = "X2";
            txts[2] = "10¥";
            sunmiPrinterService.printColumnsString(txts, width, align, null);
            txts[0] = "薯条";
            txts[1] = "X2";
            txts[2] = "11¥";
            sunmiPrinterService.printColumnsString(txts, width, align, null);
            txts[0] = "炸鸡";
            txts[1] = "X2";
            txts[2] = "11¥";
            sunmiPrinterService.printColumnsString(txts, width, align, null);
            txts[0] = "圣代";
            txts[1] = "X2";
            txts[2] = "10¥";
            sunmiPrinterService.printColumnsString(txts, width, align, null);

            //打印横线
            if (paper == 1) {
                sunmiPrinterService.printText("--------------------------------\n", null);
            } else {
                sunmiPrinterService.printText("------------------------------------------------\n", null);
            }

            //打印左侧的自定义大小文字
            sunmiPrinterService.printTextWithFont("总计:          59¥\b", null, 40, null);

            //居中打印二维码
//            sunmiPrinterService.setAlignment(1, null);
//            sunmiPrinterService.printQRCode("谢谢惠顾", 10, 0, null);
//
//            //设置打印字体大小,然后打印字体
//            sunmiPrinterService.setFontSize(36, null);
//            sunmiPrinterService.printText("谢谢惠顾", null);

            //打印完成出一点纸
            sunmiPrinterService.autoOutPaper(null);

        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

可以测试一下各种样式,可以理解一个小票为一个FrameLayout,常用的一些属性为:文本的左对齐,右对齐,居中对齐,背景颜色,换行,空格,横线,二维码,Bitmap,等。

可以各种样式打印一次 看看效果,最后就是封装自己格式的小票了:

/**
 * 自定义的打印机管理-打印输出
 */
public class MyPrintManager {

    private static final MyPrintManager ourInstance = new MyPrintManager();

    public static MyPrintManager get() {
        return ourInstance;
    }

    private MyPrintManager() {
        initPrinterStyle();
    }

    private void initPrinterStyle() {
        if (BluetoothUtil.isBlueToothPrinter) {
            BluetoothUtil.sendData(ESCUtil.init_printer());
        } else {
            SunmiPrintHelper.getInstance().initPrinter();
        }
    }


    /**
     * 打印我的自定义小票
     */
    public void printMyOrder(OrderDetailResponseData detail) {

        if (detail == null) return;

        SunmiPrintHelper printHelper = SunmiPrintHelper.getInstance();

        CommUtils.getHandler().post(() -> {

            String printNum = SPUtils.getInstance(CommUtils.getContext()).getString(Constants.CACHE_PRINT_ORDER_NUM, "3");

            for (int i = 0; i < Integer.parseInt(printNum); i++) {

                String storeId = detail.order.store_id;
                if (storeId.length() == 1) {
                    storeId = "#000" + storeId;
                } else if (storeId.length() == 2) {
                    storeId = "#00" + storeId;
                } else if (storeId.length() == 3) {
                    storeId = "#0" + storeId;
                } else if (storeId.length() >= 4) {
                    storeId = "#" + storeId;
                }
                printHelper.printYYOrder(storeId, detail.order.store_name, detail.order.receive_time,
                        detail.order.deliver_no, detail.order.member_name, "Paid",
                        detail.order.deliver_type.equals("1") ? "Delivery Time" : "Self Collection",
                        detail.order.deliver_time, detail.goods.size(), detail.goods, detail.order.sold_total, detail.order.total,
                        detail.order.gst_total, detail.order.user_remark, new InnerResultCallbcak() {

                            @Override
                            public void onRunResult(boolean isSuccess) throws RemoteException {
                                YYLogUtils.w("isSuccess:" + isSuccess);
                                //打印成功回调

                                //发送通知打印订单
                                LiveEventBus.get(Constants.EVENT_PHONE_PRINT_ORDER_SUCCESS, Boolean.class).post(true);

                            }

                            @Override
                            public void onReturnString(String result) throws RemoteException {
                                YYLogUtils.w("result:" + result);
                            }

                            @Override
                            public void onRaiseException(int code, String msg) throws RemoteException {
                                YYLogUtils.w("msg:" + msg);
                            }

                            @Override
                            public void onPrintResult(int code, String msg) throws RemoteException {
                                YYLogUtils.w("msg:" + msg);
                            }
                        });

            }

        });

    }
}

具体文本排列如下:

   //打印YY-Food订单
    public void printYYOrder(String merchantId, String merchantName, String orderDate, String pickNum, String customerName,
                             String paidStatusStr, String deliverType, String deliveryTime, int foodCount,
                             List<OrderDetailResponseData.GoodsData> goods, String submitMoney,
                             String totalMoney, String gstMoney, String remark, ICallback callback) {

        if (sunmiPrinterService == null) {
            return;
        }

        try {
            int paper = sunmiPrinterService.getPrinterPaper();
            sunmiPrinterService.printerInit(null);
            sunmiPrinterService.setAlignment(1, null);  //1代表居中打印
            sunmiPrinterService.setFontSize(24, null);  //默认为18字体

            try {
                sunmiPrinterService.setPrinterStyle(WoyouConsts.ENABLE_BOLD, WoyouConsts.ENABLE);  //设置文本加粗
            } catch (RemoteException e) {
                sunmiPrinterService.sendRAWData(ESCUtil.boldOn(), null);
            }

            //店铺的id(加粗)
            sunmiPrinterService.printTextWithFont("YY Food " + merchantId + "\n", null, 30, null);

            try {
                sunmiPrinterService.setPrinterStyle(WoyouConsts.ENABLE_BOLD, WoyouConsts.DISABLE);  //设置取消文字加粗
            } catch (RemoteException e) {
                sunmiPrinterService.sendRAWData(ESCUtil.boldOff(), null);
            }

            //店铺的名字
            sunmiPrinterService.printText(merchantName + "\n", null);
            //接单时间
            sunmiPrinterService.printText(orderDate + "\n", null);

            try {
                sunmiPrinterService.setPrinterStyle(WoyouConsts.ENABLE_BOLD, WoyouConsts.ENABLE);  //设置文本加粗
            } catch (RemoteException e) {
                sunmiPrinterService.sendRAWData(ESCUtil.boldOn(), null);
            }

            //取货编号(加粗)
            sunmiPrinterService.printTextWithFont(pickNum + "\n", null, 30, null);

            try {
                sunmiPrinterService.setPrinterStyle(WoyouConsts.ENABLE_BOLD, WoyouConsts.DISABLE);  //设置取消文字加粗
            } catch (RemoteException e) {
                sunmiPrinterService.sendRAWData(ESCUtil.boldOff(), null);
            }

            //打印横线
            sunmiPrinterService.printTextWithFont("--------------------------------\n", null, 24, null);

            sunmiPrinterService.setAlignment(1, null);  //1代表居中打印
            //客户名字
            sunmiPrinterService.printText("Customer:\n", null);
            sunmiPrinterService.printText(customerName + "\n", null);

            //支付状态 ,使用反白模式打印
            sunmiPrinterService.setPrinterStyle(WoyouConsts.ENABLE_ANTI_WHITE, WoyouConsts.ENABLE);

            sunmiPrinterService.printTextWithFont("  " + paidStatusStr + "  \n", null, 26, null);

            sunmiPrinterService.setPrinterStyle(WoyouConsts.ENABLE_ANTI_WHITE, WoyouConsts.DISABLE);


            try {
                sunmiPrinterService.setPrinterStyle(WoyouConsts.ENABLE_BOLD, WoyouConsts.ENABLE);  //设置文本加粗
            } catch (RemoteException e) {
                sunmiPrinterService.sendRAWData(ESCUtil.boldOn(), null);
            }
            //送达时间(加粗)
            sunmiPrinterService.printTextWithFont(deliverType + "\n", null, 30, null);
            sunmiPrinterService.printTextWithFont(deliveryTime + " \n", null, 26, null);

            try {
                sunmiPrinterService.setPrinterStyle(WoyouConsts.ENABLE_BOLD, WoyouConsts.DISABLE);  //设置取消文字加粗
            } catch (RemoteException e) {
                sunmiPrinterService.sendRAWData(ESCUtil.boldOff(), null);
            }

            //共计多少数量
            sunmiPrinterService.printText(foodCount + " Items\n", null);

            //打印横线
            sunmiPrinterService.printTextWithFont("--------------------------------\n", null, 24, null);

            sunmiPrinterService.setAlignment(0, null);  //0代表左侧打印

            //开始商品的展示
            String txts[] = new String[]{"", "", ""};
            int width[] = new int[]{1, 4, 2};  //代表权重
            int align[] = new int[]{0, 0, 2};

            //Food遍历
            for (OrderDetailResponseData.GoodsData food : goods) {

                //打印食品
                sunmiPrinterService.setFontSize(26, null);
                sunmiPrinterService.lineWrap(1, null);   //走纸一行
                txts[0] = food.quantity + " X";
                txts[1] = food.goods_name;
                txts[2] = "$" + food.goods_price;
                sunmiPrinterService.printColumnsString(txts, width, align, null);

                //打印选项
                List<OrderDetailResponseData.OptionData> values = food.option_value;
                for (OrderDetailResponseData.OptionData value : values) {
                    String raisePrice = value.raise_price;
                    sunmiPrinterService.setFontSize(23, null);
                    txts[0] = "";
                    txts[1] = "1 x " + value.value_name;
                    txts[2] = !CheckUtil.isEmpty(raisePrice) && Double.parseDouble(raisePrice) > 0 ? "$" + raisePrice : "-";
                    sunmiPrinterService.printColumnsString(txts, width, align, null);
                }
            }

            sunmiPrinterService.lineWrap(1, null);   //走纸一行
            sunmiPrinterService.setAlignment(1, null);  //居中

            //打印横线
            sunmiPrinterService.printTextWithFont("--------------------------------\n", null, 24, null);
            sunmiPrinterService.setAlignment(0, null);  //0代表左侧打印

            //如果有remark才打印出来
            if (!CheckUtil.isEmpty(remark)) {
                sunmiPrinterService.lineWrap(1, null);   //走纸一行
                sunmiPrinterService.printText("Remark: " + remark + "\n", null);
                sunmiPrinterService.lineWrap(1, null);   //走纸一行
                sunmiPrinterService.printTextWithFont("--------------------------------\n", null, 24, null);
            }

            //统计
            sunmiPrinterService.setFontSize(24, null);
            String submits[] = new String[]{"", ""};
            submits[0] = "SUBMIT";
            submits[1] = "$" + submitMoney;
            sunmiPrinterService.printColumnsString(submits, new int[]{1, 1}, new int[]{0, 2}, null);

            sunmiPrinterService.lineWrap(1, null);   //走纸一行

            //TOTAL
            sunmiPrinterService.printTextWithFont("Total\n", null, 26, null);
            try {
                sunmiPrinterService.setPrinterStyle(WoyouConsts.ENABLE_BOLD, WoyouConsts.ENABLE);  //设置文本加粗
            } catch (RemoteException e) {
                sunmiPrinterService.sendRAWData(ESCUtil.boldOn(), null);
            }
            //Total金额
            sunmiPrinterService.printTextWithFont("$" + totalMoney + "\n", null, 30, null);
            try {
                sunmiPrinterService.setPrinterStyle(WoyouConsts.ENABLE_BOLD, WoyouConsts.DISABLE);  //设置取消文字加粗
            } catch (RemoteException e) {
                sunmiPrinterService.sendRAWData(ESCUtil.boldOff(), null);
            }

            //GST
            sunmiPrinterService.setFontSize(23, null);
            String gsts[] = new String[]{"", ""};
            gsts[0] = "GST (Included)";
            gsts[1] = "$" + gstMoney;
            sunmiPrinterService.printColumnsString(gsts, new int[]{1, 1}, new int[]{0, 2}, null);


            //打印完成出一点纸
            sunmiPrinterService.lineWrap(1, null);   //走纸一行
            sunmiPrinterService.autoOutPaper(callback);

        } catch (RemoteException e) {
            e.printStackTrace();
        }

    }

打印效果如下:

到处结束啦,希望对大家能有所帮助!