USB打印機(jī)開(kāi)發(fā)記錄

最近公司的餐廳點(diǎn)餐app又提了新需求,增加對(duì)usb打印機(jī)的支持。

USB開(kāi)發(fā),涉及到主從模式

  • 主機(jī)模式
    Android設(shè)備充當(dāng)主主設(shè)備,并為總線供電察迟。
    例如數(shù)字相機(jī)斩狱,鍵盤(pán),鼠標(biāo)和游戲控制器扎瓶。USB設(shè)備與Android應(yīng)用進(jìn)行數(shù)據(jù)交互所踊。

  • 附件模式(從屬)
    外部硬件充當(dāng)USB主設(shè)備,并為總線供電概荷。例如手機(jī)和電腦連接

主從模式圖

做usb通信污筷,首先要先弄清楚哪邊是HOST那邊是SLAVE。
比如你的android手機(jī)做host乍赫,要獲得slave瓣蛀,用UsbDevice表示slave
要是你的android手機(jī)做slave,要獲得host雷厂,用UsbAccessory表示host

我們的項(xiàng)目是主模式開(kāi)發(fā),也就是說(shuō)USB打印機(jī)連接到我們的點(diǎn)餐設(shè)備上,點(diǎn)餐設(shè)備充當(dāng)USB主設(shè)備,并為總線供電惋增。

首先你可以在manifest.xml清單文件中,聲明usb權(quán)限是否為必須。
如果不聲明改鲫,默認(rèn)為false诈皿。

如果required=true,則在安裝app的同時(shí),如果android設(shè)備不支持usb功能,則app是無(wú)法安裝的。

同時(shí)像棘,做聲明的好處是稽亏,如果你的app受眾人群在國(guó)外,那么googleplay會(huì)幫你過(guò)濾掉沒(méi)有usb功能的設(shè)備缕题,也就是說(shuō)沒(méi)有usb功能的設(shè)備時(shí)搜索不到或者無(wú)法下載你的app的!

 <uses-feature
        android:name="android.hardware.usb.host"
        android:required="true" />

聲明插入或拔出usb設(shè)備時(shí),打開(kāi)指定的activity.

 <activity
            android:name="com.mjt.print.PrinterConnectDialog"
            android:configChanges="keyboardHidden|orientation|screenSize"
            android:label="@string/app_name"
            android:theme="@android:style/Theme.Light" >

            <intent-filter>
                <!--host模式開(kāi)發(fā)時(shí),在設(shè)備插入/拔出時(shí)啟動(dòng)該activity的action-->
                <action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
                <action android:name="android.hardware.usb.action.USB_DEVICE_DETACHED" />

                <!--accessory模式開(kāi)發(fā)時(shí),在設(shè)備插入/拔出時(shí)啟動(dòng)該activity的action-->

                <!--<action android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED" />-->
                <!--<action android:name="android.hardware.usb.action.USB_ACCESSORY_DETTACHED" />-->
            </intent-filter>

            <!--過(guò)濾usb設(shè)備,在device_file.xml文件中聲明的設(shè)備,-->
            <meta-data
                android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
                android:resource="@xml/device_filter" />
           

  </activity>

聲明要過(guò)濾的usb設(shè)備的相關(guān)信息

vender-id為生產(chǎn)廠家號(hào) product-id為產(chǎn)品號(hào)

其中vendor-id和product-id為插入U(xiǎn)SB設(shè)備的生產(chǎn)廠家號(hào)和產(chǎn)品號(hào)截歉,在 插入(attached)上面列出的設(shè)備之一時(shí),就會(huì)彈出選擇打開(kāi)應(yīng)用程序的對(duì)話框烟零。

<resources>
    <!-- Accept all device VID/PID combinations -->
    <usb-device vendor-id="22339" product-id="1155" />
</resources>

usb開(kāi)發(fā)的相關(guān)api在android.hardware.usb包下


image.png
說(shuō)明
UsbManager 獲得 USB 管理器瘪松,與連接的 USB 設(shè)備通信。
UsbDevice host模式下,USB 設(shè)備的抽象锨阿,每個(gè)UsbDevice 都代表一個(gè) USB 設(shè)備宵睦。
UsbAccessory (從屬模式下,也就是你的android設(shè)備相當(dāng)于一個(gè)附件掛在usb設(shè)備上)此時(shí)的UsbAccessory代表的就是android設(shè)備所掛在的那個(gè)設(shè)備
UsbInterface 表示一個(gè)UsbDevice的一個(gè)接口。UsbDevice可以具有一個(gè)或多個(gè)在接口用來(lái)通信墅诡。
UsbEndpoint 表示一個(gè)UsbInterface一個(gè)端點(diǎn)壳嚎,它是接口的通信通道的烁。一個(gè)接口可以具有一個(gè)或多個(gè)端點(diǎn)实束,與設(shè)備進(jìn)行雙向通信通常有一個(gè)端點(diǎn)用于輸入和一個(gè)端點(diǎn)用于輸出。
UsbDeviceConnection 表示與設(shè)備的連接趴乡,用來(lái)收發(fā)數(shù)據(jù)荐吉,傳輸控制信息焙糟。
UsbRequest 通過(guò)UsbDeviceConnection與設(shè)備通信的異步請(qǐng)求,只用來(lái)異步通信
UsbConstants USB常量定義,與Linux內(nèi)核的linux / usb / ch9.h中的定義相對(duì)應(yīng)

UsbPort

package com.mjt.factory.print.engine;

import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.hardware.usb.UsbConstants;
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbDeviceConnection;
import android.hardware.usb.UsbEndpoint;
import android.hardware.usb.UsbInterface;
import android.hardware.usb.UsbManager;
import android.util.Log;

import com.gprinter.io.PortManager;
import com.gprinter.utils.Utils;
import com.mjt.common.base.CommonLib;

import java.io.IOException;
import java.util.Vector;

import static com.mjt.common.constants.Constants.ACTION_USB_PERMISSION;

/**
 * Copyright:mjt_pad_android
 * Author: liyang <br>
 * Date:2019-05-28 14:07<br>
 * Desc: <br>
 */
public class UsbPort extends PortManager {
    private static final String TAG = UsbPort.class.getSimpleName();


    private UsbDevice usbDevice;

    private UsbManager usbManager;

    private UsbDeviceConnection connection;

    private UsbInterface interf;

    private UsbEndpoint epIn, epOut;

    public static final int USB_REQUEST_CODE = 0;

    public UsbPort(UsbDevice device) {
        usbDevice = device;
        usbManager = (UsbManager) CommonLib.getContext().getSystemService(Context.USB_SERVICE);
    }

    public boolean openPort() {
        if (this.usbDevice != null) {
            if (!this.usbManager.hasPermission(this.usbDevice)) {
                PendingIntent intent = PendingIntent.getBroadcast(CommonLib.getContext(), USB_REQUEST_CODE, new Intent(ACTION_USB_PERMISSION), 0);
                usbManager.requestPermission(usbDevice, intent);
                return false;
            } else {
                openUsbPort();
                return  epOut != null;
            }
        }
        return false;
    }

    private void openUsbPort() {
        int          count        = usbDevice.getInterfaceCount();
        UsbInterface usbInterface = null;

        if (count > 0) {
            usbInterface = usbDevice.getInterface(0);
        }

        if (usbInterface != null) {
            interf = usbInterface;
            connection = null;
            connection = usbManager.openDevice(usbDevice);
        }
        if (connection != null && connection.claimInterface(interf, true)) {
            for (int i = 0; i < interf.getEndpointCount(); ++i) {
                UsbEndpoint ep = interf.getEndpoint(i);
                if (ep.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK) {
                    if (ep.getDirection() == UsbConstants.USB_DIR_OUT) {
                        this.epOut = ep;
                    } else {
                        this.epIn = ep;
                    }
                }
            }
        }

    }

    public void writeDataImmediately(Vector<Byte> data) throws IOException {
        this.writeDataImmediately(data, 0, data.size());
    }


    public void writeDataImmediately(Vector<Byte> data, int offset, int len) throws IOException {
        int          result   = 0;
        Vector<Byte> sendData = new Vector<>();

        for (int i = 0; i < data.size(); ++i) {
            if (sendData.size() >= 1024) {
                Log.e(TAG, "i = " + i + "\tsendData size -> " + sendData.size() + "\tdata size -> " + data.size());
                result += this.connection.bulkTransfer(this.epOut, Utils.convertVectorByteTobytes(sendData), sendData.size(), 1000);
                sendData.clear();
                Log.e(TAG, "sendData.clear() size -> " + sendData.size());
            }

            sendData.add(data.get(i));
        }

        if (sendData.size() > 0) {
            Log.e(TAG, "sendData size -> " + sendData.size());
            result += this.connection.bulkTransfer(this.epOut, Utils.convertVectorByteTobytes(sendData), sendData.size(), 1000);
        }

        if (result == data.size()) {
            Log.d(TAG, "send success");
        }else {
            throw new IOException("send failed");
        }
    }

    public int readData(byte[] bytes) throws IOException {
        return this.connection != null&&epIn!=null ? this.connection.bulkTransfer(this.epIn, bytes, bytes.length, 200) : 0;
    }

    public boolean closePort() {
        if (this.interf != null && this.connection != null) {
            this.connection.releaseInterface(this.interf);
            this.connection.close();
            this.connection = null;
            return true;
        } else {
            return false;
        }
    }

    public UsbDevice getUsbDevice() {
        return this.usbDevice;
    }

    public void setUsbDevice(UsbDevice usbDevice) {
        this.usbDevice = usbDevice;
    }
}

在MainActivity中注冊(cè)Usb廣播

   private final BroadcastReceiver usbDeviceReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            Log.d("action", action);
            UsbDevice mUsbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
            if (ACTION_USB_PERMISSION.equals(action)) {
                synchronized (this) {
                    if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false) && mUsbDevice != null) {
                        Log.d("receiver", action + ",device:" + mUsbDevice.getDeviceName() + "被授予了權(quán)限!");
                    } else {
                        UIUtils.showToast(context, "USB設(shè)備請(qǐng)求被拒絕");
                    }
                }
            } else if (UsbManager.ACTION_USB_DEVICE_DETACHED.equals(action)) {
                if (mUsbDevice != null) {
                    UIUtils.showToast(context, "有設(shè)備拔出");
                    EventBus.getDefault().post(new UsbAttachedChangedEvent(mUsbDevice, false));
                }
            } else if (UsbManager.ACTION_USB_DEVICE_ATTACHED.equals(action)) {
                if (mUsbDevice != null) {
                    UIUtils.showToast(context, "有設(shè)備插入");
                    EventBus.getDefault().post(new UsbAttachedChangedEvent(mUsbDevice, true));
                }
            }
        }
    };

DeviceConnManager類的openPort方法

  /**
     * 打開(kāi)端口
     *
     * @return
     */
    public void openPort(boolean needNotify) {
        this.needNotify = needNotify;
        isOpenPort = false;
        sendStateBroadcast(CONN_STATE_CONNECTING);
        switch (connMethod) {
            case BLUETOOTH:
                System.out.println("id -> " + id);
                mPort = new BluetoothPort(macAddress);
                isOpenPort = mPort.openPort();

                break;
            case USB:
                mPort = new UsbPort(mUsbDevice);
                isOpenPort = mPort.openPort();

                break;
            case WIFI:
                mPort = new EthernetPort(ip, port);
                isOpenPort = mPort.openPort();
                break;
            case SERIAL_PORT:
                mPort = new SerialPort(serialPortPath, baudrate, 0);
                isOpenPort = mPort.openPort();
                break;
            default:
                break;
        }
        //端口打開(kāi)成功后样屠,檢查連接打印機(jī)所使用的打印機(jī)指令ESC、TSC
        Log.e(TAG, "openPort :" + isOpenPort + ",thread is " + Thread.currentThread().getName());
        if (!isOpenPort) {
            if (getConnMethod() == CONN_METHOD.USB && mUsbDevice != null) {
                mUsbDevice = null;
            }
            mPort.closePort();
            sendStateBroadcast(CONN_STATE_FAILED);
        } else {
            sendStateBroadcast(CONN_STATE_CONNECTED);
        }
    }

PrinterManager類中的addUsbPrinter方法和connectUsb方法

  public void addUsbPrinter(String id, String name) {
        DeviceConnFactoryManager usbManager = getManager(id);
        UsbManager               manager    = (UsbManager) CommonLib.getContext().getSystemService(Context.USB_SERVICE);
        UsbDevice                usbDevice  = null;
        for (UsbDevice usb : manager.getDeviceList().values()) {
            String pidVid = String.format("%s%s", usb.getProductId(), usb.getVendorId());
            if (usb.getInterface(0).getInterfaceClass() == UsbConstants.USB_CLASS_PRINTER && pidVid.equals(id)) {
                usbDevice = usb;
            }
        }
        if (usbManager == null) {
            if (usbDevice != null) {
                new DeviceConnFactoryManager.Build().setId(id)
                                                    .setConnMethod(DeviceConnFactoryManager.CONN_METHOD.USB)
                                                    .setName(name)
                                                    .setUsbDevice(usbDevice)
                                                    .build();
            }

        } else {
            //由于usb設(shè)備每次查吧usbdevice的deviceId都會(huì)變,所以需要重新設(shè)備UsbDevice
            if (usbDevice != null && usbManager.usbDevice() == null) {
                usbManager.setUsbDevice(usbDevice);
            }
        }
    }


   public void connectUsb(String pidVid, String name, boolean needNotify) {
        DeviceConnFactoryManager deviceManager = getManager(pidVid);
        UsbManager               manager       = (UsbManager) CommonLib.getContext().getSystemService(Context.USB_SERVICE);
        UsbDevice                usbDevice     = null;
        for (UsbDevice usb : manager.getDeviceList().values()) {
            String pv = String.format("%s%s", usb.getProductId(), usb.getVendorId());
            if (usb.getInterface(0).getInterfaceClass() == UsbConstants.USB_CLASS_PRINTER && pidVid.equals(pv)) {
                usbDevice = usb;
            }
        }
        if (deviceManager == null) {
            if (usbDevice != null) {
                new DeviceConnFactoryManager.Build().setId(pidVid)
                                                    .setConnMethod(DeviceConnFactoryManager.CONN_METHOD.USB)
                                                    .setUsbDevice(usbDevice)
                                                    .setName(name)
                                                    .build();
            } else {
                Log.e(TAG, "connectUsb: usbDevice is null");
            }

        } else {
            if (usbDevice != null) {
                deviceManager.setUsbDevice(usbDevice);
            } else {
                Log.e(TAG, "connectUsb: usbDevice is null");
            }

        }
        if (getManager(pidVid)!=null){
            getManager(pidVid).openPort(needNotify);
        }else {
            UIUtils.showToast("請(qǐng)確保已插入該USB打印機(jī)!");
            Log.e(TAG, String.format("connectUsb: getManager(%s)",pidVid));
        }

    }
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市痪欲,隨后出現(xiàn)的幾起案子悦穿,更是在濱河造成了極大的恐慌,老刑警劉巖业踢,帶你破解...
    沈念sama閱讀 221,820評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件栗柒,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡知举,警方通過(guò)查閱死者的電腦和手機(jī)瞬沦,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,648評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)雇锡,“玉大人逛钻,你說(shuō)我怎么就攤上這事∶烫幔” “怎么了曙痘?”我有些...
    開(kāi)封第一講書(shū)人閱讀 168,324評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)立肘。 經(jīng)常有香客問(wèn)我边坤,道長(zhǎng),這世上最難降的妖魔是什么谅年? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,714評(píng)論 1 297
  • 正文 為了忘掉前任茧痒,我火速辦了婚禮,結(jié)果婚禮上融蹂,老公的妹妹穿的比我還像新娘文黎。我一直安慰自己,他們只是感情好殿较,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,724評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布耸峭。 她就那樣靜靜地躺著,像睡著了一般淋纲。 火紅的嫁衣襯著肌膚如雪劳闹。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 52,328評(píng)論 1 310
  • 那天洽瞬,我揣著相機(jī)與錄音本涕,去河邊找鬼。 笑死伙窃,一個(gè)胖子當(dāng)著我的面吹牛菩颖,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播为障,決...
    沈念sama閱讀 40,897評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼晦闰,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼放祟!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起呻右,我...
    開(kāi)封第一講書(shū)人閱讀 39,804評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤跪妥,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后声滥,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體眉撵,經(jīng)...
    沈念sama閱讀 46,345評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,431評(píng)論 3 340
  • 正文 我和宋清朗相戀三年落塑,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了纽疟。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,561評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡憾赁,死狀恐怖污朽,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情缠沈,我是刑警寧澤膘壶,帶...
    沈念sama閱讀 36,238評(píng)論 5 350
  • 正文 年R本政府宣布,位于F島的核電站洲愤,受9級(jí)特大地震影響颓芭,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜柬赐,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,928評(píng)論 3 334
  • 文/蒙蒙 一亡问、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧肛宋,春花似錦州藕、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,417評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至沉帮,卻和暖如春锈死,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背穆壕。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,528評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工待牵, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人喇勋。 一個(gè)月前我還...
    沈念sama閱讀 48,983評(píng)論 3 376
  • 正文 我出身青樓缨该,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親川背。 傳聞我的和親對(duì)象是個(gè)殘疾皇子贰拿,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,573評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容