这篇文章起源于之前遗留的一个问题(https://www.jianshu.com/p/189816294b37),公司的产品需要使用开发板做host与多个串口通信,多个串口同时打开会发生crash,必须先连接成功一个,再插另一根线连接另一个,很不方便,当时想尽办法并没有解决,最近在研究framework的时候有了新的发现。
环境
操作系统:ubuntu 16.04
源码版本:5.1.1
目标
android系统做host与多个串口同时通信。
发现过程
当时在使用pl2303出现这个问题时,我曾一度怀疑是芯片厂商提供的jar包代码有bug,因为我使用官方的demo打开多个串口时也有相同的问题,但是没有源码,向台湾厂家反应也毫无回应,最后不了了之。
最近这个问题又被提起,心想干脆不使用厂家封装好的jar包,自己用系统api来试试能不能通信,没想到刚写了几行代码就发现了问题:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val usbManager:UsbManager = getSystemService(Context.USB_SERVICE) as UsbManager
btnDeviceList.setOnClickListener {
val deviceList = usbManager.deviceList
println("设备数目:${deviceList.size}")
deviceList.forEach { println(it) }
}
}
}
非常简单的一段代码,只是检测一下当前连接多少设备,并将设备信息依次打印出来,我分别试了连接一个设备和连接两个设备,下面看下日志:
05-08 06:32:19.059 2101-2101/com.lxf.usbdemo I/System.out: 设备数目:1
05-08 06:32:19.060 2101-2101/com.lxf.usbdemo I/System.out: /dev/bus/usb/001/003=UsbDevice[mName=/dev/bus/usb/001/003,mVendorId=1659,mProductId=8963,mClass=0,mSubclass=0,mProtocol=0,mManufacturerName=Prolific Technology Inc. ,mProductName=USB-Serial Controller D,mSerialNumber=null,mConfigurations=[
05-08 06:32:19.060 2101-2101/com.lxf.usbdemo I/System.out: UsbConfiguration[mId=1,mName=null,mAttributes=160,mMaxPower=50,mInterfaces=[
05-08 06:32:19.060 2101-2101/com.lxf.usbdemo I/System.out: UsbInterface[mId=0,mAlternateSetting=0,mName=null,mClass=255,mSubclass=0,mProtocol=0,mEndpoints=[
05-08 06:32:19.060 2101-2101/com.lxf.usbdemo I/System.out: UsbEndpoint[mAddress=129,mAttributes=3,mMaxPacketSize=10,mInterval=1]
05-08 06:32:19.060 2101-2101/com.lxf.usbdemo I/System.out: UsbEndpoint[mAddress=2,mAttributes=2,mMaxPacketSize=64,mInterval=0]
05-08 06:32:19.060 2101-2101/com.lxf.usbdemo I/System.out: UsbEndpoint[mAddress=131,mAttributes=2,mMaxPacketSize=64,mInterval=0]]]]
这是一台设备的信息,当时我看到时心里是毫无波动的,因为除了看出来连接成功了其他啥也看不懂/(ㄒoㄒ)/~~,然后看两台设备的日志:
05-08 06:34:47.284 2101-2101/com.lxf.usbdemo I/System.out: 设备数目:2
05-08 06:34:47.284 2101-2101/com.lxf.usbdemo I/System.out: /dev/bus/usb/001/004=UsbDevice[mName=/dev/bus/usb/001/004,mVendorId=1659,mProductId=8963,mClass=0,mSubclass=0,mProtocol=0,mManufacturerName=Prolific Technology Inc. ,mProductName=USB-Serial Controller D,mSerialNumber=null,mConfigurations=[
05-08 06:34:47.285 2101-2101/com.lxf.usbdemo I/System.out: UsbConfiguration[mId=1,mName=null,mAttributes=128,mMaxPower=50,mInterfaces=[]]
05-08 06:34:47.285 2101-2101/com.lxf.usbdemo I/System.out: /dev/bus/usb/001/003=UsbDevice[mName=/dev/bus/usb/001/003,mVendorId=1659,mProductId=8963,mClass=0,mSubclass=0,mProtocol=0,mManufacturerName=Prolific Technology Inc. ,mProductName=USB-Serial Controller D,mSerialNumber=null,mConfigurations=[
05-08 06:34:47.285 2101-2101/com.lxf.usbdemo I/System.out: UsbConfiguration[mId=1,mName=null,mAttributes=160,mMaxPower=50,mInterfaces=[
05-08 06:34:47.285 2101-2101/com.lxf.usbdemo I/System.out: UsbInterface[mId=0,mAlternateSetting=0,mName=null,mClass=255,mSubclass=0,mProtocol=0,mEndpoints=[
05-08 06:34:47.285 2101-2101/com.lxf.usbdemo I/System.out: UsbEndpoint[mAddress=129,mAttributes=3,mMaxPacketSize=10,mInterval=1]
05-08 06:34:47.285 2101-2101/com.lxf.usbdemo I/System.out: UsbEndpoint[mAddress=2,mAttributes=2,mMaxPacketSize=64,mInterval=0]
05-08 06:34:47.285 2101-2101/com.lxf.usbdemo I/System.out: UsbEndpoint[mAddress=131,mAttributes=2,mMaxPacketSize=64,mInterval=0]]]]
2台设备的日志信息居然和1台设备的信息差不多,仔细看发现第一台设备的mInterfaces属性为空,回想之前发生crash的错误信息,于是大胆怀疑是系统源码的问题,毕竟这次是完全使用系统api的。
解决过程
android启动后会加载SystemServer来启动很多系统服务,我们看下SystemServer的结构:
方法并不多,一看就懂,在启动服务的方法中搜索一下我们关心的usb服务:
if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_USB_HOST)
|| mPackageManager.hasSystemFeature(
PackageManager.FEATURE_USB_ACCESSORY)) {
// Manage USB host and device support
mSystemServiceManager.startService(USB_SERVICE_CLASS);
}
...
private static final String USB_SERVICE_CLASS =
"com.android.server.usb.UsbService$Lifecycle";
然后我们打开UsbService:
/**
* UsbService manages all USB related state, including both host and device support.
* Host related events and calls are delegated to UsbHostManager, and device related
* support is delegated to UsbDeviceManager.
*/
public class UsbService extends IUsbManager.Stub {
翻译一下,UsbService管理所有的USB相关状态,支持做主机(host)和设备(device)。做主机时相关事件和回调由UsbHostManager管理,做设备时相关事件和回调由UsbDeviceManager管理。
于是打开USBHostManager,发现了一些我们熟悉的东西:
private UsbDevice mNewDevice;
private UsbConfiguration mNewConfiguration;
private UsbInterface mNewInterface;
private ArrayList<UsbConfiguration> mNewConfigurations;
private ArrayList<UsbInterface> mNewInterfaces;
private ArrayList<UsbEndpoint> mNewEndpoints;
很明显,这就是我们开始在日志中看到的相关信息。再看下结构图:
说下比较重要的几个方法:
- systemReady:该类的起点,运行monitorUsbHostBus,一个native方法。
- beginUsbDeviceAdded:有新的设备时由monitorUsbHostBus()调用,接下来会继续由jni调用addUsbConfiguration、addUsbInterface、addUsbEndpoint方法添加相应参数信息,并最后调用endUsbDeviceAdded方法。
- usbDeviceRemoved:移除设备时由monitorUsbHostBus()调用。
联想我们开始发现的mInterfaces为空问题,看下addUsbConfiguration、addUsbInterface、addUsbEndpoint三个方法:
/* Called from JNI in monitorUsbHostBus() to report new USB configuration for the device
currently being added. Returns true if successful, false in case of error.
*/
private void addUsbConfiguration(int id, String name, int attributes, int maxPower) {
if (mNewConfiguration != null) {
mNewConfiguration.setInterfaces(
mNewInterfaces.toArray(new UsbInterface[mNewInterfaces.size()]));
mNewInterfaces.clear();
}
mNewConfiguration = new UsbConfiguration(id, name, attributes, maxPower);
mNewConfigurations.add(mNewConfiguration);
}
/* Called from JNI in monitorUsbHostBus() to report new USB interface for the device
currently being added. Returns true if successful, false in case of error.
*/
private void addUsbInterface(int id, String name, int altSetting,
int Class, int subClass, int protocol) {
if (mNewInterface != null) {
mNewInterface.setEndpoints(
mNewEndpoints.toArray(new UsbEndpoint[mNewEndpoints.size()]));
mNewEndpoints.clear();
}
mNewInterface = new UsbInterface(id, altSetting, name, Class, subClass, protocol);
mNewInterfaces.add(mNewInterface);
}
/* Called from JNI in monitorUsbHostBus() to report new USB endpoint for the device
currently being added. Returns true if successful, false in case of error.
*/
private void addUsbEndpoint(int address, int attributes, int maxPacketSize, int interval) {
mNewEndpoints.add(new UsbEndpoint(address, attributes, maxPacketSize, interval));
}
发现在添加相应usb信息时会判断mNewConfiguration和mNewInterface是否为null,如果不为null则会进行mNewInterfaces.clear()操作,而在endUsbDeviceAdded方法中并没有将mNewConfiguration和mNewInterface重置为null的操作,意味着除了第一次检测到usb接入,后续的都会执行clear操作。
因此尝试在add相关信息之前将mNewConfiguration和mNewInterface重置为null,所以在beginUsbDeviceAdded方法的最后添加代码:
mNewConfiguration = null;
mNewInterface = null;
System.out.println("UsbHostManager by lxf");
重新编译services模块后刷入开发板,重新运行测试代码,日志是这样的:
05-08 07:48:16.489 2101-2101/com.lxf.usbdemo I/System.out: 设备数目:2
05-08 07:48:16.490 2101-2101/com.lxf.usbdemo I/System.out: /dev/bus/usb/001/005=UsbDevice[mName=/dev/bus/usb/001/005,mVendorId=1659,mProductId=8963,mClass=0,mSubclass=0,mProtocol=0,mManufacturerName=Prolific Technology Inc. ,mProductName=USB-Serial Controller D,mSerialNumber=null,mConfigurations=[
05-08 07:48:16.490 2101-2101/com.lxf.usbdemo I/System.out: UsbConfiguration[mId=1,mName=null,mAttributes=128,mMaxPower=50,mInterfaces=[
05-08 07:48:16.490 2101-2101/com.lxf.usbdemo I/System.out: UsbInterface[mId=0,mAlternateSetting=0,mName=null,mClass=255,mSubclass=0,mProtocol=0,mEndpoints=[
05-08 07:48:16.490 2101-2101/com.lxf.usbdemo I/System.out: UsbEndpoint[mAddress=129,mAttributes=3,mMaxPacketSize=10,mInterval=1]
05-08 07:48:16.490 2101-2101/com.lxf.usbdemo I/System.out: UsbEndpoint[mAddress=2,mAttributes=2,mMaxPacketSize=64,mInterval=0]
05-08 07:48:16.490 2101-2101/com.lxf.usbdemo I/System.out: UsbEndpoint[mAddress=131,mAttributes=2,mMaxPacketSize=64,mInterval=0]]]]
05-08 07:48:16.491 2101-2101/com.lxf.usbdemo I/System.out: /dev/bus/usb/001/003=UsbDevice[mName=/dev/bus/usb/001/003,mVendorId=1659,mProductId=8963,mClass=0,mSubclass=0,mProtocol=0,mManufacturerName=Prolific Technology Inc. ,mProductName=USB-Serial Controller D,mSerialNumber=null,mConfigurations=[
05-08 07:48:16.491 2101-2101/com.lxf.usbdemo I/System.out: UsbConfiguration[mId=1,mName=null,mAttributes=160,mMaxPower=50,mInterfaces=[
05-08 07:48:16.491 2101-2101/com.lxf.usbdemo I/System.out: UsbInterface[mId=0,mAlternateSetting=0,mName=null,mClass=255,mSubclass=0,mProtocol=0,mEndpoints=[
05-08 07:48:16.491 2101-2101/com.lxf.usbdemo I/System.out: UsbEndpoint[mAddress=129,mAttributes=3,mMaxPacketSize=10,mInterval=1]
05-08 07:48:16.491 2101-2101/com.lxf.usbdemo I/System.out: UsbEndpoint[mAddress=2,mAttributes=2,mMaxPacketSize=64,mInterval=0]
05-08 07:48:16.491 2101-2101/com.lxf.usbdemo I/System.out: UsbEndpoint[mAddress=131,mAttributes=2,mMaxPacketSize=64,mInterval=0]]]]
发现确实两个设备的信息都打印出来了,于是激动的去测试多个串口同时打开的问题,并没有发生crash,并且可以连接上,附一张图,绿色提示表示连接成功了。