Zebra 打印机 Android 端驱动接口开发及调用案例

Zebra 打印机 Android 端驱动接口开发及调用

Android 设备驱动 Zebra ZQ520 移动式打印机打印条码信息案例

GitHub 源码

开发步骤

环境配置

  • 硬件环境
    • Zebra ZQ520 移动打印机 1 台
    • Android 设备 1 台
    • 打印纸(50mm * 40mm)
  • 软件环境
    • Android Studio 3.2
      • gradle:3.2.0

Zebra SDK 资源下载

  • 方法1:官网下载(需要各种注册信息)
    dowloadSDK
    由于我是 windows 系统,所以点击 windows Installer,下载完成后,在 windows 系统中直接双击安装,安装完成后在目录:\link_os_sdk\android\v2.14.5198\lib 目录下可以找到所需要的 jar 包。

  • 方法2:CSDN 资源下载(会需要少量积分)

  • 方法3:百度网盘下载(链接:https://pan.baidu.com/s/1tVg6lW52SSeh3j6kW7ih8Q 提取码: qfc8 )

Android 开发环境搭建

正常来讲,如果是开发完整的 app 引用,则直接引入上一步中的 jar 包就可以调用 Zebra 的驱动接口了。

但是由于业务需求,我这边只负责开发一个处理拆分数据、生成ZPL命令、驱动列印功能的接口。

其他同事负责开发 Android 客户端并调用我提供的接口

所以,本案例采用的开发模式是:

  • 开发 Android Library ,打包成 arr 文件;
  • 新建空 App 应用,调用打包的 arr 接口测试。
  • 测试成功后,将测试案例与 arr 接口发送给调用者使用。

新建Android项目

  • 输入项目名称、包名,选择项目目录。

    createnewproject

  • 选择支持的驱动环境。选择 Phone,最低 Android 4.0
    targetAndroiddevices

  • 创建一个空模板,方便后续测试。
    chooseemptyactivity

  • Activity 的 name 和 layout name,默认即可,点击 finish
    configureactivity

新建完成后, Gradle 会自动同步,此时需要保持网络畅通(需要一段时间,耐心等待即可。如果没有网络,需要保证 gradle 的离线版本支持)

如果遇到如下错误:

Unable to resolve dependency for ‘:app@debug/compileClasspath’: Could not find any version that matches com.android.support:appcompat-v7:29.+.
Open File
Show Details

这是因为没有找到符合正则验证: com.android.support:appcompat-v7:29.+. 版本的文件

解决方案:

简单暴力:放宽该包的正则验证规则。

步骤:

  • 点击报错信息中的 Open File 超链接打开对应的 build gradle 文件;
  • 找到 com.android.support:appcompat-v7:29.+ 这一行;
    androidsupport
  • 删除 29. 几个字符,保存
    delete29
  • 点击上方的 “try agine”,或者 “File --> Sync Project With Gradle Files” 重新同步 gradle 文件
    syncprojectwithfradlefiles

至此,一个空的 Android 项目就新建完成了。

新建 Android Library

  • 在项目文件上右键,或者 File --> new module
  • 选择 Android Library
    Androidlibrary
  • 输入 library name,选择 minmum SDK 版本(建议和 Android 项目相同),点击 finish。
    androidlibrary

如果遇到如下错误:

Unable to resolve dependency for ‘:app@debug/compileClasspath’: Could not find any version that matches com.android.support:appcompat-v7:29.+.
Open File
Show Details

解决方案和上面相同(注意Library有自己对应的 build.gradle 文件,建议 点击报错信息中 open file 超链接跳转)

导入 Zebra 驱动包

  • 点击左上角资源总览视图中的 Android 部分,切换为 project 显示方式
    changetoproject
  • 引入 Zebra 驱动文件到 Android Library 模块的 libs 目录下(第一步中下载的 jar 包)
    importlib
  • 选中所有 jar 包,右键,Add as Library,选择 add to module 为 刚才新建的 Android Library
    addaslibrary

Add 成功后,在 Android Library 对应的 build.gradle 文件下,会自动生成如下一段代码:

dependencies {
   	...
    implementation files('libs/commons-io-2.2.jar')
    implementation files('libs/commons-net-3.1.jar')
    implementation files('libs/commons-validator-1.4.0.jar')
    implementation files('libs/httpcore-4.3.1.jar')
    implementation files('libs/httpmime-4.3.2.jar')
    implementation files('libs/jackson-annotations-2.2.3.jar')
    implementation files('libs/jackson-core-2.2.3.jar')
    implementation files('libs/jackson-databind-2.2.3.jar')
    implementation files('libs/opencsv-2.2.jar')
    implementation files('libs/snmp6_1.jar')
    implementation files('libs/ZSDK_ANDROID_API.jar')
}

如果上一步中右键没有找到 “Add as Library” 选项,也可以直接手动在对应的 build.gradle 文件中添加如上代码(注意路径)

Android Library 权限配置

由于本案例采用 BlueTooth 连接,所以,需要配置 BlueTooth 赋予蓝牙的访问权限。

  • 在 Android Library 模块(zebraprinter)下依次展开 src --> main --> AndroidMainfest.xml 文件。加入如下两行代码:

    <uses-permission android:name="android.permission.BLUETOOTH" />
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
    

    如果需要其他需求,也可以在这里继续配置权限(如:后续可能需要获取主机mac地址,所以我再加入WIFI_STATE权限)
    promise

ZPL II 语法在线测试

推荐一个在线测试 ZPL II 的网站。点击跳转

在这里,你可以在线编写 ZPL 代码,并随时点击 redraw 按钮预览结果,非常方便!

zplonline

主体功能开发

本案例的主体需求为:收到一份信息,将该信息进行拆分,打印出两份信息。

Android Library 的代码基本结构如下:

codetree
其中:

  • Entity 包中存放了两个实体:
    • ResultObj:返回信息实体类。包含一个 boolean 类型的执行状态,和一个 String 类型的执行信息
    • ZeroSymbolBill:存储案例中收到与拆分的信息。为了方便操作。
  • Utils 包中存放了一个工具类:
    • Utils:工具类。包含一个生成唯一码 GUID 的静态方法。
  • ZebraPrinter 包中存放了打印机相关代码
    • ZQ520 Printer:存放了调用 ZQ520 打印机的主体代码

具体代码如下:

package com.amborsecdmeng.demo.entity;

/**
 * Result Object
 */
public class ResultObj {

    /**
     * 状态
     */
    private boolean status;
    /**
     * 信息
     */
    private String message;

    public ResultObj() {
        this.status = false;
    }

    public ResultObj(boolean status) {
        this.status = status;
    }

    public ResultObj(boolean status, String message) {
        this.status = status;
        this.message = message;
    }

    public boolean isStatus() {
        return status;
    }

    public void setStatus(boolean status) {
        this.status = status;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }
}

package com.amborsecdmeng.demo.entity;

/**
 * Model of ZeroSymbolBill
 *
 * Example:
 *
 * W,NEW00182631190729C0001,P2a-J60102,2T459M000-000-G5,20190729,WmL-J76036,2200,PCS,VCN00182631190729C0001
 * W,NEW00182631190729C0002,P2a-J60102,2T459M000-000-G5,20190729,WmL-J76036,5000,PCS,VCN00182631190729C0001
 */
public class ZeroSymbolBill {

    /**
     * 类型:W
     */
    private String type;
    /**
     * 料号:2T459M000-000-G5
     */
    private String pn;
    /**
     * 数量:7200
     */
    private Integer qty;
    /**
     * 拆分:5000
     */
    private Integer splitQty;
    /**
     * 单位:PCS
     */
    private String unit;

    /**
     * 旧 GUID:VCN00182631190729C0001
     */
    private String oldGuid;
    /**
     * 新 GUID:NEW00182631190729C0001
     */
    private String newGuid;
    /**
     * 日期:20190729
     */
    private String date;

    /**
     * 预留字段 1:P2a-J60102
     */
    private String var1;
    /**
     * 预留字段 2:WmL-J76036
     */
    private String var2;

    /**
     * Constructor without params
     */
    public ZeroSymbolBill() {
    }

    /**
     * Constructor with all params
     *
     * @param type
     * @param newGuid
     * @param var1
     * @param pn
     * @param date
     * @param var2
     * @param qty
     * @param unit
     * @param oldGuid
     * @param splitQty
     */
    public ZeroSymbolBill(String type, String newGuid, String var1, String pn, String date, String var2, Integer qty, String unit, String oldGuid, Integer splitQty) {
        this.type = type;
        this.newGuid = newGuid;
        this.var1 = var1;
        this.pn = pn;
        this.date = date;
        this.var2 = var2;
        this.qty = qty;
        this.unit = unit;
        this.oldGuid = oldGuid;
        this.splitQty = splitQty;
    }

    /**
     * Getter & Setter
     */

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

    public String getPn() {
        return pn;
    }

    public void setPn(String pn) {
        this.pn = pn;
    }

    public Integer getQty() {
        return qty;
    }

    public void setQty(Integer qty) {
        this.qty = qty;
    }

    public Integer getSplitQty() {
        return splitQty;
    }

    public void setSplitQty(Integer splitQty) {
        this.splitQty = splitQty;
    }

    public String getUnit() {
        return unit;
    }

    public void setUnit(String unit) {
        this.unit = unit;
    }

    public String getOldGuid() {
        return oldGuid;
    }

    public void setOldGuid(String oldGuid) {
        this.oldGuid = oldGuid;
    }

    public String getNewGuid() {
        return newGuid;
    }

    public void setNewGuid(String newGuid) {
        this.newGuid = newGuid;
    }

    public String getDate() {
        return date;
    }

    public void setDate(String date) {
        this.date = date;
    }

    public String getVar1() {
        return var1;
    }

    public void setVar1(String var1) {
        this.var1 = var1;
    }

    public String getVar2() {
        return var2;
    }

    public void setVar2(String var2) {
        this.var2 = var2;
    }
}
package com.amborsecdmeng.demo.utils;

import java.util.UUID;

public class Utils {
    public static String GUID() {
        UUID uuid = UUID.randomUUID();
        String str = uuid.toString();
        String uuidStr = str.replace("-", "");
        return uuidStr;
    }
}
package com.amborsecdmeng.demo.zerbraprinter;

import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.content.Context;
import android.net.wifi.WifiManager;

import com.amborsecdmeng.demo.entity.ResultObj;
import com.amborsecdmeng.demo.entity.ZeroSymbolBill;
import com.amborsecdmeng.demo.utils.Utils;
import com.zebra.sdk.comm.BluetoothConnection;
import com.zebra.sdk.comm.Connection;
import com.zebra.sdk.comm.ConnectionException;
import com.zebra.sdk.comm.TcpConnection;
import com.zebra.sdk.printer.PrinterLanguage;
import com.zebra.sdk.printer.PrinterStatus;
import com.zebra.sdk.printer.ZebraPrinter;
import com.zebra.sdk.printer.ZebraPrinterFactory;
import com.zebra.sdk.printer.ZebraPrinterLinkOs;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Set;

/**
 * ZQ520 Printer
 */
public class ZQ520Printer {

    private Context context;

    public ZQ520Printer(Context context) {
        this.context = context;
    }

    private Connection connection = null;
    private boolean isConnByBluetooth = true;

    private String bluetoothMacAddress = "";// Test Machine mac address:    AC:3F:A4:E4:D6:3F
    private String tcpAddress = "127.0.0.1";
    private Integer tcpPortNumber = 0;

    private String printerStatusMsg = "";


    /**
     * ZeroSymbolBill Printer
     *
     * @param codeMsg  BQ code message.
     *                 Example:
     *                 W,NEW00182631190729C0001,P2a-J60102,2T459M000-000-G5,20190729,WmL-J76036,2000,PCS,VCN00182631190729C0001
     * @param splitQty split quantity
     * @return
     */
    public ResultObj zeroSymbolBill(String codeMsg, Integer splitQty) {

        String[] codeMsgArr = codeMsg.split(",");
        ArrayList<ZeroSymbolBill> zsbs = new ArrayList<>();

        /* BQ code message before split. include split quantity */
        ZeroSymbolBill zsb = new ZeroSymbolBill();

        try {
            zsb.setType(codeMsgArr[0].trim());     // type.
            zsb.setNewGuid(codeMsgArr[1].trim());  // current guid.    -- after split. It will become the old guid
            zsb.setVar1(codeMsgArr[2].trim());     //
            zsb.setPn(codeMsgArr[3].trim());       // p/n
            zsb.setDate(codeMsgArr[4].trim());     // date
            zsb.setVar2(codeMsgArr[5].trim());     //
            zsb.setQty(Integer.valueOf(codeMsgArr[6].trim())); // quantity
            zsb.setUnit(codeMsgArr[7].trim());     // unit
            zsb.setSplitQty(splitQty);
        } catch (NumberFormatException e) {
            return new ResultObj(false, "Exc-03: "+e.getMessage());
        }

        if (zsb.getSplitQty() >= zsb.getQty())
            return new ResultObj(false, "the split quantity must be less than quantity");

        /* BQ code message after split. return an arrayList */
        zsbs.add(new ZeroSymbolBill(
                zsb.getType(),
                Utils.GUID(), // set new guid for BQ code after split
                zsb.getVar1(),
                zsb.getPn(),
                zsb.getDate(),
                zsb.getVar2(),
                zsb.getSplitQty(),// first split.    quantity = split quantity
                zsb.getUnit(),
                zsb.getNewGuid(),   // become the old guid after split
                0
        ));

        zsbs.add(new ZeroSymbolBill(
                zsb.getType(),
                Utils.GUID(), // set new guid for BQ code after split
                zsb.getVar1(),
                zsb.getPn(),
                zsb.getDate(),
                zsb.getVar2(),
                zsb.getQty() - zsb.getSplitQty(),// second split.    quantity = quantity - split quantity
                zsb.getUnit(),
                zsb.getNewGuid(),   // become the old guid after split
                0
        ));
        /* BQ code split completion */

        if (isConnByBluetooth) {
            /* get first bonded mac address */
            bluetoothMacAddress = findBluetoothMacAddress().get(0);
            connection = new BluetoothConnection(bluetoothMacAddress);
        } else {
            try {
                connection = new TcpConnection(tcpAddress, tcpPortNumber);
            } catch (NumberFormatException e) {
                return new ResultObj(false, "Exc-01: Tcp connection open failed");
            }
        }

        try {
            connection.open();
            ZebraPrinter printer = ZebraPrinterFactory.getInstance(connection);
            ZebraPrinterLinkOs linkOsPrinter = ZebraPrinterFactory.createLinkOsPrinter(printer);
            PrinterStatus printerStatus = (linkOsPrinter != null) ? linkOsPrinter.getCurrentStatus() : printer.getCurrentStatus();

            if (printerStatus.isReadyToPrint) {
                return sendToPrint(printer, zsbs);
            } else if (printerStatus.isHeadOpen)
                printerStatusMsg = "Err-01: Head Open! \n Please close Printer Head to print. ";
            else if (printerStatus.isHeadCold)
                printerStatusMsg = "Err-02: Head Cold! \n Please try again. ";
            else if (printerStatus.isHeadTooHot)
                printerStatusMsg = "Err-03: Head too hot! \n Please do it later. ";
            else if (printerStatus.isPaperOut)
                printerStatusMsg = "Err-04: Media Out! \n Please load Media to Print. ";
            else if (printerStatus.isPartialFormatInProgress)
                printerStatusMsg = "Err-05: Head Open! \n Please try again later. ";
            else if (printerStatus.isPaused)
                printerStatusMsg = "Err-06: Printer Paused. ";
            else if (printerStatus.isReceiveBufferFull)
                printerStatusMsg = "Err-07: Buffer full! \n Please do it later. ";
            else if (printerStatus.isRibbonOut)
                printerStatusMsg = "Err-08: Ribbon Out! \n Please retry after adjustment. ";

            connection.close();

            return new ResultObj("".equals(printerStatusMsg), printerStatusMsg);
        } catch (Exception e) {
            return new ResultObj(false, "Exc-02: " + e.getMessage());
        } finally {

        }
    }


    private ResultObj sendToPrint(ZebraPrinter printer, ArrayList<ZeroSymbolBill> zsbs) {
        String filename = "TEMP.LBL";
        try {
            File file = context.getFileStreamPath(filename);
            if (!file.exists())
                file.createNewFile();
            createZPLFile(printer, filename, zsbs);
            printer.sendFileContents(file.getAbsolutePath());
            return new ResultObj(true, "filepath: " + file.getAbsolutePath());
        } catch (IOException e) {
            return new ResultObj(false, e.getMessage());
        } catch (ConnectionException e) {
            return new ResultObj(false, e.getMessage());

        }
    }

    private void createZPLFile(ZebraPrinter printer, String filename, ArrayList<ZeroSymbolBill> zsbs) throws IOException {

        FileOutputStream os = context.openFileOutput(filename, Context.MODE_PRIVATE);
        byte[] zplByte = null;

        PrinterLanguage printerLanguage = printer.getPrinterControlLanguage();

        if (printerLanguage == printerLanguage.ZPL) {
            StringBuilder sb = new StringBuilder();
            for (ZeroSymbolBill zsb : zsbs) {
                sb.append(buildZPLTemplate(zsb));
            }
            zplByte = sb.toString().getBytes();
        }
        os.write(zplByte);
        os.flush();
        os.close();
    }

    private String buildZPLTemplate(ZeroSymbolBill zsb) {

        final String formName = "Zero Symbol Bill";

        zsb.setNewGuid(Utils.GUID());

        StringBuilder sb = new StringBuilder();

        sb.append("^XA\n")
                .append("^LL320\n")
                .append("^PW400\n")
                .append("^LH0,0\n")
                .append("^CI26\n")
                .append("^SEE:GB18030.DAT\n")

                .append("^FO48,20^AEN,10,10^FD" + formName + "^FS\n")//Zero Symbol Bill
                .append("^FO24,62^AEN,10,10^FDP/N:^FS\n")//P/N
                .append("^FO54,102^AEN,10,10^FD" + zsb.getPn() + "^FS\n")//2T459M000-000-G5
                .append("^FO24,142^AEN,10,10^FDQTY:^FS\n")//QTY
                .append("^FO54,182^AEN,10,10^FD" + zsb.getQty() + " PCS^FS\n")//200000
                .append("^FO24,222^AEN,10,10^FDDATE:^FS\n")//DATE
                .append("^FO54,264^AEN,10,10^FD" + zsb.getDate() + "^FS\n")//20191107

                .append("^FO280,172^BQN,2,2^FD\n")
                .append("   " + zsb.getType()
                        + "," + zsb.getNewGuid()
                        + "," + zsb.getVar1()
                        + "," + zsb.getPn()
                        + "," + zsb.getDate()
                        + "," + zsb.getVar2() + "," + zsb.getQty() + ",PCS," + zsb.getOldGuid() + "^FS\n")

                .append("^XZ");

        return sb.toString();
    }

    /**
     * find bluetooth mac address list
     *
     * @return
     */
    private ArrayList<String> findBluetoothMacAddress() {
        ArrayList<String> macAddressList = new ArrayList<>();
        BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
        Set<BluetoothDevice> devices = mBluetoothAdapter.getBondedDevices();
        for (BluetoothDevice device : devices)
            macAddressList.add(device.getAddress());
        return macAddressList;
    }


    /**
     * get Local Mac Address
     *
     * @return
     */
    private String getLocalMacAddress(){
        WifiManager wifi = (WifiManager)context.getSystemService(Context.WIFI_SERVICE);
        return wifi.getConnectionInfo().getMacAddress();
    }
}


打包 arr 文件

arr 文件和 jar 文件类似都可以理解为一个类库文件。不同的是,arr 文件包含了class以及res资源文件,而 jar 文件只包含了 class 文件。

至于这两种文件详细的异同,请自行查阅资料,本文不做详细介绍。

  • 在 Android Studio 代码界面最右侧 “gradle” 选项,选中开始创建的 Android Library 目录,依次展开:Tasks --> build --> assemble。 双击执行。
    build

  • 打包 arr 文件。(输出在 /build/outputs/arr 目录下)
    arrbuild

    arr

    其中,zebraprinter-release.aar 文件就是打包好的发布版本(才发现,模块名字中我将 zebra 误写成 zerbra 了

调用 arr 接口

以上,arr 接口开发以及打包已经完成,按理来说,这里只需要将 arr 文件打包发送 Android 客户端的开发人员即可。

但是,我们这里先自行测试一下。

  • 将 arr 文件 copy 到 项目 app 模块(最开始创建的 Android 应用空模板)的 libs 目录下并重命名(记得切换到 project 显示,否则不显示 libs 包)。
    copyarr

  • 修改 build.gradle 配置文件(如不修改,在使用时 Android Studio 也会提示修改,建议手动修改)
    在 app 模块的 build.gradle 文件中的 dependencies 代码块中,加入如下代码:

    implementation files('libs/zebraprinter.aar')
    

    buildgradle

  • 在 app 模块的 activity_main.xml 界面设计文件中,增加一个按钮用于测试。
    main.xml

    <?xml version="1.0" encoding="utf-8"?>
    <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">
    
        <Button
            android:id="@+id/btnPrintTest"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="67dp"
            android:text="PrintTest"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />
    
    </android.support.constraint.ConstraintLayout>
    

    layout

  • 在 app 模块的 MainActivity 文件中为 printtest 按钮增加点击监听事件,调用 arr 接口中的 zeroSymbolBill 方法发送打印命令
    mainactivity

    package com.foxconn.mac1.zebraprintdemo;
    
    import android.support.v7.app.AppCompatActivity;
    import android.os.Bundle;
    import android.view.View;
    import android.widget.Button;
    import android.widget.Toast;
    
    import com.foxconn.mac1.zebraprinter.Entity.ResultObj;
    import com.foxconn.mac1.zebraprinter.ZebraPrinter.ZQ520Printer;
    
    public class MainActivity extends AppCompatActivity {
    
        protected Button btnPrint;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            btnPrint = this.findViewById(R.id.btnPrintTest);
            btnPrint.setOnClickListener(new View.OnClickListener(){
    
                @Override
                public void onClick(View view){
                    printTest();
                }
            });
        }
    
        public void printTest(){
            try {
                /* Example for Test */
                String BQcode = "W,VCN00182631190729C0001,P2a-J60102,2T459M000-000-G5,20190729,WmL-J76036,7200,PCS";
                Integer split = 2000;
    
                ZQ520Printer zq520Printer = new ZQ520Printer(this);
                ResultObj resultObj = zq520Printer.zeroSymbolBill(BQcode, split);
                if (resultObj.isStatus()){
                    Toast.makeText(MainActivity.this, "print test success", Toast.LENGTH_SHORT).show();
                }else{
                    Toast.makeText(MainActivity.this, resultObj.getMessage(), Toast.LENGTH_SHORT).show();
                }
    
                /* Example end */
            } catch (Exception e) {
                e.printStackTrace();
                Toast.makeText(MainActivity.this, e.getMessage(), Toast.LENGTH_SHORT).show();
            }
        }
    }
    
    

至此,代码开发及测试已经完成。

附加:APP 打包成 APK 文件

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章