硬件條件:
- OTG接口轉換器(或者自帶usb接口的設備(大頭))
- USB掃碼槍(支持USB虛擬串口)
- 安卓設備
實現方式:
- 串口方式
- USB方式
使用場景:
在掃碼槍連接機器的時候,當掃碼槍掃描到內容時,獲取掃描到的內容。
解決的問題:
之前掃碼槍使用的是USB模式(即掃描到的內容會直接輸入到EditText之中,並且末尾會增加一個回車鍵),但是後來發現,當二維碼包含中文的時候,中文不會輸出進來,於是乎,使用以上兩種方式實現。
實現思路:
連接掃碼槍,因爲數據肯定是以字節流的方式發送的,那麼我們只需獲取到輸入的字節流,自己處理成需要的內容就可以了。
實現方式------串口方式:
需要經過以下幾個步驟:
- 集成谷歌原生serial_port包,參考該文章集成就可以了: Android串口集成
- 尋找設備串口地址(這個咱們僅僅是爲了獲取串口地址,因爲串口地址是固定的,所以這裏可以不加在程序中,咱們目的是爲了知道USB掃碼槍的串口地址)
- 連接設備
- 獲取數據流
步驟一:集成谷歌原生serial_port包,這個自己看上面教程了。
步驟二:尋找設備串口地址,在完成步驟一之後,會有一個SerialPortFinder的類,這個類的作用是尋找咱們安卓設備上連接的串口設備(包括掃碼槍),具體獲取掃碼槍串口地址的思路:獲取未插入掃碼槍時所有設備的串口地址------》獲取插入掃碼槍時所有設備的串口地址
-----》看看多出那個,多出的那個就是掃碼槍的串口地址了,廢話不多說,直接貼代碼了:
case R.id.btn_print://獲取所有串口設備地址,mFinder就是SerialPortFinder
StringBuffer stringBuffer = new StringBuffer();
for (String str:mFinder.getAllDevicesPath()) {
stringBuffer.append(str + "\n");
}
mTvMessage.setText(stringBuffer.toString());
break;
怎麼找USB掃碼槍你懂我意思吧。
步驟三:連接設備:
廢不多講,首先咱們連接到串口設備,如果你連接不上,那麼就是步驟一有問題,請到步驟一那篇博客問問作者:
private String mSerialPath = "/dev/ttyUSB0" ;//物理串口地址,這個就是咱們步驟二找到的地址了,這是我的設備的地址,你的自己找去
private int baudrate = 9600;//波特率,這個是可以掃碼槍自己設置的,看說明書
private SerialPort mSerialPort;
private InputStream mInputStream;
@Override
protected int getContentView() {
return R.layout.activity_test;
}
@Override
protected void initView() {
super.initView();
initSerialPort();
}
private void initSerialPort(){//連接串口設備,建議加try,硬件設備你永遠不知道會爲啥崩潰
try {
mSerialPort = new SerialPort(new File(mSerialPath),baudrate);
mInputStream = mSerialPort.getInputStream();
ReadThread thread = new ReadThread();//這是讀取數據流的線程,代碼在下方貼出
thread.start();//啓動數據流讀取線程
} catch (Exception e) {
e.printStackTrace();
}
}
步驟四:獲取數據流,這時候步驟三,我們開啓了一個線程讀取掃碼槍的數據流,這時候我寫了一個死循環,輪詢掃碼槍發送的內容,線程代碼如下
private InputStream mInputStream;
/**
* 讀串口線程
*/
private class ReadThread extends Thread {//這是我的內部類,我代碼習慣不好,別瞎雞兒抄
@Override
public void run() {
super.run();
while ((!Thread.currentThread().isInterrupted())) {
final int size;
try {
if (mInputStream == null)
return;
final byte[] buffer = new byte[512];
size = mInputStream.read(buffer);//這就是掃描到的內容了
if (size > 0) {
runOnUiThread(new Runnable() {
@Override
public void run() {
try {
ToastUtils.makeText(TestActivity.this,new String(buffer,"GB2312"));//此處我設置的編碼是GB2312,這個可以看說明書,可以設置的
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
});
}
Thread.sleep(300);//你自己可以設置睡眠時間,這個睡眠時間可以會影響識別速度,那麼就可以叫用戶加錢,你懂我意思吧。
} catch (Exception e) {
//如果拋出異常則再次設置中斷請求
Thread.currentThread().interrupt();
return;
}
}
}
}
嗯,到了這一步,大功告成了。
注意:串口方式實現的話兼容性不好,因爲遇到部分安卓設備直接閹割了這部分功能,所以我纔會研究USB方式的。
驚不驚喜,意不意外。
實現方式------USB方式:
該種方式好處在於不需要集成serial_port包,但是裏面一些類理解比較靈性,USB方式需要經過以下幾個步驟:
- 尋找需要連接的devices設備
- 連接設備
- 獲取數據流
步驟一:尋找需要連接的devices設備,這裏的話有兩種場景
1.USB掃碼槍一直插在安卓設備上,我們需要在程序啓動的時候自動找到並且連接
2.USB掃碼槍在熱拔插的情況下,我們需要在插入的時候連接
第一種的話我是直接寫了一個方法,在程序啓動的時候檢查時候插入需要支持的設備類型,如果有,那麼直接連接,判斷方法是通過設備的pid和vid對比進行判斷,pid和vid獲取方式自己百度,或者把掃碼槍插在電腦上,在設備管理可以看到,我通過findDevices方法找到需要連接的掃碼槍設備。
貼代碼:
private UsbManager mManager;
//查找已連接的設備
UsbDevice findDevices() {
if (null == mManager) {
return null;
}
HashMap<String, UsbDevice> mMap = mManager.getDeviceList();
for (UsbDevice device : mMap.values()) {
if (BaseScanner.isS5920(device) || BaseScanner.isS5600(device)) {//判斷設備是否支持的類型
return device;
}
}
return null;
}
這是baseScanner,這個類名我覺得不夠準確,它是用來記錄需要支持的設備的信息的,包含設備的判斷方法:
public class BaseScanner{
private static final int scanner5600_pid = 4456;
private static final int scanner5600_vid = 866;
private static final int scanner5920_pid = 9527;
private static final int scanner5920_vid = 549;
public static boolean isS5600(UsbDevice usbDevice){//是不是s5600掃碼槍
return usbDevice.getProductId() == scanner5600_pid && usbDevice.getVendorId() == scanner5600_vid;
}
public static boolean isS5920(UsbDevice usbDevice){//是不是s5920
return usbDevice.getProductId() == scanner5920_pid && usbDevice.getVendorId() == scanner5920_vid;
}
}
第二種USB掃碼槍熱拔插連接方式,這時候我們採用的是廣播監聽的方式,因爲設備撥叉都會有廣播發送,這條廣播包含的信息包含一個USBDevices對象(也就是掃碼槍),我們需要動態註冊拔,插兩條廣播,靜態註冊不生效,代碼如下:
//這是廣播類,因爲我的掃描是寫在一個service中,所以把連接的設備交給service處理就可以
public class USBBroadcastReceiver extends BroadcastReceiver{
@Override
public void onReceive(Context context, Intent intent) {
// TODO Auto-generated method stub
String action = intent.getAction();
if (UsbManager.ACTION_USB_DEVICE_DETACHED.equals(action)) {
UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
if (device != null) {
// call your method that cleans up and closes communication with the device
Toast.makeText(context, "拔出", Toast.LENGTH_LONG).show();
}
}else if(UsbManager.ACTION_USB_DEVICE_ATTACHED.equals(action)){//設備插入
UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);//獲取插入的設備
Intent intent1 = new Intent(context,ScannerService.class);
Bundle bundle = new Bundle();
bundle.putParcelable("device",device);
intent1.putExtra("data",bundle);//將設備包入intent中,交給service處理
context.startService(intent1);
Toast.makeText(context, "插入", Toast.LENGTH_LONG).show();
}
}
}
我是在程序啓動的時候就註冊了這個廣播如下:
public class MyApplication extends Application {
private USBBroadcastReceiver mUsbReceiver;
@Override
public void onCreate() {
super.onCreate();
registBroadCast();
}
@Override
public void onTerminate() {
unregisterReceiver(mUsbReceiver);
super.onTerminate();
}
private void registBroadCast(){//註冊USB插拔廣播
mUsbReceiver = new USBBroadcastReceiver();
IntentFilter usbDeviceStateFilter = new IntentFilter();
usbDeviceStateFilter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);//插入設備狀態
usbDeviceStateFilter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED);//拔出設備狀態
registerReceiver(mUsbReceiver, usbDeviceStateFilter);
}
}
步驟二,連接設備,這邊我把所有都寫註釋裏面:
private UsbDevice mDevide;
private UsbDeviceConnection mConnect;
private static final String ACTION_USB_PERMISSION = "com.android.example.USB_PERMISSION";
private static UsbConnectManager mInstance;
private ScannerListener mListener;
private UsbManager mManager;
private UsbEndpoint mUsbEndpointIn;
private UsbInterface mUsbInterface;
private Thread mReadingthread;
public void connetDevice(UsbDevice device, Context context) {
disConnect();//斷開之前的連接
mDevide = device;//當前需要連接的設備
try {
if (!mManager.hasPermission(mDevide)) {//檢查是否有掃碼槍的訪問權限,如果沒有,那麼請求一下臨時權限
PendingIntent intent = PendingIntent.getBroadcast(context, 0, new Intent(ACTION_USB_PERMISSION), 0);
mManager.requestPermission(mDevide, intent);
}
mConnect = mManager.openDevice(mDevide);//打開設備
mUsbInterface = device.getInterface(0);//獲取數據的接口.這個可以理解成設備有多少個線頭子
mUsbEndpointIn = mUsbInterface.getEndpoint(0);//獲取設備的輸出流向,輸入或者輸出因爲usb掃碼槍只有一個輸出項,所以直接選擇0
if (mConnect.claimInterface(mUsbInterface, true)) {//獲取設備數據寫入流,如果獲取成功,那麼數據已經通常,我這邊寫的是一個獲取數據,輸入數據的話把getEndpoint(0)換成輸入的接口即可
Log.i(TAG, "connetDevice: find device,start get data!!!");
startReading();//開啓讀取數據流的線程
} else {
mConnect.close();
Log.i(TAG, "connetDevice: not find device !!!");
}
} catch (Exception e) {
e.printStackTrace();
}
}
步驟三,獲取輸入的流,並解析成需要的內容,代碼如下:
//開線程讀取數據
private void startReading() {
if (null != mReadingthread) {
mReadingthread.interrupt();
}
mReadingthread = new Thread(new Runnable() {
@Override
public void run() {
while (!mReadingthread.isInterrupted()) {
synchronized (this) {
byte[] bytes = new byte[128];
int ret = mConnect.bulkTransfer(mUsbEndpointIn, bytes, bytes.length, 100);//獲取數據流
if (ret > 0) {
try {
byte[] bs = new byte[ret];
System.arraycopy(bytes, 0, bs, 0, ret);//獲取數據長度
String str = new String(bs,"GB2312");//轉換成漢字
if (null != mListener){
mListener.onScanner(str);//你自己需要的處理方式
}
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
}
}
}
});
mReadingthread.start();
}
OK啦,USB模式的也完成了。
有疑問或者有建議的膀友可以加羣:497438697 我是羣裏的 杭州-大魔王,有艾特必應。
祝大家新年快樂,萬事亨通!!!