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 文件

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