说实话这玩样儿的代码量真的很少,大家如果能耐得住性子啃一会儿也就能撸懂了。

在这之前研究USB线插拔的时候就知道了有这么个东西,当时也就看了看,但没做什么笔记。最近想用起来,却发现就只有个名字在记忆中了。

好了,又扯了这么多,来回到正题。

首先按照技术博客一贯的作风,得先有个入口点,这里我就先从怎么使用这个UEventObserver开始一步步分析。

首先这玩样儿是java代码,所以你就别想着c++什么用了。

这里我举例了USB线插拔来分析,代码地址如下:

frameworks/base/services/java/com/android/server/usb/UsbDeviceManager.java

   /*

    * Listens for uevent messages from the kernel to monitor the USB state

    */

   private final UEventObserver mUEventObserver = new UEventObserver() {

       @Override

       public void onUEvent(UEventObserver.UEvent event) {

           if (DEBUG) Slog.v(TAG, "USB UEVENT: " + event.toString());

           String state =event.get("USB_STATE");

           String accessory = event.get("ACCESSORY");

           if (state != null) {

                mHandler.updateState(state);

           } else if ("START".equals(accessory)) {

                if (DEBUG) Slog.d(TAG,"got accessory start");

                startAccessoryMode();

           }

       }

   };

这里我们可以看到使用前必须先初始化一个新的UEventObserver类来处理事件,即函数onUEvent,这是一个回调函数,之后我们会看到他是怎么被调用的。这里这个回调函数根据处理的事件消息来判断是否要更新USB的状态,继而触发通知栏显示。

初始化完UEventObserver之后,我们还需要使用这个类,接下来的代码做了这个事。

   private static final String USB_STATE_MATCH =

           "DEVPATH=/devices/virtual/android_usb/android0";

       public UsbHandler(Looper looper) {

           super(looper);

                // Watch for USB configurationchanges

               mUEventObserver.startObserving(USB_STATE_MATCH);

从中我们看出他直接调用了UEventObserver的startObserving函数,并且指定了参数,即一段字符串。

接下来我们看下startObserving到底做了什么事。

    public final void startObserving(String match) {

       if (match == null || match.isEmpty()) {

           throw new IllegalArgumentException("match substring must benon-empty");

       }

       final UEventThread t = getThread();

       t.addObserver(match, this);

    }

这个函数首先会判断是否字符串为空,然后调用getThread来获取UEventThread类,我们来看下getThread

   private static UEventThread getThread() {

       synchronized (UEventObserver.class) {

           if (sThread == null) {

                sThread = new UEventThread();

                sThread.start();

           }

           return sThread;

       }

    }

我们可以发现,这个函数告诉我们每个UEventObserver类中只有一个UEventThread类。从名字就能看出这货是个处理线程。因为我们这里是第一次进行获取,所以这里会新建一个UEventThread类,并调用start函数启动它。(有些安卓线程经验的同学,应该知道它最终会调用线程类中run函数,具体为神马的请百度吧,或者等我的博客更新)

   private static final class UEventThread extends Thread {

       @Override

       public void run() {

           nativeSetup();

           while (true) {

                String message =nativeWaitForNextEvent();

                if (message != null) {

                    if (DEBUG) {

                        Log.d(TAG, message);

                    }

                    sendEvent(message);

                }

           }

       }

我们可以从while循环看出他就是一个不停工作的线程。首先我们先看下nativeSetup做了什么,这货一般你看到就应该知道都是native层实现了,麻溜的就去找对应的cpp文件吧。

frameworks/base/core/jni/android_os_UEventObserver.cpp

static void nativeSetup(JNIEnv *env, jclassclazz) {

   if (!uevent_init()) {

       jniThrowException(env, "java/lang/RuntimeException",

                "Unable to open socket forUEventObserver");

    }

}

hardware/libhardware_legacy/uevent/uevent.c

int uevent_init()

{

   struct sockaddr_nl addr;

   int sz = 64*1024;

   int s;

   memset(&addr, 0, sizeof(addr));

   addr.nl_family = AF_NETLINK;

   addr.nl_pid = getpid();

   addr.nl_groups = 0xffffffff;

    s= socket(PF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT);

   if(s < 0)

       return 0;

   setsockopt(s, SOL_SOCKET, SO_RCVBUFFORCE, &sz, sizeof(sz));

   if(bind(s, (struct sockaddr *) &addr, sizeof(addr)) < 0) {

       close(s);

       return 0;

    }

   fd = s;

   return (fd > 0);

}

其实么这个函数调了很多层,最终只是调用了uevent实现里的初始化socket来接收UEVENT消息的,注意这里的接收蔟为NETLINK_KOBJECT_UEVENT,这货好像是全局接收各种uevent的,不只是单单某一种。(具体可以百度下socket知识,我也只是知道个皮毛。)

好了建完socket返回fd之后,我们回到UEventThread,他会继续调用native层的nativeWaitForNextEvent来获得一个消息。说实话,以前经常看别人的博客吐槽java层偷懒,时间长了确实感同身受,各种native处理啊。

static jstringnativeWaitForNextEvent(JNIEnv *env, jclass clazz) {

   char buffer[1024];

   for (;;) {

       int length = uevent_next_event(buffer, sizeof(buffer) - 1);

       if (length <= 0) {

           return NULL;

       }

       buffer[length] = '\0';

       ALOGV("Received uevent message: %s", buffer);

       if (isMatch(buffer, length)) {

           // Assume the message is ASCII.

           jchar message[length];

           for (int i = 0; i < length; i++) {

                message[i] = buffer[i];

           }

           return env->NewString(message, length);

       }

    }

}

我们先看下uevent_next_event,它和uevent_init函数一样,都是调到uevent里的。

int uevent_next_event(char* buffer, intbuffer_length)

{

   while (1) {

       struct pollfd fds;

       int nr;'

       nr = poll(&fds, 1, -1);

       if(nr > 0 && (fds.revents & POLLIN)) {

           int count = recv(fd, buffer, buffer_length, 0);

这个函数其实也很简单,我稍微省略了些内容,它的主要功能就两个,首先调用poll来等socket数据,有数据来的话接收到buffer里,然后返回。

好了,回到nativeWaitForNextEvent函数里。在接收到uevent消息后,我们会调用isMatch函数来检测是否匹配。

static bool isMatch(const char* buffer,size_t length) {

   AutoMutex _l(gMatchesMutex);

   for (size_t i = 0; i < gMatches.size(); i++) {

       const String8& match = gMatches.itemAt(i);

            if (strstr(field, match.string())) {

                ALOGV("Matched ueventmessage with pattern: %s", match.string());

                return true;

           }

这里就是判断是否这个返回的uevent消息存在于gMatches里,这里大家可能会问gMatches里什么都没有啊,不可能会存在。这时候我们在看下我们当初分析的时候,我们除了启动一个新线程,我们还做了什么。


       final UEventThread t = getThread();

       t.addObserver(match, this);

这里我们会调用UEventThread的addObserver函数,参数为传进来的字符串以及新建的UEventObserver,这里我们看下这个函数。


        public void addObserver(String match,UEventObserver observer) {

           synchronized (mKeysAndObservers) {

                mKeysAndObservers.add(match);

               mKeysAndObservers.add(observer);

                nativeAddMatch(match);

            }

       }

这个函数首先会把我们传进来的参数添加到mKeysAndObservers里,并且调用native函数nativeAddMatch添加匹配字符串。

static void nativeAddMatch(JNIEnv* env,jclass clazz, jstring matchStr) {

   ScopedUtfChars match(env, matchStr);

   AutoMutex _l(gMatchesMutex);

   gMatches.add(String8(match.c_str()));

}

好吧,这里你看到了gMatches了吧,没错他就是记录我们要查询的字符串的。

之后我们回到判断gMatches的isMatch函数,这时候我们已经有了一个字符串,如果这时候我们接收的uevent消息里包含了这段字符串就会返回true

还记得我们设置的字符串吗

DEVPATH=/devices/virtual/android_usb/android0

一般USB线插拔的时候都会有这段uevent消息来告诉用户所属设配路径。

假设这时候确实插入了USB线,即会返回true

那么我们就可以返回nativeWaitForNextEvent函数来看下接下来的处理。


       if (isMatch(buffer, length)) {

           // Assume the message is ASCII.

           jchar message[length];

           for (int i = 0; i < length; i++) {

                message[i] = buffer[i];

           }

           return env->NewString(message, length);

       }

之后他会进行保存并返回一个新的字符串,注意这里的字符串类型是jchar,即JAVA中的String类,这样java层才能进行进一步处理。

OK,我们也跟着它返回到UEventThread,这时候我们已经得到了一个uevent匹配的消息,然后调用sentEvent进行处理。

                String message = nativeWaitForNextEvent();

                if (message != null) {

                    if (DEBUG) {

                        Log.d(TAG, message);

                    }

                    sendEvent(message);

                }

       private void sendEvent(String message) {

           synchronized (mKeysAndObservers) {

                final int N =mKeysAndObservers.size();

                for (int i = 0; i < N; i +=2) {

                    final String key =(String)mKeysAndObservers.get(i);

                    if (message.contains(key)){

                        final UEventObserverobserver =

                               (UEventObserver)mKeysAndObservers.get(i + 1);

                       mTempObserversToSignal.add(observer);

                    }

                }

           }

           if (!mTempObserversToSignal.isEmpty()) {

                final UEvent event = newUEvent(message);

                final int N =mTempObserversToSignal.size();

                for (int i = 0; i < N; i++){

                    final UEventObserverobserver = mTempObserversToSignal.get(i);

                    observer.onUEvent(event);

                }

                mTempObserversToSignal.clear();

           }

       }

这个函数有点长,但是做的事情一目了览,首先它会根据返回的消息在mKeysAndObservers查找UEventObserver,如果有匹配的话就返回下一个类,还记得我们在之前添加的两个参数吗,即查询字符串和UEventObserver类。这里他就会找到UEventObserver,并且添加到mTempObserversToSignal里。当所有匹配的UEventObserver被添加完后,循环调用每个类的onUEvent回调函数。这个函数我们已经在一开始新建UEventObserver的时候实现过了。


   /*

    * Listens for uevent messages from the kernel to monitor the USB state

    */

   private final UEventObserver mUEventObserver = new UEventObserver() {

       @Override

       public void onUEvent(UEventObserver.UEvent event) {

           if (DEBUG) Slog.v(TAG, "USB UEVENT: " + event.toString());

            String state =event.get("USB_STATE");

           if (state != null) {

                mHandler.updateState(state);

这样我们就把UEventObserver说清楚了,大家觉得是不是很简单呢,但是说实话这货确实是java层检测硬件变化的好帮手,推荐使用。

最后做个总结,我画了张简单的流程图,大家凑合着看就行了。

USB线插拔检测使用UEventObserver检测uevent事件的分析的更多相关文章

  1. Android中监控USB的插拔

    一.需求 在Android应该开发过程中,用到了USB通讯,需要应用监控USB设备的插入和拔出,从而刷新USB设备列表. 二.实现 在使用时,需要register和unregister. 通过UsbD ...

  2. USB 设备插拔事件处理

            Windows 系统下,设备连接至电脑或从电脑移除,系统会广播一条 WM_DEVICECHANGE 消息到所有应用程序,在程序的消息处理函数中可以对事件进行相应. 1: class C ...

  3. android6.0 外部存储设备插拔广播以及获取路径(U盘)【转】

    本文转载自:https://blog.csdn.net/zhouchengxi/article/details/53982222 这里我将U盘作为例子来说明解析. android4.1版本时U盘插拔时 ...

  4. C#中USB转串口的拔插捕获

    // usb消息定义 public const int WM_DEVICE_CHANGE = 0x219; public const int DBT_DEVICEARRIVAL = 0x8000; p ...

  5. QTC++监控USB插拔

    #if defined(Q_OS_WIN) #include <qt_windows.h> #include <QtCore/qglobal.h> #include <d ...

  6. 2018-8-10-WPF-判断USB插拔

    title author date CreateTime categories WPF 判断USB插拔 lindexi 2018-08-10 19:16:53 +0800 2018-8-5 13:0: ...

  7. C#.NET U盘插拔监控

    [1]涉及的知识点 1) windows消息处理函数 ? 1 protected override void WndProc(ref Message m) 捕获Message的系统硬件改变发出的系统消 ...

  8. ARM上的linux如何实现无线网卡的冷插拔和热插拔

    ARM上的linux如何实现无线网卡的冷插拔和热插拔 fulinux 凌云实验室 1. 冷插拔 如果在系统上电之前就将RT2070/RT3070芯片的无线网卡(以下简称wlan)插上,即冷插拔.我们通 ...

  9. USB线上/串口/I2C引脚串联电阻的作用

    对引脚的保护. 第一是阻抗匹配.因为信号源的阻抗很低,跟信号线之间阻抗不匹配,串上一个电阻后,可改善匹配情况,以减少反射,避免振荡等. 第二是可以减少信号边沿的陡峭程度,从而减少高频噪声以及过冲等.因 ...

随机推荐

  1. 使用ajax与jqplot的小体会

    在使用ajax与jqplot时遇到了传值的问题!一开始都不知值是怎么传过去的,只找到了例子是以<div id="data">原始数据</div>这样子来接收 ...

  2. [转]SPFA算法的玄学方法

    最近想到了许多优化spfa的方法,这里想写个日报与大家探讨下 前置知识:spfa(不带任何优化) 由于使用较多 STLSTL ,本文中所有代码的评测均开启 O_2O2​ 优化 对一些数组的定义: di ...

  3. Redis学习笔记(1)- CentOS 6.4 安装Redis

    Redis学习笔记(1)- CentOS 6.4 安装Redis 2013.10.13     学习环境 vm 10.1 + 默认.新装的干净 CentOS 6.4  64BIT系统     准备 1 ...

  4. luogu P2757 [国家集训队]等差子序列

    题目链接 luogu P2757 [国家集训队]等差子序列 题解 线段树好题 我选择暴力 代码 // luogu-judger-enable-o2 #include<cstdio> inl ...

  5. [USACO07JAN]Balanced Lineup

    OJ题号:洛谷2880 思路1: 线段树维护区间最大最小值. #include<cstdio> #include<cctype> #include<utility> ...

  6. tomcat 启动 关闭 重启脚本

    启动 #!/bin/bash # Author:wanglan # Mail:@qq.com # Fuction:Tomcat Start/stop/restart script # Version: ...

  7. linux和mac

    整理下来的linux常用指令 mount [-t 文件系统] 设备文件名 挂载点挂载命令,一般用于在挂载ISO,或者其他比如U盘等设备时使用,[-t iso9660]为固定格式,可写可不写,非必写项. ...

  8. MikroTik RouterOS安装后初始化配置(PPPOE拨号上网)

    1.修改登入密码 路由器默认登入账号为admin,密码为空,强烈建议修改登入密码保证安全: 2.修改接口名称 选择Interface,切换到Ethernet标签,找到状态是R(run)的两个端口. 给 ...

  9. 【DevOps】谁说大象不能跳舞?

    作者:范军 (Frank Fan) 新浪微博:@frankfan7   微信:frankfan7 很多企业,尤其是大企业在产品开发和运维上存在着一些普遍问题,比如开发周期长.人员合作程度不高.开发和运 ...

  10. jQuery $('div>ul') $('div ul'

    $('div>ul')是<div>的直接后代里找<ul>: 而$('div ul')是在<div>的所有后代里找<ul>.