最近公司的餐廳點(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包下
類 | 說(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));
}
}