USB打印机开发记录

最近公司的餐厅点餐app又提了新需求,增加对usb打印机的支持。

USB开发,涉及到主从模式

  • 主机模式
    Android设备充当主主设备,并为总线供电。
    例如数字相机,键盘,鼠标和游戏控制器。USB设备与Android应用进行数据交互。

  • 附件模式(从属)
    外部硬件充当USB主设备,并为总线供电。例如手机和电脑连接

主从模式图

做usb通信,首先要先弄清楚哪边是HOST那边是SLAVE。
比如你的android手机做host,要获得slave,用UsbDevice表示slave
要是你的android手机做slave,要获得host,用UsbAccessory表示host

我们的项目是主模式开发,也就是说USB打印机连接到我们的点餐设备上,点餐设备充当USB主设备,并为总线供电。

首先你可以在manifest.xml清单文件中,声明usb权限是否为必须。
如果不声明,默认为false。

如果required=true,则在安装app的同时,如果android设备不支持usb功能,则app是无法安装的。

同时,做声明的好处是,如果你的app受众人群在国外,那么googleplay会帮你过滤掉没有usb功能的设备,也就是说没有usb功能的设备时搜索不到或者无法下载你的app的!

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

声明插入或拔出usb设备时,打开指定的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模式开发时,在设备插入/拔出时启动该activity的action-->
                <action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
                <action android:name="android.hardware.usb.action.USB_DEVICE_DETACHED" />

                <!--accessory模式开发时,在设备插入/拔出时启动该activity的action-->

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

            <!--过滤usb设备,在device_file.xml文件中声明的设备,-->
            <meta-data
                android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
                android:resource="@xml/device_filter" />
           

  </activity>

声明要过滤的usb设备的相关信息

vender-id为生产厂家号 product-id为产品号

其中vendor-id和product-id为插入USB设备的生产厂家号和产品号,在 插入(attached)上面列出的设备之一时,就会弹出选择打开应用程序的对话框。

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

usb开发的相关api在android.hardware.usb包下


image.png
说明
UsbManager 获得 USB 管理器,与连接的 USB 设备通信。
UsbDevice host模式下,USB 设备的抽象,每个UsbDevice 都代表一个 USB 设备。
UsbAccessory (从属模式下,也就是你的android设备相当于一个附件挂在usb设备上)此时的UsbAccessory代表的就是android设备所挂在的那个设备
UsbInterface 表示一个UsbDevice的一个接口。UsbDevice可以具有一个或多个在接口用来通信。
UsbEndpoint 表示一个UsbInterface一个端点,它是接口的通信通道。一个接口可以具有一个或多个端点,与设备进行双向通信通常有一个端点用于输入和一个端点用于输出。
UsbDeviceConnection 表示与设备的连接,用来收发数据,传输控制信息。
UsbRequest 通过UsbDeviceConnection与设备通信的异步请求,只用来异步通信
UsbConstants USB常量定义,与Linux内核的linux / usb / ch9.h中的定义相对应

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中注册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() + "被授予了权限!");
                    } else {
                        UIUtils.showToast(context, "USB设备请求被拒绝");
                    }
                }
            } else if (UsbManager.ACTION_USB_DEVICE_DETACHED.equals(action)) {
                if (mUsbDevice != null) {
                    UIUtils.showToast(context, "有设备拔出");
                    EventBus.getDefault().post(new UsbAttachedChangedEvent(mUsbDevice, false));
                }
            } else if (UsbManager.ACTION_USB_DEVICE_ATTACHED.equals(action)) {
                if (mUsbDevice != null) {
                    UIUtils.showToast(context, "有设备插入");
                    EventBus.getDefault().post(new UsbAttachedChangedEvent(mUsbDevice, true));
                }
            }
        }
    };

DeviceConnManager类的openPort方法

  /**
     * 打开端口
     *
     * @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;
        }
        //端口打开成功后,检查连接打印机所使用的打印机指令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设备每次查吧usbdevice的deviceId都会变,所以需要重新设备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("请确保已插入该USB打印机!");
            Log.e(TAG, String.format("connectUsb: getManager(%s)",pidVid));
        }

    }
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,076评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,658评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,732评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,493评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,591评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,598评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,601评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,348评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,797评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,114评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,278评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,953评论 5 339
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,585评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,202评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,442评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,180评论 2 367
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,139评论 2 352

推荐阅读更多精彩内容