Android监听UEvent之UEventObserver分析

news2024/11/29 12:41:11

(1)背景概述

众所周知,在安卓系统中有状态栏,在插入外设的时候,会在顶部状态栏显示小图标。
比如,camera设备,耳机设备,U盘,以及电池等等。这些都需要在状态栏动态显示。

在这里插入图片描述
从上面这张图片可以看出这些设备都有自己的服务一直在跑,并且都是继承了UEventObserver.java这个类去获取kernel的Event事件。下面将着重分析UEventObserver是如何去监听kernel的Event事件。

(2)源码分析

(A)Java层源码分析

//frameworks/base/core/java/android/os/UEventObserver.java

/*
UEventObserver是一个从内核接收UEvents的抽象类。

子类UEventObserver实现onUEvent(UEvent事件),调用startObserving()与匹配字符串匹配,
然后UEvent线程将调用onUEvent()方法,调用stopObserving()停止接收UE事件。

每个进程只有一个UEvent线程,即使该进程具有多个UEventObserver子类实例。
UEvent线程在以下情况下启动:
在该过程中首次调用startObserving()。一旦已启动UEvent线程不会停止(尽管它可以停止通知UEventObserver通过stopObserving())

//hide
*/

public abstract class UEventObserver {

    private static UEventThread sThread;

    private static native void nativeSetup();
    private static native String nativeWaitForNextEvent();
    private static native void nativeAddMatch(String match);
    private static native void nativeRemoveMatch(String match);

	private static UEventThread getThread() {
        synchronized (UEventObserver.class) {
            if (sThread == null) {
                sThread = new UEventThread();
                sThread.start();
            }
            return sThread;
        }
    }

    private static UEventThread peekThread() {
        synchronized (UEventObserver.class) {
            return sThread;
        }
    }

	//注释监听Observer
	public final void startObserving(String match) {
        if (match == null || match.isEmpty()) {
            throw new IllegalArgumentException("match substring must be non-empty");
        }

        final UEventThread t = getThread();
        t.addObserver(match, this);
    }

	//停止监听Observer
    public final void stopObserving() {
        final UEventThread t = peekThread();
        if (t != null) {
            t.removeObserver(this);
        }
    }

    public abstract void onUEvent(UEvent event);

接下来看一下其使用的UEventThread。

private static final class UEventThread extends Thread {
        /** Many to many mapping of string match to observer.
         *  Multimap would be better, but not available in android, so use
         *  an ArrayList where even elements are the String match and odd
         *  elements the corresponding UEventObserver observer */
        private final ArrayList<Object> mKeysAndObservers = new ArrayList<Object>();

        private final ArrayList<UEventObserver> mTempObserversToSignal =
                new ArrayList<UEventObserver>();

        public UEventThread() {
            super("UEventObserver");
        }

        @Override
        public void run() {
            nativeSetup();	//jni调用nativeSetup

            while (true) {
                String message = nativeWaitForNextEvent();	//jni调用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 UEventObserver observer =
                                (UEventObserver)mKeysAndObservers.get(i + 1);
                        mTempObserversToSignal.add(observer);
                    }
                }
            }

            if (!mTempObserversToSignal.isEmpty()) {
                final UEvent event = new UEvent(message);
                final int N = mTempObserversToSignal.size();
                for (int i = 0; i < N; i++) {
                    final UEventObserver observer = mTempObserversToSignal.get(i);
                    observer.onUEvent(event);
                }
                mTempObserversToSignal.clear();
            }
        }

        public void addObserver(String match, UEventObserver observer) {
            synchronized (mKeysAndObservers) {
                mKeysAndObservers.add(match);
                mKeysAndObservers.add(observer);
                nativeAddMatch(match);
            }
        }

        /** Removes every key/value pair where value=observer from mObservers */
        public void removeObserver(UEventObserver observer) {
            synchronized (mKeysAndObservers) {
                for (int i = 0; i < mKeysAndObservers.size(); ) {
                    if (mKeysAndObservers.get(i + 1) == observer) {
                        mKeysAndObservers.remove(i + 1);
                        final String match = (String)mKeysAndObservers.remove(i);
                        nativeRemoveMatch(match);
                    } else {
                        i += 2;
                    }
                }
            }
        }
    }

当第一次启动这个线程的时候,会调用nativeSetup()方法做初始化,可以看出这个函数是native层来实现的。初始化完之后,进入一个while的死循环,不停的调用native层的nativeWaitForNextEvent()函数来获取Event事件,然后将Event事件转换成message,再通过sendEvent()将message事件传递给外设对应的Observer。

(B)Kernel层源码分析

//frameworks/base/core/jni/android_os_UEventObserver.cpp

static void nativeSetup(JNIEnv *env, jclass clazz) {
    if (!uevent_init()) {	//kernel当中的uevent_init
        jniThrowException(env, "java/lang/RuntimeException",
                "Unable to open socket for UEventObserver");
    }
}

static jstring nativeWaitForNextEvent(JNIEnv *env, jclass clazz) {
    char buffer[1024];

    for (;;) {
        int length = uevent_next_event(buffer, sizeof(buffer) - 1);	//kernel当中的uevent_next_event
        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);
        }
    }
}

进而调用内核Event相关函数uevent_init()和uevent_next_event()。

//hardware/libhardware_legacy/uevent.c

/* Returns 0 on failure, 1 on success */
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);
}


int uevent_next_event(char* buffer, int buffer_length)
{
    while (1) {
        struct pollfd fds;
        int nr;
    
        fds.fd = fd;
        fds.events = POLLIN;
        fds.revents = 0;
        nr = poll(&fds, 1, -1);
     
        if(nr > 0 && (fds.revents & POLLIN)) {
            int count = recv(fd, buffer, buffer_length, 0);
            if (count > 0) {
                struct uevent_handler *h;
                pthread_mutex_lock(&uevent_handler_list_lock);
                LIST_FOREACH(h, &uevent_handler_list, list)
                    h->handler(h->handler_data, buffer, buffer_length);
                pthread_mutex_unlock(&uevent_handler_list_lock);

                return count;
            } 
        }
    }
    
    // won't get here
    return 0;
}

uevent_init:初始化socket,用来接收来自内核的event事件。
uevent_next_event:不停的调用poll来等待socket的数据,并将数据存放在buffer中,然后返回。

最后附上流程时序图:
在这里插入图片描述
(3)简单测试用例

(A)UsbDeviceManager

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

private static final String USB_STATE_MATCH = "DEVPATH=/devices/virtual/android_usb/android0";
private static final String ACCESSORY_START_MATCH = "DEVPATH=/devices/virtual/misc/usb_accessory";

private final UEventObserver mUEventObserver;

// Watch for USB configuration changes
mUEventObserver = new UsbUEventObserver();
mUEventObserver.startObserving(USB_STATE_MATCH);
mUEventObserver.startObserving(ACCESSORY_START_MATCH);

/*
     * Listens for uevent messages from the kernel to monitor the USB state
     */
    private final class UsbUEventObserver extends UEventObserver {
        @Override
        public void onUEvent(UEventObserver.UEvent event) {
            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 ("GETPROTOCOL".equals(accessory)) {
                //...
            } else if ("SENDSTRING".equals(accessory)) {
                //...
            } else if ("START".equals(accessory)) {
                //...
            }
        }
    }

(B)监听摄像头的打开和关闭

在上层服务中监听摄像头的打开和关闭,并作相应处理。需要内核摄像头驱动中也要发出event事件才行,所以改动分为内核和上层两部分。

//kernel drv

static atomic_t g_CamHWOpend; 	//camera是否打开成功的标志,其它地方赋值
struct device* sensor_device = NULL;
static void set_camera_status()
{
  char *envp[2];
  int ret = atomic_read(&g_CamHWOpend)? 1 : 0;
	if(ret)
		envp[0] = "STATUS=OPEN";
	else
		envp[0] = "STATUS=CLOSE";
	envp[1] = NULL;
	kobject_uevent_env(&sensor_device->kobj, KOBJ_CHANGE, envp);	//将envp通过kobject上报
	return;
}

//上层监听

	m_CameraStatusObserver.startObserving("DEVPATH=/devices/virtual/sensordrv/kd_camera_hw");

	private UEventObserver m_CameraStatusObserver = new UEventObserver(){
		public void onUEvent(UEvent event){
			String status = event.get("STATUS");    //没有取特定长度字符串,直接取=前面的子串
			if( "OPEN".equals(status)){
				Log.i(TAG,"camera app open");			
			}
			else if ("CLOSE".equals(status)){
				Log.i(TAG,"camera app close");
			}
		}
	};

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/85173.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

wy的leetcode刷题记录_Day58

wy的leetcode刷题记录_Day58 声明 本文章的所有题目信息都来源于leetcode 如有侵权请联系我删掉! 时间&#xff1a;2022-12-2 前言 力扣每日一题简单模拟左右抵消和二叉平衡搜索树 1769. 移动所有球到每个盒子所需的最小操作数和108. 将有序数组转换为二叉搜索树 目录wy的l…

无线路由器首次配置、修改WiFi名称和密码—— Cisco实验/家里实验

一、Csico实验 192.168.0.1、192.168.1.0和192.168.1.1是路由器常用的默认IP 1. 在PC打开浏览器&#xff08;PC用网线直连无线路由器&#xff09;&#xff0c; 输入无线路由器在局域网内的静态IP&#xff1a;192.168.0.1 2. 输入管理者的账号和密码&#xff0c;默认都是admin…

搜索与图论- Dijkstra 算法

文章目录一、Dijkstra 算法1. 简介2. 基本思想3. 朴素 Dijkstra 算法&#xff08;重点&#xff09;3.1 朴素 Dijkstra 算法实现步骤3.2 朴素 Dijkstra 算法伪代码4. 朴素 Dijkstra 算法具体实现详见例题 Dijkstra 求最短路 I 。5. 堆优化朴素 Dijkstra 算法6. 堆优化 Dijkstra …

cookie、sessionStorage和localStorage的区别(二)

cookie、sessionStorage和localStorage的区别&#xff08;一&#xff09;详细精炼知识调用前言引入核心干货webstorage本地存储cookiesessionStoragelocalStorage知识调用 文章中可能用到的知识点前端学习&#xff1a;浏览器缓存方式有哪些&#xff08; cookie localstorage s…

rocketmq源码学习-broker启动

前言 这篇笔记记录broker启动的源码学习 broker主要完成一下几件事情&#xff1a; 1.接收producer的发送请求&#xff0c;并对消息进行持久化、同步其他节点 2.接收consumer读取消息星球 3.定时向nameSrv注册心跳信息&#xff0c;保持连接 在启动的时候&#xff0c;也是分了…

Ant Design 6.0.0 实践集合

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 使用的6.0.0 beta版本 文章目录 前言一、pandas是什么&#xff1f;二、使用步骤 1.引入库2.读入数据总结前言 Ant Design 简称为 Antd antd 为 Web 应用提供了丰富的基础 U…

操作指南|通过JumpServer实现Kubernetes运维安全审计

本文重点介绍如何通过JumpServer实现Kubernetes的运维安全审计。此前&#xff0c;我们专门介绍过在Kubernetes集群上快速部署JumpServer的方法步骤&#xff0c;可参见《操作指南&#xff5c;在Kubernetes集群上快速部署JumpServer开源堡垒机》一文。 一、Kubernetes运维审计现…

ABP Vnext 学习03-授权中心微信小程序登录

前言 小程序开发的 前置条件 1 需要服务端是https 和域名 Ip 是不可以的 2 需要申请appid 小程序的官方流程图 个人理解 对于上面的流程图 步骤一 客户端 小程序调用wx.login 方法 获取用户的code 这个code 是限时的五分钟就会过期 拿到code 就可以向服务端发起登录请求了 …

vue3中ref的作用及ref和reactive之间的转化

ref的作用&#xff1a; &#xff08;1&#xff09;第一个作用&#xff1a;和vue一样&#xff1a; 绑在dom节点上拿到的是dom节点&#xff1b;绑在组件上拿到的是组件对象&#xff1b; 定义方式&#xff1a; <template><div><input type"text" ref&…

java运行数据区域分布

Java在运行程序过程中&#xff0c;会将自己的内存划分为若干个不同的数据数据区域&#xff0c;这些若干个区域&#xff0c;每个区域都有自己的用途&#xff0c;具体看下图 java是面向对象的语言&#xff0c;那么虚拟机中的数据&#xff08;对象&#xff09;是怎么被创建出来的呢…

加密 笔记

文章目录简单异或加密对称加密DES加密AES加密1.简单的加密解密逻辑2.填充方式**noPadding**3.加密模式1、**ECB模式&#xff08;默认&#xff09;**2、**CBC模式**3、CFB模式4、OFB模式5、CTR模式代码案例ECB加密和CBC加密测试非对称加密RSA加密AES和RSA混合加密哈希散列算法什…

[附源码]Python计算机毕业设计动物保护资讯推荐网站Django(程序+LW)

该项目含有源码、文档、程序、数据库、配套开发软件、软件安装教程 项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等…

RabbitMQ[3]-RabbitMQ如何保证消息的可靠性投递与消费?

上篇文章&#xff1a;RabbitMQ的核心概念有哪些&#xff1f;它们的职责是什么&#xff1f;中我们详细介绍了RabbitMQ的工作模式&#xff0c;根据它的工作模式&#xff0c;一条消息从生产者发出&#xff0c;到消费者消费&#xff0c;需要经历以下4个步骤&#xff1a; 生产者将消…

Java笔记——String类各种方法的使用总结(附带实例)

String类的获取方法 String类实现获取功能的方法有 int length() —— 获取字符串长度 char charAt(int index) —— 获取指定索引处的字符值 int indexOf(int ch) —— 获取指定字符第一次出现的索引位置 int indexOf(String str) —— 获取指定字符串第一次出现的索引位…

ArrayDeque源码解析

ArrayDeque源码解析 问题 &#xff08;1&#xff09;什么是双端队列&#xff1f; &#xff08;2&#xff09;ArrayDeque 是怎么实现双端队列的&#xff1f; &#xff08;3&#xff09;ArrayDeque 是线程安全的吗&#xff1f; &#xff08;4&#xff09;ArrayDeque 是有界的…

【正点原子FPGA连载】 第三十五章双目OV5640摄像头HDMI显示实验 摘自【正点原子】DFZU2EG/4EV MPSoC 之FPGA开发指南V1.0

1&#xff09;实验平台&#xff1a;正点原子MPSoC开发板 2&#xff09;平台购买地址&#xff1a;https://detail.tmall.com/item.htm?id692450874670 3&#xff09;全套实验源码手册视频下载地址&#xff1a; http://www.openedv.com/thread-340252-1-1.html 第三十五章双目O…

基于jsp+mysql+ssm二手书交易管理系统-计算机毕业设计

项目介绍 这样一个二手书交易网站为用户提供了一个可以在网上买卖图书的平台&#xff0c;用户可以通过二手书交易管理系统进行注册或登录操作&#xff0c;登录成功后可以查看自己已发布的售书信息或者求购信息。同时&#xff0c;用户可以浏览其他用户发布的售书信息和求购信息…

基于51单片机的多层电梯(1-16层)运行系统仿真设计_层数可改

基于51单片机的多层电梯(1-16层)运行系统仿真设计_层数可改 仿真图proteus 8.9 程序编译器&#xff1a;keil 4/5 编程语言&#xff1a;C语言 设计编号&#xff1a;S0027 视频演示 基于51单片机的多层电梯(1-16层)运行系统仿真设计演示视频主要功能&#xff1a; 结合实际情…

[附源码]Python计算机毕业设计SSM基于web的学生社团管理系统(程序+LW)

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

[附源码]Python计算机毕业设计SSM基于的二手房交易系统(程序+LW)

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 Ma…