android framework之旅(六)Usb多串口同时打开

这篇文章起源于之前遗留的一个问题(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的结构:


SystemServer.png

方法并不多,一看就懂,在启动服务的方法中搜索一下我们关心的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;

很明显,这就是我们开始在日志中看到的相关信息。再看下结构图:


UsbHostManager.png

说下比较重要的几个方法:

  • 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,并且可以连接上,附一张图,绿色提示表示连接成功了。


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

推荐阅读更多精彩内容