【android 9】【input】【7.发送按键事件1——InputReader线程】

系列文章目录

本人系列文章-CSDN博客


目录

系列文章目录

1.简介

1.1发送流程介绍

[1.2 时序图](#1.2 时序图)

2.普通按键消息发送部分源码分析

[2.1 设备的监听](#2.1 设备的监听)

[2.2 inputreader线程阻塞等待事件发生](#2.2 inputreader线程阻塞等待事件发生)

[2.3 按键事件的产生](#2.3 按键事件的产生)

[2.4 EventHub::getEvents](#2.4 EventHub::getEvents)

[2.5 InputReader::loopOnce](#2.5 InputReader::loopOnce)

[2.6 processEventsLocked](#2.6 processEventsLocked)

[2.7 processEventsForDeviceLocked](#2.7 processEventsForDeviceLocked)

[2.8 InputDevice::process](#2.8 InputDevice::process)

[2.9 KeyboardInputMapper::process](#2.9 KeyboardInputMapper::process)

[2.10 KeyboardInputMapper::process(扫描消息)](#2.10 KeyboardInputMapper::process(扫描消息))

[2.11 KeyboardInputMapper::process(按键按下)](#2.11 KeyboardInputMapper::process(按键按下))

[2.12 KeyboardInputMapper::processKey](#2.12 KeyboardInputMapper::processKey)

[2.13 QueuedInputListener::notifyKey](#2.13 QueuedInputListener::notifyKey)

[2.14 KeyboardInputMapper::process(同步消息)](#2.14 KeyboardInputMapper::process(同步消息))

[2.15 KeyboardInputMapper::process(扫描事件)](#2.15 KeyboardInputMapper::process(扫描事件))

[2.16 KeyboardInputMapper::process(按键抬起事件)](#2.16 KeyboardInputMapper::process(按键抬起事件))

[2.17 KeyboardInputMapper::process(同步消息)](#2.17 KeyboardInputMapper::process(同步消息))

[2.18 QueuedInputListener::flush](#2.18 QueuedInputListener::flush)

[2.19 NotifyKeyArgs::notify](#2.19 NotifyKeyArgs::notify)

[2.20 InputDispatcher::notifyKey](#2.20 InputDispatcher::notifyKey)

[2.21 NativeInputManager::interceptKeyBeforeQueueing](#2.21 NativeInputManager::interceptKeyBeforeQueueing)

[2.22 IMS中的interceptKeyBeforeQueueing](#2.22 IMS中的interceptKeyBeforeQueueing)

[2.23 WMS中的interceptKeyBeforeQueueing](#2.23 WMS中的interceptKeyBeforeQueueing)

[2.24 PhoneWindowManager中的interceptKeyBeforeQueueing](#2.24 PhoneWindowManager中的interceptKeyBeforeQueueing)

[2.25 NativeInputManager::handleInterceptActions](#2.25 NativeInputManager::handleInterceptActions)

[2.26 InputDispatcher::enqueueInboundEventLocked](#2.26 InputDispatcher::enqueueInboundEventLocked)

[2.27 NotifyKeyArgs::notify(抬起)](#2.27 NotifyKeyArgs::notify(抬起))


1.简介

从之前的篇幅我们知道了,事件分为设备增删事件和原始输入事件,而原始输入事件主要有两种,一种是key按键事件的派发,一种是触摸事件的派发。本篇区别于其他作者,会不放过任何一个函数,进行系统完整的分析。此篇主要分析一个按键事件从驱动上报到应用监听获取的全流程。

1.1发送流程介绍

1.input启动后,完成对设备的加载后,当无事件产生时,inputreader线程便阻塞在epoll_wait等待有消息的产生。

2.当物理按键按下和抬起的时候,即按键事件便产生了。内核会上报按下和抬起的消息给到input系统。

3.inputreader线程中的不再阻塞,然后从notifyfd中读取原始的input_event输入事件。

4.对原始的input_event事件,进行加工成RawEvent事件,然后调用对应的KeyboardInputMapper进行处理。

5.KeyboardInputMapper会将RawEvent事件再加工成NotifyKeyArgs事件,然后插入到mArgsQueue队列中。

6.然后会调用InputDispatcher的notifyKey,将NotifyKeyArgs事件再加工成KeyEntry事件,放入mInboundqueue队列队尾,然后唤醒InputDispatcher分发线程,进行按键事件的分发。

1.2 时序图

为了完整的画出所有时序,较为模糊,读者可保存照片到本地放大观看。


2.普通按键消息发送部分源码分析

为了便于读者清晰的了解发送流程,此章节会删除不执行的代码,便于理解。

2.1 设备的监听

要想知道某设备的事件发生,首先,便是要监听设备。在启动篇中,我们知道会注册设备到epoll中完成对设备的监听。

下面是前文监听设备节点的代码

cpp 复制代码
status_t EventHub::registerDeviceForEpollLocked(Device* device) {
    struct epoll_event eventItem;
    memset(&eventItem, 0, sizeof(eventItem));
    eventItem.events = EPOLLIN;//监听的文件描述符是否有可读事件

    eventItem.data.u32 = device->id;//事件触发时会返回eventItem.data数据
    if (epoll_ctl(mEpollFd, EPOLL_CTL_ADD, device->fd, &eventItem)) {//监听device->fd对应的设备是否有可读事件。
	//如果有事件可读,则返回eventItem.data
        
    }
    return OK;
}

2.2 inputreader线程阻塞等待事件发生

cpp 复制代码
void InputReader::loopOnce() {
    int32_t oldGeneration;
    int32_t timeoutMillis;
    bool inputDevicesChanged = false;
    Vector<InputDeviceInfo> inputDevices;
    { 
 
        oldGeneration = mGeneration;//新的值
        timeoutMillis = -1;
 
    size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);//通过EventHub的getEvents函数获取事件,
	//并存放在mEventBuffer中。
	//参数分析:timeoutMillis=-1,代表等待超时的时间,感觉如果数字为0代表立即执行
	//mEventBuffer是一个存放从eventhub中读取的rawevent结构体类型的数组,源代码是:RawEvent mEventBuffer[EVENT_BUFFER_SIZE];
	//EVENT_BUFFER_SIZE值是256,代表最大可以读取256个原始事件,查看833行-840行,RawEvent结构体如下
.......

即:此时阻塞在getEvents函数的epoll_wait中。

cpp 复制代码
//第一次for循环,会阻塞在epoll_wait中,等待设备增加事件的到来。
 
size_t EventHub::getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSize) {
    ALOG_ASSERT(bufferSize >= 1);
 
 
 
    struct input_event readBuffer[bufferSize];//bufferSize值为传入的256
 
    RawEvent* event = buffer;//event指针指向传入的buffer首地址,每存入一个事件,event指针向后移动一个元素
    size_t capacity = bufferSize;//capacity表示buffer中剩余端元素数量,capacity为0,表示buffer已满
    bool awoken = false;
    for (;;) {
        nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);//获取当前时间
 
 
        mPendingEventIndex = 0;//此时mPendingEventItems数组中未处理的事件已经处理完了,将mPendingEventIndex设置为0
 
 
        int pollResult = epoll_wait(mEpollFd, mPendingEventItems, EPOLL_MAX_EVENTS, timeoutMillis);//此时读线程会阻塞在此处,等待有消息的事件发生
		
	......

2.3 按键事件的产生

首先从本系列的input第一篇可知,一次按键按下和抬起的过程中,会产生六条数据包,一个按键按下到抬起,发了6包数据,依次是:

扫描事件、KEY事件、同步事件、扫描事件、KEY事件、同步事件。

比如按下A按键,然后抬起。则内核上报的数据为:

cpp 复制代码
按下:
/dev/input/event4: EV_MSC MSC_SCAN 00090001//扫描事件,EV_MSC代表是杂项事件,MSC_SCAN代表是杂项事件中的扫描事件,Value代表值是00090001,说明扫描到BTN_GAMEPAD这个按键有变化
/dev/input/event4: EV_KEY BTN_GAMEPAD 00000001 //KEY事件,EV_KEY代表这是按键类型事件,BTN_GAMEPAD代表手柄注册中的按键是BTN_GAMEPAD,00000001表示按下
/dev/input/event4: EV_SYN SYN_REPORT 00000000 //同步事件,标识一个独立的事件的发生,它与另一个同步包夹中间的一组的打印就是一个事件的发生
抬起:
/dev/input/event4: EV_MSC MSC_SCAN 00090001 //扫描事件
/dev/input/event4: EV_KEY BTN_GAMEPAD 00000000 //KEY事件,00000000代表抬起
/dev/input/event4: EV_SYN SYN_REPORT 00000000 //同步事件

然后便是按键事件的处理

2.4 EventHub::getEvents

主要作用是:

1.epoll_wait不再阻塞,从发生事件的fd中读取按键原始事件并加工成RawEvent类型的消息。

回到2.2 inputreader线程阻塞的地方,因为对设备的fd进行了epoll监听其是否有可读事件,所以当有事件时,第一次for循环中epoll_wait不再阻塞,向下执行。

cpp 复制代码
//第一次死循环,当按键按下产生按键按下的消息。epoll_wait不再阻塞,向下执行。
size_t EventHub::getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSize) {
    

    struct input_event readBuffer[bufferSize];//bufferSize值为传入的256

    RawEvent* event = buffer;//event指针指向传入的buffer首地址,每存入一个事件,event指针向后移动一个元素
    size_t capacity = bufferSize;//capacity表示buffer中剩余端元素数量,capacity为0,表示buffer已满
    bool awoken = false;
    for (;;) {
        nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);//获取当前时间

        mPendingEventIndex = 0;//此时mPendingEventItems数组中未处理的事件已经处理完了,将mPendingEventIndex设置为0

        int pollResult = epoll_wait(mEpollFd, mPendingEventItems, EPOLL_MAX_EVENTS, timeoutMillis);//此时读线程会阻塞在此处,等待有消息的事件发生
		
		

        ///if (pollResult < 0) 
		{
        } else {
            // 发生了某些事件
            mPendingEventCount = size_t(pollResult);
        }
    }

    /// 此时并没有返回而是开启了下一次死循环。
    ///return event - buffer;
}

第二次for循环,会从设备的fd中读取原始事件,并将消息加工成RawEvent类型。

cpp 复制代码
//第二次for死循环
//假设只有一个设备,一个按下,一个抬起,相当于六条消息,
//依次为扫描事件、key按下事件、同步事件、扫描事件、key抬起事件、同步事件。
size_t EventHub::getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSize) {
	size_t capacity = bufferSize;//capacity表示buffer中剩余端元素数量,capacity为0,表示buffer已满
	RawEvent* event = buffer;
    for (;;) {
        nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);//获取当前时间



        //如果存在未处理的事件,则从mPendingEventItems数组中取出事件
        bool deviceChanged = false;
        while (mPendingEventIndex < mPendingEventCount) {//此时会执行,此时mPendingEventIndex=0,mPendingEventCount为设备有变化的数量
		//此时相当于mPendingEventCount =1,
            const struct epoll_event& eventItem = mPendingEventItems[mPendingEventIndex++];



            ssize_t deviceIndex = mDevices.indexOfKey(eventItem.data.u32);//获取事件的data.u32字段,此字段存储的是device-id,
			//然后判断此事件发生的设备是否在mDevices中能查找到对应的deviceid
            if (deviceIndex < 0) {//在已经打开的设备中,没找到此事件对应的设备
                ALOGW("Received unexpected epoll event 0x%08x for unknown device id %d.",
                        eventItem.events, eventItem.data.u32);
                continue;
            }

            Device* device = mDevices.valueAt(deviceIndex);//通过deviceid,找到对应的device
            if (eventItem.events & EPOLLIN) {//现在处理的是input设备产生的原始事件
                int32_t readSize = read(device->fd, readBuffer,sizeof(struct input_event) * capacity);//从input设备中,读取event事件,存入readBuffer数组中,从input设备中,读取event事件,存入readBuffer数组中,
                if (readSize == 0 || (readSize < 0 && errno == ENODEV)) {//如果满足此条件,代表在通知INotify之前,设备已被移除。
                }
				else
				{
                    int32_t deviceId = device->id == mBuiltInKeyboardId ? 0 : device->id;//判断是否是内置键盘,给devcie-id赋值

                    size_t count = size_t(readSize) / sizeof(struct input_event);//计算事件的数量,此时等于6
                    for (size_t i = 0; i < count; i++) {
                        struct input_event& iev = readBuffer[i];

						
						//此处是将input_event信息, 封装成RawEvent
                        event->deviceId = deviceId;
                        event->type = iev.type;
                        event->code = iev.code;
                        event->value = iev.value;
                        event += 1;
                        capacity -= 1;
                    }
                    if (capacity == 0) {//buffer数组已满,重置mPendingEventIndex,
					//注意,此时有三个数组,分别是
					//1.存储epoll_event结构体的mPendingEventItems数组,epoll_event& eventItem = mPendingEventItems[mPendingEventIndex++];
					//2.存储input_event结构体的readbuffer数组,input_event readBuffer[bufferSize],
					//3.存储RawEvent类型的buffer数组,RawEvent* event = buffer
					//流程如下:
					//当设备有可读事件时,会返回一个epoll_event结构体,存储mPendingEventItems数组中,然后从mPendingEventItems数组中,
					//取出这个epoll_event结构体,函数进行判断epoll_event结构体的eventItem.events = EPOLLIN,此字段代表监听的fd有可读事件,
					//然后,通过read(fd,readbuffer)函数,去读取此设备中的多个input_event结构体类型的事件,
					//存储在readbuffer数组中,这是第二个数组,然后循环取出input_event类型的数据,
					//将其进行加工成rawinput类型,存储在存储RawEvent类型的buffer数组中。
					
					//所以此处的mPendingEventIndex含义减去一的含义是:
					//比如:buffer数组的容量是200,一个eventItem相当于对应一个设备,所以一个eventItem会对应多个事件。
					//第一个eventItem对应的按键事件有150个,第二个eventItem对应的事件有200个,
					//当拿到第二个eventItem时,因为eventItem = mPendingEventItems[mPendingEventIndex++];,所以mPendingEventIndex值等于3,
					//然后去读取数据,并将其加工到buffer数组中,此时buffer在加工到50个的时候已经满了。但是第二个eventItem对应的剩余事件还没加工完
					//所以此处重新吧mPendingEventIndex设置为第二个,等待下一次getevent中调用,继续处理第二个eventitem对应的所以事件。
                        mPendingEventIndex -= 1;
                        break;//退出循环,此时退出的是eventItem这个循环。
                    }
                }
            }
        }


        // 当event的指针不再指向buffer的首地址时,代表里面有数据,或者被唤醒时,立即退出循环
        if (event != buffer || awoken) {
            break;//退出for死循环
        }
    }

    // 全部完成后,返回我们读取的事件数。
    return event - buffer;
}

加工的代码如下:

cpp 复制代码
RawEvent* event = buffer;
event->deviceId = deviceId;
event->type = iev.type;
event->code = iev.code;
event->value = iev.value;



struct RawEvent {
    nsecs_t when;//时间
    int32_t deviceId;//事件发生的设备id
    int32_t type;//类型,例如按键事件等
    int32_t code;//扫描码,按键对应的扫描码
    int32_t value;//值,表示按键按下,或者抬起等
};

所以此时只有按键按下的情况时,有六条RawEvent消息。

第一条:RawEvent是对应的扫描事件。type对应的是EV_MSC,code对应的是MSC_SCAN,value对应的是00090001

后面消息类同。

然后,我们需要回到调用getEvents函数的InputReader::loopOnce中。

2.5 InputReader::loopOnce

当getEvents函数返回6条RawEvent的消息时候,会调用processEventsLocked进行处理。

cpp 复制代码
void InputReader::loopOnce() {
    int32_t oldGeneration;
    int32_t timeoutMillis;
    bool inputDevicesChanged = false;
    Vector<InputDeviceInfo> inputDevices;
    

        oldGeneration = mGeneration;//值为1,在inputreader构造函数中mGeneration=1,
        timeoutMillis = -1;

        uint32_t changes = mConfigurationChangesToRefresh;//值为0,初始化时设置为0,代表无变化,不刷新配置
        

    size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);//通过EventHub的getEvents函数获取事件,
	//并存放在mEventBuffer中。
	//参数分析:timeoutMillis=-1,代表啥意思不清楚,感觉如果数字为0代表立即执行
	//mEventBuffer是一个存放从eventhub中读取的rawevent结构体类型的数组,源代码是:RawEvent mEventBuffer[EVENT_BUFFER_SIZE];
	//EVENT_BUFFER_SIZE值是256,代表最大可以读取256个原始事件

    

        if (count) {//返回的事件数量大于,则调用processEventsLocked处理事件
            processEventsLocked(mEventBuffer, count);
        }



    //发布事件。 processEventsLocked()函数在对事件进行加工处理之后,便将处理后的事件存储在
    // mQueuedListener中。在循环的最后,通过调用flush()函数将所有事件交付给InputDispatcher
    mQueuedListener->flush();
}

2.6 processEventsLocked

此函数的主要作用是:

1.如果事件是原始输入事件,将同一设备的原始输入事件进行打包处理。

2.如果事件是设备增删事件,则进行增删事件的处理。

cpp 复制代码
void InputReader::processEventsLocked(const RawEvent* rawEvents, size_t count) {//用rawEvents指针接收传入的从传入的mEventBuffer数组的首地址
    for (const RawEvent* rawEvent = rawEvents; count;) {//for循环遍历数组
        int32_t type = rawEvent->type;//取出数组中第0号的元素rawEvent的type.
        size_t batchSize = 1;
		//事件信息有两种,一种是设备节点的增加和删除事件,统称为设备事件,另一种是原始输入事件。
		//此处满足条件,则是对原始输入事件进行处理
        if (type < EventHubInterface::FIRST_SYNTHETIC_EVENT) {//FIRST_SYNTHETIC_EVENT值是0x10000000,type值都是小于FIRST_SYNTHETIC_EVENT
            int32_t deviceId = rawEvent->deviceId;//继续取出0号元素的id号
            while (batchSize < count) {
                if (rawEvent[batchSize].type >= EventHubInterface::FIRST_SYNTHETIC_EVENT
                        || rawEvent[batchSize].deviceId != deviceId) {//循环数组中所有其他的元素rawEvent对象,
						//只有当数组中其他rawEvent对象,即是原始输入事件也是同一个设备id的时候,则batchSize+1
                    break;
                }
                batchSize += 1;
            }

            processEventsForDeviceLocked(deviceId, rawEvent, batchSize);//此处相当于三个事件一起打包
			//此处举个例子
			//现在rawEvent数组有五个事件,前两个是id为1的设备产生的。第三个是id为2设备产生的,第四和第五是id为1设备产生的。
			//此时会先取出第一个事件的设备id,然后遍历第2.3.4.5事件的设备id,发现第二个事件也是设备id为1的。那就batchSize+1=2,
			//此时遍历第三个事件设备id,发现和前两个不一样,则退出循环。
			//表示,从第一个事件开始,一共有两个事件是相同的设备id为1产生的。所以,rawEvent参数和batchSize参数的含义就明显了。
			//那后面的第3.4.5事件呢,从下面的代码看出调用完processEventsForDeviceLocked后,rawEvent指针指向了数组中第三个事件,
			//然后继续遍历第4.5事件的id。
        }
		
        count -= batchSize;
        rawEvent += batchSize;
    }
}

2.7 processEventsForDeviceLocked

主要作用:

1.从保存的容器中找到此设备id对应的InputDevice类对象

2.调用此对象的process方法

cpp 复制代码
void InputReader::processEventsForDeviceLocked(int32_t deviceId,const RawEvent* rawEvents, size_t count) {
    ssize_t deviceIndex = mDevices.indexOfKey(deviceId);//从已经扫描到的设备容器中查找此id对应的device对象


    InputDevice* device = mDevices.valueAt(deviceIndex);//获取此deviceId对应的device对象


    device->process(rawEvents, count);//调用device->process方法处理。rawEvents是指向所有事件数组的首地址的指针,
	//count之前打包的是同一设备产生的输入事件的数量

}

2.8 InputDevice::process

cpp 复制代码
void InputDevice::process(const RawEvent* rawEvents, size_t count) {
   //按顺序处理每个映射器的所有事件。
    size_t numMappers = mMappers.size();//mMappers是一个存储InputMapper指针的容器,Vector<InputMapper*> mMappers;
    for (const RawEvent* rawEvent = rawEvents; count != 0; rawEvent++) {//取出每一个按键消息。此时第一个消息是扫描消息


		else {
            for (size_t i = 0; i < numMappers; i++) {//循环调用所有的mapper去派发按键消息
                InputMapper* mapper = mMappers[i];
                mapper->process(rawEvent);//将事件交给mapper处理。InputMapper有多个子类。
				//后续以keyborardinputMapper,用于处理键盘输入事件分析。
            }
        }
        --count;
    }
}

2.9 KeyboardInputMapper::process

此函数的主要作用是:

1.循环取出所有原始事件,进行处理。

1.1 如果是扫描消息,则取出其Value作为下一个按键消息的策略flag。但此扫描消息并不派发。

1.2 如果是按键消息,则进行处理派发。

1.3 如果是同步消息,则重置此按键消息的策略flag。

cpp 复制代码
void KeyboardInputMapper::process(const RawEvent* rawEvent) {
    switch (rawEvent->type) {
    case EV_KEY: {//如果是按键消息
        int32_t scanCode = rawEvent->code;
        int32_t usageCode = mCurrentHidUsage;
        mCurrentHidUsage = 0;

        if (isKeyboardOrGamepadKey(scanCode)) {
            processKey(rawEvent->when, rawEvent->value != 0, scanCode, usageCode);
        }
        break;
    }
    case EV_MSC: {//如果是杂项消息
        if (rawEvent->code == MSC_SCAN) {
            mCurrentHidUsage = rawEvent->value;
        }
        break;
    }
    case EV_SYN: {//如果是同步消息
        if (rawEvent->code == SYN_REPORT) {
            mCurrentHidUsage = 0;
        }
    }
    }
}

2.10 KeyboardInputMapper::process(扫描消息)

我们先看看第一个扫描消息是如何处理的。

cpp 复制代码
//先处理第一个消息-扫描消息,没派发
void KeyboardInputMapper::process(const RawEvent* rawEvent) {
    switch (rawEvent->type) {
    case EV_MSC: {
        if (rawEvent->code == MSC_SCAN) {
            mCurrentHidUsage = rawEvent->value;//记录当前扫描事件的value值。此时值为00090001,此值后续会对应policyflag
			}
        break;
		}
    }
}

2.11 KeyboardInputMapper::process(按键按下)

接下来,我们看看第二个消息按键按下是如何处理的。

cpp 复制代码
//处理第二个消息-key消息(A按键按下信息)
void KeyboardInputMapper::process(const RawEvent* rawEvent) {
    switch (rawEvent->type) {
    case EV_KEY: {//如果此事件是按键事件
        int32_t scanCode = rawEvent->code;//取出事件代码保存的键盘扫描码,此时是0130
        int32_t usageCode = mCurrentHidUsage;//保存值为00090001
        mCurrentHidUsage = 0;//设置为0

        if (isKeyboardOrGamepadKey(scanCode)) {//排除对鼠标按键的处理。鼠标按键由CursorInputMapper处理
            processKey(rawEvent->when, rawEvent->value != 0, scanCode, usageCode);//调用KeyboardInputMapper的processKey方法。
			//processKey方法,主要是将rawevent事件加工成NotifyKeyArgs,然后将NotifyKeyArgs放入队列中。
			//传入参数分析:rawEvent->when表示时间,rawEvent->value != 0如果为真,表示按键按下,scanCode扫描码
        }
        break;
    }
    }
}






//排除是鼠标的按键,鼠标按键由CursorInputMapper处理
bool KeyboardInputMapper::isKeyboardOrGamepadKey(int32_t scanCode) {
    return scanCode < BTN_MOUSE
        || scanCode >= KEY_OK
        || (scanCode >= BTN_MISC && scanCode < BTN_MOUSE)
        || (scanCode >= BTN_JOYSTICK && scanCode < BTN_DIGI);
}

2.12 KeyboardInputMapper::processKey

此处的主要作用是:

1.将扫描码和usagecode通过加载好的按键映射表转化为keycode和派发策略。

2.生成NotifyKeyArgs按键事件,插入到分发线程的队列中,等待分发。

那么为什么要将扫描码和usagecode转化为keycode和派发策略呢?

因为驱动往往是各个不同的供应商开发的,故存在不同键盘的扫描码是不同的,为了保证上层无论是什么牌子的键盘,对于按键A受到的消息是一致的,因此需要转化为统一的keycode,厂商会将对应关系写入配置文件,这样,无论是任何牌子的键盘按键A,上层都是同一个keycode。

cpp 复制代码
void KeyboardInputMapper::processKey(nsecs_t when, bool down, int32_t scanCode,int32_t usageCode) {
    int32_t keyCode;
    int32_t keyMetaState;
    uint32_t policyFlags;
	
	//通过EventHub的mapKey()函数进行映射。映射的输入为scancode和usagecode,而输出为keyCode和flag
	//scancode->keyCode
	//usagecode->flag
	//此步的作用是将物理的扫描码------scancode描述的按键,转化为操作系统的识别的keyCode------虚拟键值
	//EventHub::mapKey()函数可以根据设备Id找到对应的KeyLayoutMap,进而根据扫描码找到 
	//对应的Key结构体中所保存的虚拟键值(传出参数keycode)以及策略值(传出参数flag),从 
	//而完成从扫描码到虚拟键值的映射工作
    if (getEventHub()->mapKey(getDeviceId(), scanCode, usageCode, mMetaState,&keyCode, &keyMetaState, &policyFlags)) {
		keyCode = AKEYCODE_UNKNOWN;如果映射失败,则使用UNKNOWN作为事件的虚拟键值
        keyMetaState = mMetaState;//mMetaState默认是0,代表shift等控制键的状态
        policyFlags = 0;
    }

    if (down) {
        

        //KeyboardInputMapper维护了保存KeyDown结构体的容器mKeyDowns。
		//当按键按下时会生成一个保存了扫描码与keyCode的 KeyDown对象并添加到集合中。 
        
        ssize_t keyDownIndex = findKeyDown(scanCode);//通过扫描码对mKeyDowns容器查找,结果表明了按键是否是重复按下
		
         if (keyDownIndex >= 0) {
        } 
		else {
    
			
			//生成keyDown,并保存到mKeyDowns容器中
            mKeyDowns.push();
            KeyDown& keyDown = mKeyDowns.editTop();
            keyDown.keyCode = keyCode;
            keyDown.scanCode = scanCode;
        }

        mDownTime = when;//按键按下的时间,也就是事件发生的时间
    }
	
	

    nsecs_t downTime = mDownTime;
	
	//将所有的按键事件信息封装为NotifykeyArga对象,并将此对象通知给inputdispather的listener
    NotifyKeyArgs args(when, getDeviceId(), mSource, policyFlags,
            down ? AKEY_EVENT_ACTION_DOWN : AKEY_EVENT_ACTION_UP,
            AKEY_EVENT_FLAG_FROM_SYSTEM, keyCode, scanCode, keyMetaState, downTime);
			//参数分析:
			//when对应eventTime, 其实是rawEvent->when,就是事件发生的时间
			//getDeviceId()对应deviceId, 返回的是设备id
			//mSource对应source,是KeyboardInputMapper初始化时赋值的
			//其值是INPUT_DEVICE_CLASS_KEYBOARD,INPUT_DEVICE_CLASS_DPAD从,INPUT_DEVICE_CLASS_GAMEPAD的某一个
			//policyFlags对应policyFlags值是策略标志
			//down ? AKEY_EVENT_ACTION_DOWN : AKEY_EVENT_ACTION_UP,对应action,描述按下或者抬起
			//AKEY_EVENT_FLAG_FROM_SYSTEM对应flag,表示此事件是系统信任的。
			//keyCode对应keyCode,虚拟健keycode
			//scanCode对应scanCode,扫描码
			//keyMetaState对应metaState,控制健的值,其可以表示控制键的按下或者抬起,此时值应该是0,代表无任何控制键按下
			//downTime对应downTime就等于when
    getListener()->notifyKey(&args);//将生成的NotifyKeyArgs插入到mArgsQueue队列中
	//getListener返回的是QueuedInputListener类对象,其的一个属性是InputDispatcher类的对象
}

此处便是scancode和usagecode转化输出为keyCode和flag的地方。

cpp 复制代码
status_t EventHub::mapKey(int32_t deviceId,
        int32_t scanCode, int32_t usageCode, int32_t metaState,
        int32_t* outKeycode, int32_t* outMetaState, uint32_t* outFlags) const {
    AutoMutex _l(mLock);
    Device* device = getDeviceLocked(deviceId);//获取device对象
    status_t status = NAME_NOT_FOUND;

    if (device) {
       
        sp<KeyCharacterMap> kcm = device->getKeyCharacterMap();//获取键盘的映射表
        if (kcm != NULL) {
            if (!kcm->mapKey(scanCode, usageCode, outKeycode)) {//将扫描码和usageCode转化为outKeycode,如果转化失败,则代表需要策略
                *outFlags = 0;
                status = NO_ERROR;
            }
        }
		
        // Check the key layout next.
        if (status != NO_ERROR && device->keyMap.haveKeyLayout()) {
            if (!device->keyMap.keyLayoutMap->mapKey(
                    scanCode, usageCode, outKeycode, outFlags)) {//将扫描码和usageCode转化为outKeycode和outFlags
                status = NO_ERROR;
            }
        }

        if (status == NO_ERROR) {//转化成功后,再尝试将metaState转化为对应的outMetaState
            if (kcm != NULL) {
                kcm->tryRemapKey(*outKeycode, metaState, outKeycode, outMetaState);
            } else {
                *outMetaState = metaState;
            }
        }
    }
	


    return status;
}
cpp 复制代码
ssize_t KeyboardInputMapper::findKeyDown(int32_t scanCode) {
    size_t n = mKeyDowns.size();//mKeyDowns是一个存储KeyDown结构体的容器,Vector<KeyDown> mKeyDowns; 
	//    struct KeyDown {
    //    int32_t keyCode;
    //    int32_t scanCode;
    //};
    for (size_t i = 0; i < n; i++) {
        if (mKeyDowns[i].scanCode == scanCode) {
            return i;
        }
    }
    return -1;
}

2.13 QueuedInputListener::notifyKey

将按键消息放入分发队列中

cpp 复制代码
void QueuedInputListener::notifyKey(const NotifyKeyArgs* args) {
    mArgsQueue.push(new NotifyKeyArgs(*args));//将消息放入mArgsQueue队列中
}

2.14 KeyboardInputMapper::process(同步消息)

cpp 复制代码
//处理第三个消息-同步消息,也没派发。
void KeyboardInputMapper::process(const RawEvent* rawEvent) {
    switch (rawEvent->type) {
    case EV_SYN: {
        if (rawEvent->code == SYN_REPORT) {
            mCurrentHidUsage = 0;//将mCurrentHidUsage重置为o
        }
    }
    }
}

2.15 KeyboardInputMapper::process(扫描事件)

接下来,是第四条消息的扫描事件的处理。此处同2.10

cpp 复制代码
//,处理第四个消息-扫描消息,没派发
void KeyboardInputMapper::process(const RawEvent* rawEvent) {
    switch (rawEvent->type) {
    case EV_MSC: {
        if (rawEvent->code == MSC_SCAN) {
            mCurrentHidUsage = rawEvent->value;//记录当前扫描事件的value值。此时值为00090001,此值后续会对应policyflag
			}
        break;
		}
    }
}

2.16 KeyboardInputMapper::process(按键抬起事件)

cpp 复制代码
//处理第二个消息-key消息(A按键抬起信息)
void KeyboardInputMapper::process(const RawEvent* rawEvent) {
    switch (rawEvent->type) {
    case EV_KEY: {//如果此事件是按键事件
        int32_t scanCode = rawEvent->code;//取出事件代码保存的键盘扫描码,此时是0130
        int32_t usageCode = mCurrentHidUsage;//保存值为00090001
        mCurrentHidUsage = 0;//设置为0

        if (isKeyboardOrGamepadKey(scanCode)) {//排除对鼠标按键的处理。鼠标按键由CursorInputMapper处理
            processKey(rawEvent->when, rawEvent->value != 0, scanCode, usageCode);//调用KeyboardInputMapper的processKey方法。
			//processKey方法,主要是将rawevent事件加工成NotifyKeyArgs,然后将NotifyKeyArgs放入队列中。
			//传入参数分析:rawEvent->when表示时间,rawEvent->value != 0如果为真,表示按键按下,scanCode扫描码
        }
        break;
    }
    }
}

此处的作用是:

1.将按键的扫描码和usagecode通过键盘映射表转化为keycode和分发策略

2.生成按键抬起的NotifyKeyArgs事件,并放入消息队列中。

cpp 复制代码
//按键抬起事件分析
void KeyboardInputMapper::processKey(nsecs_t when, bool down, int32_t scanCode,
        int32_t usageCode) {
    int32_t keyCode;
    int32_t keyMetaState;
    uint32_t policyFlags;

    if (getEventHub()->mapKey(getDeviceId(), scanCode, usageCode, mMetaState,
                              &keyCode, &keyMetaState, &policyFlags)) {
        keyCode = AKEYCODE_UNKNOWN;
        keyMetaState = mMetaState;
        policyFlags = 0;
    }

    if (down) {
        ......
    } else {

        ssize_t keyDownIndex = findKeyDown(scanCode);
        if (keyDownIndex >= 0) {
            keyCode = mKeyDowns.itemAt(keyDownIndex).keyCode;//当按键抬起时,和按键按下保持同一个keycode
            mKeyDowns.removeAt(size_t(keyDownIndex));//从保存按下的容器中删除key按下
        } 
    }
	
	
	
    nsecs_t downTime = mDownTime;//此按键按下发生的时间

	
	//将所有的按键事件信息封装为NotifykeyArga对象,并将此对象通知给inputdispather的listener
    NotifyKeyArgs args(when, getDeviceId(), mSource, policyFlags,
            down ? AKEY_EVENT_ACTION_DOWN : AKEY_EVENT_ACTION_UP,
            AKEY_EVENT_FLAG_FROM_SYSTEM, keyCode, scanCode, keyMetaState, downTime);
			//参数分析:
			//when对应eventTime, 其实是rawEvent->when,就是事件发生的时间
			//getDeviceId()对应deviceId, 返回的是设备id
			//mSource对应source,是KeyboardInputMapper初始化时赋值的
			//其值是INPUT_DEVICE_CLASS_KEYBOARD,INPUT_DEVICE_CLASS_DPAD从,INPUT_DEVICE_CLASS_GAMEPAD的某一个
			//policyFlags对应policyFlags值是策略标志
			//down ? AKEY_EVENT_ACTION_DOWN : AKEY_EVENT_ACTION_UP,对应action,描述按下或者抬起
			//AKEY_EVENT_FLAG_FROM_SYSTEM对应flag,表示此事件是系统信任的。
			//keyCode对应keyCode,虚拟健keycode
			//scanCode对应scanCode,扫描码
			//keyMetaState对应metaState,控制健的值,其可以表示控制键的按下或者抬起,此时值应该是0,代表无任何控制键按下
			//downTime对应按键按下的downTime
    getListener()->notifyKey(&args);
}

2.17 KeyboardInputMapper::process(同步消息)

cpp 复制代码
//抬起时的第三条消息同步消息,也没派发
void KeyboardInputMapper::process(const RawEvent* rawEvent) {
    switch (rawEvent->type) {
    case EV_SYN: {
        if (rawEvent->code == SYN_REPORT) {
            mCurrentHidUsage = 0;
        }
    }
    }
}

2.18 QueuedInputListener::flush

所以此时分发队列的线程中存在两个事件,一个是按下事件,一个是抬起事件。然后在InputReader::loopOnce函数会调用flush唤醒分发线程去处理。

cpp 复制代码
/所以当一个按键按下抬起时,产生6个消息时,只有key事件会被加入队列派发。
void QueuedInputListener::flush() {
    size_t count = mArgsQueue.size();
    for (size_t i = 0; i < count; i++) {//从存储NotifyArgs的容器中循环取出并调用notify
        NotifyArgs* args = mArgsQueue[i];
        args->notify(mInnerListener);//传入的参数mInnerListener是InputDispatcher类对象
        delete args;
    }
    mArgsQueue.clear();
}

2.19 NotifyKeyArgs::notify

此时先查看第一个按下事件是如何处理的。

cpp 复制代码
void NotifyKeyArgs::notify(const sp<InputListenerInterface>& listener) const {
    listener->notifyKey(this);//此处listener是InputDispatcher类对象,this是调用者也就是一个NotifyKeyArgs对象
}

2.20 InputDispatcher::notifyKey

此函数的作用为:

1.首先通过NotifyKeyArgs生成KeyEvent事件,KeyEvent事件一般是上层最终消费的事件类型。

然后向ims询问此事件的发送策略,是否是PhoneWindowManager消费。因为有的按键不会派发给应用,如手机的开机按键,不需要派发给应用,是派发给PhoneWindowManager拦截消费的,如果此按键被拦截消费了,则不再派发给应用。

2.查询派发策略是派发给应用的后,通过NotifyKeyArgs生成KeyEntry事件,并将其放入分发队列的队尾。然后唤醒分发线程派发。

注意:此处是每个消息都会唤醒一次,故此时是按键按下事件会唤醒一次,然后按键抬起事件再唤醒一次。

cpp 复制代码
//此时我们分析的是普通按键的发送,故先不分析如开关键等需要phoneManager消费的按键事件。
void InputDispatcher::notifyKey(const NotifyKeyArgs* args) {
    if (!validateKeyEvent(args->action)) {//验证keyEvent是否有效,主要是验证action是否等于按下或者抬起,
	//如果不是则数据无效,则返回
        return;
    }

    uint32_t policyFlags = args->policyFlags;//派发策略flag
    int32_t flags = args->flags;//此时flag是AKEY_EVENT_FLAG_FROM_SYSTEM
    int32_t metaState = args->metaState;//控制键按下或者抬起的信息
    

    policyFlags |= POLICY_FLAG_TRUSTED;//指示输入事件来自受信任的源,例如直接连接的输入设备

    int32_t keyCode = args->keyCode;
	
	
    KeyEvent event;//用NotifyKeyArgs初始化了KeyEvent,主要用于inputfiter,询问是否过滤
	//例如:有些需要phoneManager消费的按键,如手机的开关键等,会拦截发送给phoneManager系统
    event.initialize(args->deviceId, args->source, args->action,
            flags, keyCode, args->scanCode, metaState, 0,
            args->downTime, args->eventTime);
	

    android::base::Timer t;
    mPolicy->interceptKeyBeforeQueueing(&event, policyFlags);//向mPolicy询问派发策略
    if (t.duration() > SLOW_INTERCEPTION_THRESHOLD) {}

    bool needWake;
    { // acquire lock
        mLock.lock();


        int32_t repeatCount = 0;
        KeyEntry* newEntry = new KeyEntry(args->eventTime,//用NotifyKeyArgs初始化了KeyEntry
                args->deviceId, args->source, policyFlags,
                args->action, flags, keyCode, args->scanCode,
                metaState, repeatCount, args->downTime);

        needWake = enqueueInboundEventLocked(newEntry);//此函数会在内部将KeyEntry添加到mInboundqueue队列队尾,
		//并返回是否需要唤醒,如果是true,则唤醒dispatcher线程
        mLock.unlock();
    } // release lock

    if (needWake) {
        mLooper->wake();//唤醒分发线程。
    }
}
cpp 复制代码
static bool validateKeyEvent(int32_t action) {
    if (! isValidKeyAction(action)) {
        ALOGE("Key event has invalid action code 0x%x", action);
        return false;
    }
    return true;
}



static bool isValidKeyAction(int32_t action) {
    switch (action) {
    case AKEY_EVENT_ACTION_DOWN:
    case AKEY_EVENT_ACTION_UP:
        return true;
    default:
        return false;
    }
}

2.21 NativeInputManager::interceptKeyBeforeQueueing

此处主要作用为:

1.向IMS询问派发策略,最后其实是向PhoneWindowManager询问派发按键策略。

cpp 复制代码
void NativeInputManager::interceptKeyBeforeQueueing(const KeyEvent* keyEvent, uint32_t& policyFlags) {
    ATRACE_CALL();
    bool interactive = mInteractive.load();//mInteractive是原子变量,所以需要用load。此时设备是否处于交互中
    if (interactive) {
        policyFlags |= POLICY_FLAG_INTERACTIVE;//指示截获事件时设备处于交互状态。
    }
    if ((policyFlags & POLICY_FLAG_TRUSTED)) {//如果policyFlags是受信任的
        nsecs_t when = keyEvent->getEventTime();
        JNIEnv* env = jniEnv();
        jobject keyEventObj = android_view_KeyEvent_fromNative(env, keyEvent);//通过jni用c++的keyevent形成一个java的keyevent
        jint wmActions;
        if (keyEventObj) {
            wmActions = env->CallIntMethod(mServiceObj,gServiceClassInfo.interceptKeyBeforeQueueing,keyEventObj, policyFlags);
			//调用了IMS中的interceptKeyBeforeQueueing方法
            if (checkAndClearExceptionFromCallback(env, "interceptKeyBeforeQueueing")) {
                wmActions = 0;
            }
            android_view_KeyEvent_recycle(env, keyEventObj);
            env->DeleteLocalRef(keyEventObj);
        } else {
            ALOGE("Failed to obtain key event object for interceptKeyBeforeQueueing.");
            wmActions = 0;
        }

        handleInterceptActions(wmActions, when, /*byref*/ policyFlags);//将wmActions策略添加给policyFlags
    } else {//如果policyFlags是不受信任的
        if (interactive) {
            policyFlags |= POLICY_FLAG_PASS_TO_USER;//则设备消息类型为发送给应用程序
        }
    }
}

2.22 IMS中的interceptKeyBeforeQueueing

此时通过jni走到了IMS.java的interceptKeyBeforeQueueing函数。

此函数的作用是:

1.调用到WMS中的interceptKeyBeforeQueueing函数。其实现类是InputMonitor.java

cpp 复制代码
//IMS.java
//派发给wms,查看是否将消息拦截,此处并不分析//派发给wms,查看是否将消息拦截
private int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) {
    return mWindowManagerCallbacks.interceptKeyBeforeQueueing(event, policyFlags);
}

2.23 WMS中的interceptKeyBeforeQueueing

cpp 复制代码
//此时已经到了WMS中,InputMonitor.JAVA中 
public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) {
        return mService.mPolicy.interceptKeyBeforeQueueing(event, policyFlags);
//是WindowManagerPolicy接口的函数,实现类是PhoneWindowManager
    }

2.24 PhoneWindowManager中的interceptKeyBeforeQueueing

此函数的主要作用是:

1.如果是普通按键,则返回派发给应用程序的派发策略。

2.如果是特殊按键,如电源按键,会取消其派发给应用程序的策略。

java 复制代码
//PhoneWindowManager.java
//此处代码太多,只看部分代码
    public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) {


        final boolean interactive = (policyFlags & FLAG_INTERACTIVE) != 0; //interactive值是true,即应用正在和用户交互中
        final boolean down = event.getAction() == KeyEvent.ACTION_DOWN;//按下或抬起
        final boolean canceled = event.isCanceled();//是否取消false
        final int keyCode = event.getKeyCode();

        final boolean isInjected = (policyFlags & WindowManagerPolicy.FLAG_INJECTED) != 0;//是否是注入的方式,是false

 



        // Basic policy based on interactive state.
        int result;
        boolean isWakeKey = (policyFlags & WindowManagerPolicy.FLAG_WAKE) != 0
                || event.isWakeKey();//是否是唤醒按键,此时是false
        if (interactive || (isInjected && !isWakeKey)) {//此处走这里,不是唤醒,且正在和用户交互
            result = ACTION_PASS_TO_USER;//发送给应用程序
            isWakeKey = false;

            if (interactive) {
                
                if (keyCode == mPendingWakeKey && !down) {
                    result = 0;
                }
                // Reset the pending key
                mPendingWakeKey = PENDING_KEY_NULL;
            }
        }


        // 此处主要处理一些特殊按键,仅列出电源按键
        switch (keyCode) {
			
			/*
            case KeyEvent.KEYCODE_POWER: {
                // Any activity on the power button stops the accessibility shortcut
                cancelPendingAccessibilityShortcutAction();
                result &= ~ACTION_PASS_TO_USER;//可以看出如果是电源按键,则会取消派发给应用程序的ACTION_PASS_TO_USER标志
                isWakeKey = false; // wake-up will be handled separately
                if (down) {
                    interceptPowerKeyDown(event, interactive);
                } else {
                    interceptPowerKeyUp(event, interactive, canceled);
                }
                break;
            }
			*/

        }

        return result;//返回派发策略,此时是派发给应用程序
    }

2.25 NativeInputManager::handleInterceptActions

1.主要是添加发送给应用的flag标志

cpp 复制代码
void NativeInputManager::handleInterceptActions(jint wmActions, nsecs_t when,uint32_t& policyFlags) {
    if (wmActions & WM_ACTION_PASS_TO_USER) {//主要是给policyFlags添加派发给应用的flag
        policyFlags |= POLICY_FLAG_PASS_TO_USER;
    } else {
    }
}

2.26 InputDispatcher::enqueueInboundEventLocked

此函数的主要作用是:

1.将EventEntry类型的消息放入队尾。

2.然后判断其是不是home按键,甚至最长相应home按键抬起的时间是0.5秒。因此假设应用卡住,那么用户会疯狂的按home按键,请求推出,如果home按键的响应时间过长,则用户体验会非常的差,因此必须在0.5秒内响应home按键。

cpp 复制代码
bool InputDispatcher::enqueueInboundEventLocked(EventEntry* entry) {
    bool needWake = mInboundQueue.isEmpty();
    mInboundQueue.enqueueAtTail(entry);//将该事件放入mInboundQueue队列尾部
    traceInboundQueueLengthLocked();//追踪队列的长度

    switch (entry->type) {
    case EventEntry::TYPE_KEY: {
		//为HOME键或其他窗口上的点击事件提高响应速度的优化操作,这些所谓的优化操作可能会导致派发队列之前 
		//的所有事件被丢弃
        KeyEntry* keyEntry = static_cast<KeyEntry*>(entry);
        if (isAppSwitchKeyEventLocked(keyEntry)) {//判断是否是home键,如果是
            if (keyEntry->action == AKEY_EVENT_ACTION_DOWN) {//如果是home按键按下,则设置mAppSwitchSawKeyDown为true
                mAppSwitchSawKeyDown = true;
            } else if (keyEntry->action == AKEY_EVENT_ACTION_UP) {//如果是home按键抬起,则设置最迟响应home键的时间。
                if (mAppSwitchSawKeyDown) {
                    mAppSwitchDueTime = keyEntry->eventTime + APP_SWITCH_TIMEOUT;//设置mAppSwitchDueTime时间
					//mAppSwitchDueTime代表了最近发生窗口切换操作(按home键)的最迟发送时间。APP_SWITCH_TIMEOUT值是0.5秒
                    mAppSwitchSawKeyDown = false;
                    needWake = true;//需要立即唤醒inputdispatcher线程去分发
                }
            }
        }
        break;
    }
    }

    return needWake;
}
cpp 复制代码
bool InputDispatcher::isAppSwitchKeyEventLocked(KeyEntry* keyEntry) {
    return ! (keyEntry->flags & AKEY_EVENT_FLAG_CANCELED)
            && isAppSwitchKeyCode(keyEntry->keyCode)
            && (keyEntry->policyFlags & POLICY_FLAG_TRUSTED)
            && (keyEntry->policyFlags & POLICY_FLAG_PASS_TO_USER);
}





bool InputDispatcher::isAppSwitchKeyCode(int32_t keyCode) {
    return keyCode == AKEYCODE_HOME //home按键
            || keyCode == AKEYCODE_ENDCALL //挂机按键
            || keyCode == AKEYCODE_APP_SWITCH; //app切换按键
}

此时是按键按下事件的处理,接下来便是InputDispatcher线程篇的流程,请看【android 9】【input】【8.发送按键事件2------InputDispatcher线程】

我们继续看看按键抬起的事件处理。此时我们需要回到2.18节

2.27 NotifyKeyArgs::notify(抬起)

此时查看第二个抬起事件是如何处理的。其实抬起按键的处理和按下按键的处理基本相似,即从2.19----2.26节的流程,故此处不再赘述

cpp 复制代码
void NotifyKeyArgs::notify(const sp<InputListenerInterface>& listener) const {
    listener->notifyKey(this);//此处listener是InputDispatcher类对象,this是调用者也就是一个NotifyKeyArgs对象
}

此篇我们主要介绍了普通按键事件的inputreader线程是如何处理的,下一篇我将描述

InputDispatcher线程是如何派发处理的。

相关推荐
阿巴斯甜4 小时前
Android 报错:Zip file '/Users/lyy/develop/repoAndroidLapp/l-app-android-ble/app/bu
android
Kapaseker4 小时前
实战 Compose 中的 IntrinsicSize
android·kotlin
xq95275 小时前
Andorid Google 登录接入文档
android
黄林晴6 小时前
告别 Modifier 地狱,Compose 样式系统要变天了
android·android jetpack
冬奇Lab19 小时前
Android触摸事件分发、手势识别与输入优化实战
android·源码阅读
城东米粉儿1 天前
Android MediaPlayer 笔记
android
Jony_1 天前
Android 启动优化方案
android
阿巴斯甜1 天前
Android studio 报错:Cause: error=86, Bad CPU type in executable
android
张小潇1 天前
AOSP15 Input专题InputReader源码分析
android
_小马快跑_1 天前
Kotlin | 协程调度器选择:何时用CoroutineScope配置,何时用launch指定?
android