前言:公司属于北斗通信行业,项目大多都需要和各式各样的硬件设备相结合来满足项目需求,因此所涉及到的各种技术也相对比较冷门。前段时间有个项目用到了一款定制北斗设备,需要用到它自带的 type-c 线连接手机使用,开发时发现它是通过 USB(accessory)来连接手机设备的,现在项目完成了,就在这里记录和分享一下,有任何错漏或可优化之处欢迎大家留言。
一、申请权限
将以下权限申请添加到 AndroidManifest 文件中:
<uses-permission android:name="android.permission.USB_PERMISSION" />
开发时参考的文档中还提到需要另一个权限 “android.hardware.usb.accessory” 但是我这里没有添加也能正常使用,如果调不通的话可以试着把这个权限也加进去
二、直接上代码
复制再把报红的部分直接去掉或者换成自己的就能直接使用
package com.example.SecondProject.Utils.Transfer.USB;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.hardware.usb.UsbAccessory;
import android.hardware.usb.UsbManager;
import android.os.ParcelFileDescriptor;
import android.util.Log;
import android.widget.Toast;
import com.example.SecondProject.Base.MainApplication;
import com.example.SecondProject.BuildConfig;
import com.example.SecondProject.Global.Constant;
import com.example.SecondProject.Global.Variable;
import com.example.SecondProject.Utils.DataUtil;
import com.example.SecondProject.Utils.NotificationCenter;
import com.example.SecondProject.Utils.ProtocolUtil;
import java.io.ByteArrayOutputStream;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
// USB附件连接工具
public class USBAccessoryTransferUtil {
String TAG = "USBAccessoryTransferUtil";
MainApplication APP = MainApplication.getInstance(); // 主程序
public UsbManager usbManager = (UsbManager) APP.getSystemService(Context.USB_SERVICE);
private BroadcastReceiver usbAccessoryReceiver = null; // 广播监听:判断设备授权操作
public UsbAccessory usbAccessory = null; // 当前连接的 USB附件 对象
public ParcelFileDescriptor fileDescriptor = null;
public FileInputStream inputStream = null; // 输入流
public FileOutputStream outputStream = null; // 输出流
public ReadThread readThread = null; // 接收数据线程
private final String ACTION_USB_PERMISSION = BuildConfig.APPLICATION_ID + ".INTENT_ACTION_GRANT_USB_ACCESSORY"; // usb权限请求标识
private final String IDENTIFICATION = "WCHAccessory1"; // 目标设备的序列号标识
// 特定厂商的设备标识,自行修改或删除 -------------------------------------
public String ManufacturerString = "mManufacturer=WCH";
public String ModelString1 = "mModel=WCHUARTDemo";
public String VersionString = "mVersion=1.0";
// 单例 -------------------------------------------------------------------
private static USBAccessoryTransferUtil usbAccessoryTransferUtil;
public static USBAccessoryTransferUtil getInstance() {
if(usbAccessoryTransferUtil == null){
usbAccessoryTransferUtil = new USBAccessoryTransferUtil();
}
return usbAccessoryTransferUtil;
}
public void connect(){
// “Variable.isConnectUSBAccessory” 我的变量标识,自行删除或修改
if(!Variable.isConnectUSBAccessory){
registerReceiver(); // 注册广播监听
refreshDevice(); // 拿到设备
connectDevice(); // 连接设备
}
}
// 注册usb授权监听广播
public void registerReceiver(){
usbAccessoryReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
Log.e(TAG, "onReceive: "+action);
// 收到 ACTION_USB_PERMISSION 请求权限广播
if (ACTION_USB_PERMISSION.equals(action)) {
// 确保只有一个线程执行里面的任务,不与其他应用冲突
synchronized (this) {
usbAccessory = (UsbAccessory) intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY);
if(usbAccessory==null){Log.e(TAG, "usbAccessory 对象为空" );return;}
// 判断是否授予了权限
boolean havePermission = intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false);
if (havePermission) {
APP.showToast("授予 USB 权限", Toast.LENGTH_SHORT);
connectDevice(); // 授权成功,直接连接
}
else {
APP.showToast("拒绝 USB 权限", Toast.LENGTH_SHORT);
}
}
}
// 收到 USB附件 拔出的广播
else if (UsbManager.ACTION_USB_ACCESSORY_DETACHED.equals(action)) { // android.hardware.usb.action.USB_ACCESSORY_DETACHED
// 断开连接
disconnect();
}
else {
Log.e(TAG, "registerReceiver/onReceive:其它");
}
}
};
IntentFilter filter = new IntentFilter(ACTION_USB_PERMISSION);
filter.addAction(UsbManager.ACTION_USB_ACCESSORY_DETACHED); // 当收到 usb附件 插入广播动作
APP.registerReceiver(usbAccessoryReceiver, filter); // 注册
}
public void refreshDevice(){
// Log.e(TAG, "refreshDevice: 1");
UsbAccessory[] accessories = usbManager.getAccessoryList(); // 可用 UsbAccessory 列表
if(accessories == null || accessories.length == 0) {return;}
else {
Log.e(TAG, "获取到的设备数量: " + accessories.length);
// 如果只有一个就直接获取
if(accessories.length == 1){
usbAccessory = accessories[0]; // 默认拿到他的第1个
}
// 如果有多个就根据厂商提供的标识判断一下
else {
for (UsbAccessory accessory : accessories) {
if(accessory.getSerial().equals(IDENTIFICATION)){
usbAccessory = accessory;
}
}
}
}
if(usbAccessory == null){return;}
// 开始连接设备
boolean isMyDevice = checkDevice(usbAccessory); // 这是我的项目设备的判断方法,直接去掉或者改成你自己的
if(isMyDevice){
// 判断是否拥有权限,如果没权限就申请
if (!usbManager.hasPermission(usbAccessory)) {
synchronized (usbAccessoryReceiver) {
APP.showToast("请授予 USB 权限", Toast.LENGTH_SHORT);
PendingIntent pendingIntent = PendingIntent.getBroadcast(APP, 0, new Intent(ACTION_USB_PERMISSION), 0);
usbManager.requestPermission(usbAccessory,pendingIntent);
}
}
}else {
Log.e(TAG, "这不是我的设备");
usbAccessory = null;
}
}
public void connectDevice(){
Log.e(TAG, "connectDevice: 1");
if(usbAccessory == null){return;}
Log.e(TAG, "connectDevice: 2");
if(usbManager.hasPermission(usbAccessory)){
Log.e(TAG, "connectDevice: 3");
fileDescriptor = usbManager.openAccessory(usbAccessory);
if(fileDescriptor != null){
Log.e(TAG, "connectDevice: 4");
FileDescriptor fd = fileDescriptor.getFileDescriptor();
// 拿到输入/输出流
inputStream = new FileInputStream(fd);
outputStream = new FileOutputStream(fd);
// 开启接收数据线程
readThread = new ReadThread();
readThread.start();
}
}else {
APP.showToast("请先授予权限再连接",0);
}
}
public boolean checkDevice(UsbAccessory usbAccessory){
if( -1 == usbAccessory.toString().indexOf(ManufacturerString)) {
APP.showToast("Manufacturer is not matched!", Toast.LENGTH_SHORT);
return false;
}
if( -1 == usbAccessory.toString().indexOf(ModelString1) ) {
APP.showToast("Model is not matched!", Toast.LENGTH_SHORT);
return false;
}
if( -1 == usbAccessory.toString().indexOf(VersionString)) {
APP.showToast("Version is not matched!", Toast.LENGTH_SHORT);
return false;
}
if(Variable.DebugMode){
APP.showToast("制造商、型号和版本匹配", Toast.LENGTH_SHORT);
}
return true;
}
// 下发数据(16进制字符串)
public void write(String data_hex) {
if(outputStream==null){return;}
try {
byte[] data_bytes = DataUtil.hexStringToBytes(data_hex);
this.outputStream.write(data_bytes);
Log.e(TAG, "write 下发的指令是: " + DataUtil.hex2String(data_hex) );
} catch (Exception e) {
e.printStackTrace();
}
}
// 下发初始化指令,改成你自己的或删掉
public void init_device(){
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
SetConfig(19200,(byte)8,(byte)1,(byte)0,(byte)0);
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
write(ProtocolUtil.CCRMO("PWI",2,9)); // 设置pwi信号输出频度
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
write(ProtocolUtil.CCRMO("MCH",1,0)); // 关闭设备的HCM指令输出
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
write(ProtocolUtil.CCRNS(5,5,5,5,5,5)); // 设置rn指令输出频度
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
write(ProtocolUtil.CCICR(0,"00"));
}
}).start();
}
// 断开连接
public void disconnect(){
try {
// 停止数据监听
if(readThread != null){
readThread.close();
readThread = null;
}
// 关闭输入输出流
if(inputStream != null){
inputStream.close();
inputStream = null;
}
if(outputStream != null){
outputStream.close();
outputStream = null;
}
if(fileDescriptor != null){
fileDescriptor.close();
fileDescriptor = null;
}
// 清除设备
if(usbAccessory != null){
usbAccessory = null;
}
// 注销广播
if(usbAccessoryReceiver != null){
APP.unregisterReceiver(usbAccessoryReceiver);
usbAccessoryReceiver = null;
}
}catch (Exception e){
e.printStackTrace();
}
APP.showToast("断开连接",0);
Variable.isConnectUSBAccessory = false; // 修改连接标识
NotificationCenter.standard().postNotification(Constant.DISCONNECT_USB_ACCESSORY); // 发送全局广播
}
// 读取 USB附件 数据线程
private byte[] readBuffer = new byte[1024 * 2]; // 缓冲区
private class ReadThread extends Thread {
boolean alive = true;
ReadThread(){
this.setPriority(Thread.MAX_PRIORITY); // 设置线程的优先级:最高级
}
byte[] buf = new byte[2048]; // 每次从输入流读取的最大数据量:这个大小直接影响接收数据的速率,根据需求修改
ByteArrayOutputStream baos = new ByteArrayOutputStream();
public void run() {
if(inputStream == null){return;}
init_device(); // 下发初始化指令,根据自己的设备修改或直接删掉
Variable.isConnectUSBAccessory = true; // 修改连接标识
NotificationCenter.standard().postNotification(Constant.CONNECT_USB_ACCESSORY); // 发送广播
Log.e(TAG, "开启数据监听");
while(alive) {
try {
int size = inputStream.read(buf);
if(size>0){
baos.write(buf,0,size);
readBuffer = baos.toByteArray();
// 根据需求设置停止位:由于我需要接收的是北斗指令,指令格式最后两位为 “回车换行(\r\n)” 所以我只需要判断数据末尾两位
// 设置停止位,当最后两位为 \r\n 时就传出去
if (readBuffer.length >= 2 && readBuffer[readBuffer.length - 2] == (byte)'\r' && readBuffer[readBuffer.length - 1] == (byte)'\n') {
if(onReceiveData!=null){
onReceiveData.receiveData(readBuffer);
}
baos.reset(); // 重置
}
// 设置停止位:当最后一位为 \n 时就传出去
// if (readBuffer.length >= 1 && readBuffer[readBuffer.length - 1] == (byte)'\n') {
// if(onReceiveData!=null){
// onReceiveData.receiveData(readBuffer);
// }
// baos.reset(); // 重置
// }
// 设置停止位:当读取的数据长度为 30 时,就传出去(用这种方法要把每次读取的数据量改小 1-10)
// if (readBuffer.length == 30) {
// if(onReceiveData!=null){
// onReceiveData.receiveData(readBuffer);
// }
// baos.reset(); // 重置
// }
}
sleep(10); // 设置循环间隔
} catch (Throwable var3) {
if(var3.getMessage() != null){
Log.e(TAG, "ReadThread:" + var3.getMessage());
}
return;
}
}
}
public void close(){
alive = false;
this.interrupt();
}
}
// 沁恒设备设置波特率等参数方法
public void SetConfig(int baud, byte dataBits, byte stopBits, byte parity, byte flowControl) {
Log.e("TAG", "设置: " + baud + "/" + dataBits + "/" + stopBits + "/" + parity + "/" + flowControl);
byte tmp = 0x00;
byte baudRate_byte = 0x00;
byte [] writeusbdata = new byte[5];
writeusbdata[0] = 0x30;
switch(baud) {
case 300:baudRate_byte = 0x00;break;
case 600:baudRate_byte = 0x01;break;
case 1200:baudRate_byte = 0x02;break;
case 2400:baudRate_byte = 0x03;break;
case 4800:baudRate_byte = 0x04;break;
case 9600:baudRate_byte = 0x05;break;
case 19200:baudRate_byte = 0x06;break;
case 38400:baudRate_byte = 0x07;break;
case 57600:baudRate_byte = 0x08;break;
case 115200:baudRate_byte = 0x09;break;
case 230400:baudRate_byte = 0x0A;break;
case 460800:baudRate_byte = 0x0B;break;
case 921600:baudRate_byte = 0x0C;break;
default:baudRate_byte = 0x05;break; // default baudRate "9600"
}
// prepare the baud rate buffer
writeusbdata[1] = baudRate_byte;
switch(dataBits){
case 5:tmp |= 0x00;break; //reserve
case 6:tmp |= 0x01;break; //reserve
case 7:tmp |= 0x02;break;
case 8:tmp |= 0x03;break;
default:tmp |= 0x03;break; // default data bit "8"
}
switch(stopBits){
case 1:tmp &= ~(1 << 2);break;
case 2:tmp |= (1 << 2);break;
default:tmp &= ~(1 << 2);break; // default stop bit "1"
}
switch(parity){
case 0:tmp &= ~( (1 << 3) | (1 << 4) | (1 << 5) );break; //none
case 1:tmp |= (1 << 3);break; //odd
case 2:tmp |= ( (1 << 3) | (1 << 4) );break; //event
case 3:tmp |= ( (1 << 3) | (1 << 5) );break; //mark
case 4:tmp |= ( (1 << 3) | (1 << 4) | (1 << 5) );break; //space
default:tmp &= ~( (1 << 3) | (1 << 4) | (1 << 5));break;//default parity "NONE"
}
switch(flowControl){
case 0:tmp &= ~(1 << 6);break;
case 1:tmp |= (1 << 6);break;
default:tmp &= ~(1 << 6);break; //default flowControl "NONE"
}
// dataBits, stopBits, parity, flowControl
writeusbdata[2] = tmp;
writeusbdata[3] = 0x00;
writeusbdata[4] = 0x00;
write(DataUtil.bytes2Hex(writeusbdata));
writeusbdata = null;
}
// 接口 ---------------------------------------------
public interface onReceiveData{
void receiveData(byte[] data);
}
public onReceiveData onReceiveData;
public void setOnReceiveData(onReceiveData onReceiveData){
this.onReceiveData = onReceiveData;
}
}
三、使用例子
1. 使用方法
使用 setOnReceiveData 方法设置数据监听处理使用 connect() 方法连接
使用 write() 方法下发数据
记得要在适当的位置使用 disconnect() 方法断开连接释放资源
设备收到下发的指令数据并做出响应,通信成功
2. 设备接入监听
Android系统在每次拔插 USB 设备时都会广播一个意图,这样如果我们需要在 USB 设备连接时进行某种操作只需要在 manifest 文件里面给对应的 activty 添加一个声明并指定过滤规则即可
<intent-filter>
<action android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED" />
</intent-filter>
<meta-data android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED" android:resource="@xml/device_filter" />
在xml资源文件夹中添加 accessory_filter 文件
附上过滤规则文件代码:这里指定了我需要连接的设备标识,根据厂商提供的数据修改即可
<?xml version="1.0" encoding="utf-8"?>
<resources>
<usb-accessory model="WCHUARTDemo" manufacturer="WCH" version="1.0"/>
</resources>
通过声明以上的intent-filter和meta-data,表明它是一个能够处理USB_ACCESSORY设备连接事件的Activity,并且根据res/xml/accessory_filter.xml中的规则对连接的USB_ACCESSORY设备进行过滤。这样,在Android设备连接USB_ACCESSORY设备时,系统就会发出设备接入,这时授权成功后就能直接跳转到对应的 activity。
四、小结
整个连接流程大致是这样的:
获取当前系统可用的 USB 设备列表 → 选中对应的USB设备并申请权限(首次)→ 拿到输入/输出流
这里有一点需要注意的是读取数据要根据自己的实际需求作出修改,例如我要处理的是北斗协议数据,它总是以回车换行(/r/n) 为结尾因此我只要以最后两个字节作为我的判断条件即可。
另附一个北斗协议解析工具:北斗协议解析(北三)_TTTTao2323的博客-CSDN博客