【android 9】【input】【8.发送按键事件2——InputDispatcher线程】

news2024/11/19 21:31:20

系列文章目录

本人系列文章-CSDN博客


目录

系列文章目录

1.简介

1.1流程介绍

1.2 时序图

2.普通按键消息发送部分源码分析(按键按下事件)

2.1 开机后分发线程阻塞的地方

 2.2 InputDispatcher::dispatchOnceInnerLocked

 2.3 InputDispatcher::dispatchKeyLocked

 2.4 InputDispatcher::dispatchOnce

2.5 runCommandsLockedInterruptible

 2.6 InputDispatcher::doInterceptKeyBeforeDispatchingLockedInterruptible

 2.7 InputDispatcher::dispatchOnce

 2.8 InputDispatcher::dispatchKeyLocked

 2.9 InputDispatcher::findFocusedWindowTargetsLocked

2.10 dispatchEventLocked

 2.11 prepareDispatchCycleLocked

 2.12 enqueueDispatchEntriesLocked

2.13  enqueueDispatchEntryLocked

 2.14 startDispatchCycleLocked

2.15 InputPublisher::publishKeyEvent

 2.16 InputChannel::sendMessage


1.简介

从上一篇幅我们知道了,普通按键事件在inputreader线程中会将按下事件和抬起事件放入mInboundQueue队列中,然后唤醒InputDispatcher线程去进行派发。本篇我们便来介绍一下InputDispatcher线程是如何派发的。

1.1流程介绍

1.当InputDispatcher线程被唤醒时,第一次执行dispatchOnce时,会生成一个去查询按键事件派发策略的命令并放入命令队列中,然后开始第二次循环。

2.在第二次循环的时候,会从命令队列中取出命令,然后通过jni走到IMS,再走到WMS,再走到phonewindowMnager中询问此按键的派发策略。然后开始第三次循环。

3.如果询问的按键派发策略是立即派发,则会寻找焦点窗口。

4.查找到合适的目标窗口后,会从保存窗口信息的容器中取出窗口信息,窗口信息保存着socket的然后通过sokcet对,将按键消息从IMS发送到应用程序端。

此sokcet对是应用在创建的时候,会调用WMS的addView函数,此函数会创建两个socket对,其中一个fd通过binder通信返回给应用端,应用端会将此fd监听查看是否有消息,另外一个会传递给IMS,IMS也会监听其fd是否有消息。

1.2 时序图

 (图片可保存到本地放大观看)

2.普通按键消息发送部分源码分析(按键按下事件)

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

2.1 开机后分发线程阻塞的地方

void InputDispatcher::dispatchOnce() {
    nsecs_t nextWakeupTime = LONG_LONG_MAX;//下一次唤醒该线程的时间,也可以说是下一次线程循环的执行时间点
    { 
        
        mDispatcherIsAliveCondition.broadcast();//分发线程处于活跃状态,进行广播
		
        if (!haveCommandsLocked()) {//此时无命令。作用:检查inputdispatcher的缓存队列中是否有还未处理的命令,
		//只有无命令时才会执行dispatchOnceInnerLocked方法,进行事件的分发。
		
            dispatchOnceInnerLocked(&nextWakeupTime);// 传入的nextWakeupTime决定了下次派发线程循环的执行时间点,
        }

        if (runCommandsLockedInterruptible()) {//如果此时缓存队列中有命令,则立即执行该命令,
		//并将下一次线程循环的执行时间点设置为LONG_LONG_MIN,将使派发线程立刻开始下次线程。
		//最小值定义为0,最大值是unsigned long long的最大值:1844674407370955161
            nextWakeupTime = LONG_LONG_MIN;
        }
    } 

    nsecs_t currentTime = now();//获取当前时间点
    int timeoutMillis = toMillisecondTimeoutDelay(currentTime, nextWakeupTime);//下一次唤醒时间点-当前时间点=线程休眠时间
    mLooper->pollOnce(timeoutMillis);//调用pollOnce使线程休眠,等待回调,唤醒或超时。
	//Looper的pollOnce()的实质就是epoll_wait()。 因此派发线程的休眠在三种情况下可能被唤醒:
	1.调用Looper::wake()函数主动唤醒(有输入事件注入派发队列中时),
	2.到达nextWakeupTime的事件点时唤醒
	3.epoll_waitepoll_wait()监听的fd有epoll_event发生时唤醒(这种唤醒方式将在介绍ANR机制时讨论)。
}

从上一篇我们知道,当 inputreader线程向mInboundQueue中每次插入消息后,都会调用wake来唤醒分发线程。我们首先看一下按键按下的事件分发流程。

 2.2 InputDispatcher::dispatchOnceInnerLocked

此函数的主要作用是:

1.取出消息,然后通过按键的派发策略,和时间等生成对应的分发策略,默认为不丢弃。

2.然后再调用dispatchKeyLocked进行事件的派发。

void InputDispatcher::dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) {
    nsecs_t currentTime = now();

	//如果没有待发送的事件,则从mInboundQueue队列中取出一个事件,进行分发
    if (! mPendingEvent) {

       }
		
		else {
            //从派发队列中将位于队首的一条EventEntry取出并保存在mPendingEvent成员变量中。 
            // mPendingEvent表示处于派发过程中的一个输入事件
            mPendingEvent = mInboundQueue.dequeueAtHead();
            traceInboundQueueLengthLocked();//更新一下队列长度
        }

        
        if (mPendingEvent->policyFlags & POLICY_FLAG_PASS_TO_USER) //设备消息类型为发送给应用程序
		{//判断此key是否需要发送到应用程序。
            pokeUserActivityLocked(mPendingEvent);//用来决定是否要唤醒设备或者点亮屏幕,最终调用的是PowerManagerService。
        }
		

        
        resetANRTimeoutsLocked();//重置ANR超时时间
    }


    bool done = false;
    DropReason dropReason = DROP_REASON_NOT_DROPPED;//默认丢弃原因为不丢弃
	

    switch (mPendingEvent->type) {

    case EventEntry::TYPE_KEY: {//按键类型
        KeyEntry* typedEntry = static_cast<KeyEntry*>(mPendingEvent);
		
        done = dispatchKeyLocked(currentTime, typedEntry, &dropReason, nextWakeupTime);//进一步分发按键事件,结果赋值给 done
          // 无论是成功派发还是事件被丢弃,都返回 true,当要询问key派发策略时是false
        break;
    }

    default:
        break;
    }

    if (done) {//无论是成功派发还是事件被丢弃,都返回 true,当要询问key派发策略时是false

        mLastDropReason = dropReason;

        releasePendingEventLocked();
        *nextWakeupTime = LONG_LONG_MIN;  // force next poll to wake up immediately
    }
}

 2.3 InputDispatcher::dispatchKeyLocked

此函数的作用是:

1.生成一个commond命令,并入队命令队列,询问此按键按下事件的分发策略。注意,一共是有两个策略,一个是派发策略,派发策略主要是询问是否是系统消费,是否要派发给应用程序等,而此时的分发策略是询问是否丢弃派发,是否等会派发等(如组合按键)

2.终止此时按键事件的派发,开启下一轮循环,先询问按键的分发策略,再派发此按键按下事件。

//此次派发会生成一个commond命令,并入队。去询问key的派发策略,因此此key事件咱未派发。commandEntry->keyEntry = entry;中保存了此次要派发的entry的信息。
bool InputDispatcher::dispatchKeyLocked(nsecs_t currentTime, KeyEntry* entry,DropReason* dropReason, nsecs_t* nextWakeupTime) {
    // Preprocessing.
    if (! entry->dispatchInProgress) {//dispatchInProgress默认为false,表示事件是否正在派发

        entry->dispatchInProgress = true;//设置事件为正在派发中
    }


    // 给policy一个机会,去截断key.
	//如果事件的interceptKeyResult是不知道,但是事件的policyFlags是要发送到应用程序的,则生成一个CommandEntry,然后返回false,等待命令的执行
	//如果事件的interceptKeyResult是不知道,事件不是发送给应用程序的,则设置falg为INTERCEPT_KEY_RESULT_CONTINUE。
	//如果事件的interceptKeyResult是INTERCEPT_KEY_RESULT_SKIP,表示跳过此事件,则设置其丢弃原因为DROP_REASON_POLICY
	
    if (entry->interceptKeyResult == KeyEntry::INTERCEPT_KEY_RESULT_UNKNOWN) {//初始化的时候值是INTERCEPT_KEY_RESULT_UNKNOWN
	//如果此事件尚未进行过派发策略查询,则通过发送一个命令的方式查询派发策路 
        if (entry->policyFlags & POLICY_FLAG_PASS_TO_USER) {
            CommandEntry* commandEntry = postCommandLocked(//生成了一个commandEntry
                    & InputDispatcher::doInterceptKeyBeforeDispatchingLockedInterruptible);

			
            commandEntry->keyEntry = entry;
            entry->refCount += 1;
            return false; // 终止此次派发,开始下一轮的循环等待策略查询完成
        }
	}
}
InputDispatcher::CommandEntry* InputDispatcher::postCommandLocked(Command command) {
    CommandEntry* commandEntry = new CommandEntry(command);
    mCommandQueue.enqueueAtTail(commandEntry);//入队命令队列
    return commandEntry;
}

 此时我们回到 2.2 InputDispatcher::dispatchOnceInnerLocked章节,所以我们可以看出来,第一次派发的按键时,要询问该按键的派发策略,所以done的值是flase

//此时回到上面928行的这个函数,注意此时命令队列中存在一条询问key事件派发策略的命令。
void InputDispatcher::dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) {
        done = dispatchKeyLocked(currentTime, typedEntry, &dropReason, nextWakeupTime);//进一步分发按键事件,结果赋值给 done
          // 无论是成功派发还是事件被丢弃,都返回 true,当要询问key派发策略时是false
	/**
	//此时是false,
    if (done) {
        if (dropReason != DROP_REASON_NOT_DROPPED) {
            dropInboundEventLocked(mPendingEvent, dropReason);
        }
        mLastDropReason = dropReason;

        releasePendingEventLocked();
        *nextWakeupTime = LONG_LONG_MIN;  // force next poll to wake up immediately
    }
	*/
}

 2.4 InputDispatcher::dispatchOnce

然后开启了下一次循环,此时命令队列中存在查询按键按下的分发策略的命令,所以执行runCommandsLockedInterruptible函数

void InputDispatcher::dispatchOnce() {
    nsecs_t nextWakeupTime = LONG_LONG_MAX;//下一次唤醒该线程的时间,也可以说是下一次线程循环的执行时间点
    { 
        AutoMutex _l(mLock);
        mDispatcherIsAliveCondition.broadcast();//分发线程处于活跃状态,进行广播
		
		
		/*
        if (!haveCommandsLocked()) {//此时有命令。作用:检查inputdispatcher的缓存队列中是否有还未处理的命令,
		//只有无命令时才会执行dispatchOnceInnerLocked方法,进行事件的分发。
		
            dispatchOnceInnerLocked(&nextWakeupTime);// 传入的nextWakeupTime决定了下次派发线程循环的执行时间点,
        }*/

        if (runCommandsLockedInterruptible()) {//如果此时缓存队列中有一条询问key事件派发策略的命令,则立即执行该命令,
		//并将下一次线程循环的执行时间点设置为LONG_LONG_MIN,将使派发线程立刻开始下次线程。
		//最小值定义为0,最大值是unsigned long long的最大值:1844674407370955161
            nextWakeupTime = LONG_LONG_MIN;//立刻下次唤醒。
        }
    } // release lock

    nsecs_t currentTime = now();//获取当前时间点
    int timeoutMillis = toMillisecondTimeoutDelay(currentTime, nextWakeupTime);//下一次唤醒时间点-当前时间点=线程休眠时间
    mLooper->pollOnce(timeoutMillis);//调用pollOnce使线程休眠,等待回调,唤醒或超时。
	//Looper的pollOnce()的实质就是epoll_wait()。 因此派发线程的休眠在三种情况下可能被唤醒:
	1.调用Looper::wake()函数主动唤醒(有输入事件注入派发队列中时),
	2.到达nextWakeupTime的事件点时唤醒
	3.epoll_waitepoll_wait()监听的fd有epoll_event发生时唤醒(这种唤醒方式将在介绍ANR机制时讨论)。
}

2.5 runCommandsLockedInterruptible

此函数的主要作用是:

1.取出命令,执行doInterceptKeyBeforeDispatchingLockedInterruptible函数,查询分发策略。

bool InputDispatcher::runCommandsLockedInterruptible() {

    do {
        CommandEntry* commandEntry = mCommandQueue.dequeueAtHead();//取出命令

        Command command = commandEntry->command;
        (this->*command)(commandEntry); // 执行doInterceptKeyBeforeDispatchingLockedInterruptible函数,并传入commandEntry作为参数

        commandEntry->connection.clear();//清除connection
        delete commandEntry;
    } while (! mCommandQueue.isEmpty());
    return true;
}

 2.6 InputDispatcher::doInterceptKeyBeforeDispatchingLockedInterruptible

此处简单介绍一下:

会先通过JNI走到IMS中,IMS会调用到WMS中,最终会调用到phoneWindowManager的interceptKeyBeforeDispatching函数,此函数内部会返回delay。

1.正常应用获取焦点时(即应用在前台交互),那么事件则可以继续派发。

2.比如我们现在按下了手机的音量按键,有可能存在接下来按键是电源按键,则存在是组合按键实现截屏的功能,因此现在系统也不知道此时是控制音量,还是截屏,所以需要等一会再询问此按键派发策略。

3.如果此时系统或者焦点(即应用程序没有焦点),则派发给应用是无效的,所以该按键丢弃。

//向mPolicy询问,
//如果delay小于0,则设置为INTERCEPT_KEY_RESULT_SKIP,代表丢弃该事件
//如果delay=0,则设置为INTERCEPT_KEY_RESULT_CONTINUE,代表事件可以继续派发
//如果delay>0,则设置为INTERCEPT_KEY_RESULT_TRY_AGAIN_LATER,并设置interceptKeyWakeupTime,表示等会再询问派发策略
void InputDispatcher::doInterceptKeyBeforeDispatchingLockedInterruptible(
        CommandEntry* commandEntry) {
    KeyEntry* entry = commandEntry->keyEntry;//从命令队列中取出key entry事件

    KeyEvent event;
    initializeKeyEvent(&event, entry);

    mLock.unlock();

    android::base::Timer t;
    nsecs_t delay = mPolicy->interceptKeyBeforeDispatching(commandEntry->inputWindowHandle,&event, entry->policyFlags);//此时应该是空
    if (t.duration() > SLOW_INTERCEPTION_THRESHOLD) {
        ALOGW("Excessive delay in interceptKeyBeforeDispatching; took %s ms",
                std::to_string(t.duration().count()).c_str());
    }

    mLock.lock();

    if (delay < 0) {//如果delay小于0,则设置为INTERCEPT_KEY_RESULT_SKIP//代表丢弃该事件
        entry->interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_SKIP;
    } else if (!delay) {如果delay=0,则设置为INTERCEPT_KEY_RESULT_CONTINUE//代表事件可以继续派发
        entry->interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_CONTINUE;
    } else {//如果delay>0,则设置为INTERCEPT_KEY_RESULT_TRY_AGAIN_LATER,表示等会再询问派发策略,并设置interceptKeyWakeupTime
        entry->interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_TRY_AGAIN_LATER;
        entry->interceptKeyWakeupTime = now() + delay;
    }
    entry->release();
}

 2.7 InputDispatcher::dispatchOnce

然后会询问完按键按下事件的派发策略后,会立刻唤醒派发循环,开始派发此按键按下的事件。

//执行查询派发key事件策略的命令后,会立即执行下一次循环。
void InputDispatcher::dispatchOnce() {
    nsecs_t nextWakeupTime = LONG_LONG_MAX;//下一次唤醒该线程的时间,也可以说是下一次线程循环的执行时间点
    { 
        AutoMutex _l(mLock);
        mDispatcherIsAliveCondition.broadcast();//分发线程处于活跃状态,进行广播
		
        if (!haveCommandsLocked()) {//作用:检查inputdispatcher的缓存队列中是否有还未处理的命令
		//只有无命令时才会执行dispatchOnceInnerLocked方法,进行事件的分发。
		
            dispatchOnceInnerLocked(&nextWakeupTime);// 传入的nextWakeupTime决定了下次派发线程循环的执行时间点
        }

        
    } // release lock

    nsecs_t currentTime = now();//获取当前时间点
    int timeoutMillis = toMillisecondTimeoutDelay(currentTime, nextWakeupTime);//下一次唤醒时间点-当前时间点=线程休眠时间
    mLooper->pollOnce(timeoutMillis);//调用pollOnce使线程休眠,等待回调,唤醒或超时。
	//Looper的pollOnce()的实质就是epoll_wait()。 因此派发线程的休眠在三种情况下可能被唤醒:
	//1.调用Looper::wake()函数主动唤醒(有输入事件注入派发队列中时),
	//2.到达nextWakeupTime的事件点时唤醒
	//3.epoll_waitepoll_wait()监听的fd有epoll_event发生时唤醒(这种唤醒方式将在介绍ANR机制时讨论)。
}

此时我们继续查看dispatchOnceInnerLocked函数

void InputDispatcher::dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) {
    nsecs_t currentTime = now();

	//此时不为空,此时mPendingEvent是之前key按下事件,在上一次返回flse时候,没有释放此事件
    if (! mPendingEvent) {//如果没有待发送的事件,则从mInboundQueue队列中取出一个事件,进行分发
    }

    bool done = false;
    DropReason dropReason = DROP_REASON_NOT_DROPPED;//默认丢弃原因为不丢弃

    switch (mPendingEvent->type) {

    case EventEntry::TYPE_KEY: {//按键类型
        KeyEntry* typedEntry = static_cast<KeyEntry*>(mPendingEvent);
		
        done = dispatchKeyLocked(currentTime, typedEntry, &dropReason, nextWakeupTime);//进一步分发按键事件,结果赋值给 done
          // 无论是成功派发还是事件被丢弃,都返回 true,当要询问key派发策略或者此事件没有处理时是false
        break;
    }

    if (done) {
        if (dropReason != DROP_REASON_NOT_DROPPED) {
            dropInboundEventLocked(mPendingEvent, dropReason);
        }
        mLastDropReason = dropReason;

        releasePendingEventLocked();
        *nextWakeupTime = LONG_LONG_MIN;  // force next poll to wake up immediately
    }
}

 2.8 InputDispatcher::dispatchKeyLocked

此函数的主要作用是:

1.通过findFocusedWindowTargetsLocked从保存窗口信息的列表中,找到合适的派发窗口。

2.dispatchEventLocked向指定窗口派发按键按下的事件。

bool InputDispatcher::dispatchKeyLocked(nsecs_t currentTime, KeyEntry* entry,
        DropReason* dropReason, nsecs_t* nextWakeupTime) {
    // Preprocessing.
    if (! entry->dispatchInProgress) {//此时dispatchInProgress为true
    }
    
    Vector<InputTarget> inputTargets;//inputTargets是一个容器,会存储目标窗口的信息,根据key事件的类型,寻找合适的目标窗口。
	//其返回值injectionResult指明寻找结果,而找到的合适的目标窗口信息将被保存在inputTargets列表中
    int32_t injectionResult = findFocusedWindowTargetsLocked(currentTime,
            entry, inputTargets, nextWakeupTime);

    setInjectionResultLocked(entry, injectionResult);//将injectionResult结果赋值到event内部的一个属性中

    ///addMonitoringTargetsLocked(inputTargets);//默认mMonitoringChannels是false

    // Dispatch the key.
    dispatchEventLocked(currentTime, entry, inputTargets);
    return true;
}

 2.9 InputDispatcher::findFocusedWindowTargetsLocked

按键的派发相较于motion事件的派发比较简单,按键事件的派发只需要派发给获取焦点的应用程序即可,获取焦点的应用程序即:用户正在交互使用的应用程序。

此函数的作用是:

1.检查目标窗口的权限和是否已经准备就绪。

2.当窗口准备好了以后,addWindowTargetLocked会将焦点窗口的相关信息保存到inputTargets中。

那么此时读者会疑问,焦点窗口是什么时候被设置到input中的,此篇仅简单介绍一下:

当窗口发生变化时,WMS会调用InputMonitor类的updateInputWindowsLw函数,而InputMonitor类是IMS的回调类,于是便通过jni更新窗口信息到InputDispatcher中。

int32_t InputDispatcher::findFocusedWindowTargetsLocked(nsecs_t currentTime,
        const EventEntry* entry, Vector<InputTarget>& inputTargets, nsecs_t* nextWakeupTime) {
    int32_t injectionResult;
    std::string reason;


    // 如果有焦点窗口,则检查权限,权限检查失败,则
    if (! checkInjectionPermission(mFocusedWindowHandle, entry->injectionState)) {
        injectionResult = INPUT_EVENT_INJECTION_PERMISSION_DENIED;
        goto Failed;
    }

    // 检查窗口是否准备好进行更多输入事件
    reason = checkWindowReadyForMoreInputLocked(currentTime,
            mFocusedWindowHandle, entry, "focused");
    if (!reason.empty()) {//如果reason不为空,则可能发生了anr,用handleTargetsNotReadyLocked去判断
        injectionResult = handleTargetsNotReadyLocked(currentTime, entry,
                mFocusedApplicationHandle, mFocusedWindowHandle, nextWakeupTime, reason.c_str());
        goto Unresponsive;
    }

    //代表此时有焦点窗口,并且权限通过,并且准备好接受输入事件
    injectionResult = INPUT_EVENT_INJECTION_SUCCEEDED;//给flag赋值
    addWindowTargetLocked(mFocusedWindowHandle,
            InputTarget::FLAG_FOREGROUND | InputTarget::FLAG_DISPATCH_AS_IS, BitSet32(0),
            inputTargets);//addWindowTargetLocked 函数将接收按键事件的目标窗口的一些信息保存到 inputTargets容器中
			//参数分析:
			//mFocusedWindowHandle是焦点窗口
			//InputTarget::FLAG_FOREGROUND | InputTarget::FLAG_DISPATCH_AS_IS代表,是前台窗口和必须原样发送事件
			//0
			//inputTargets保存目标窗口的信息的容器,此时还是空
		

    // Done.
Failed:
Unresponsive:
    nsecs_t timeSpentWaitingForApplication = getTimeSpentWaitingForApplicationLocked(currentTime);
	//timeSpentWaitingForApplication是等待应用程序花费的时间
    updateDispatchStatisticsLocked(currentTime, entry,
            injectionResult, timeSpentWaitingForApplication);

    return injectionResult;
}
bool InputDispatcher::checkInjectionPermission(const sp<InputWindowHandle>& windowHandle,
        const InjectionState* injectionState) {
	/*		
    if (injectionState//injectionState代表是否是从IMS注入的事件,默认物理按键是null
            && (windowHandle == NULL
                    || windowHandle->getInfo()->ownerUid != injectionState->injectorUid)
            && !hasInjectionPermission(injectionState->injectorPid, injectionState->injectorUid)) {
        if (windowHandle != NULL) {
            ALOGW("Permission denied: injecting event from pid %d uid %d to window %s "
                    "owned by uid %d",
                    injectionState->injectorPid, injectionState->injectorUid,
                    windowHandle->getName().c_str(),
                    windowHandle->getInfo()->ownerUid);
        } else {
            ALOGW("Permission denied: injecting event from pid %d uid %d",
                    injectionState->injectorPid, injectionState->injectorUid);
        }
        return false;
    }
	*/
    return true;
}
//从windowHandle的句柄中获取getInfo,然后将其inputchannel等信息生成InputTarget类对象,然后保存到inputTargets容器中
void InputDispatcher::addWindowTargetLocked(const sp<InputWindowHandle>& windowHandle,
        int32_t targetFlags, BitSet32 pointerIds, Vector<InputTarget>& inputTargets) {
    inputTargets.push();

    const InputWindowInfo* windowInfo = windowHandle->getInfo();
    InputTarget& target = inputTargets.editTop();
    target.inputChannel = windowInfo->inputChannel;//获取发送窗口的通道
    target.flags = targetFlags;//此时按键的flag是InputTarget::FLAG_FOREGROUND | InputTarget::FLAG_DISPATCH_AS_IS
    target.xOffset = - windowInfo->frameLeft;//窗口坐标的左
    target.yOffset = - windowInfo->frameTop;窗口坐标的顶部
    target.scaleFactor = windowInfo->scaleFactor;//窗口的缩放系数
    target.pointerIds = pointerIds;//pointerIds是0
}

2.10 dispatchEventLocked

void InputDispatcher::dispatchEventLocked(nsecs_t currentTime,EventEntry* eventEntry, const Vector<InputTarget>& inputTargets) {

    pokeUserActivityLocked(eventEntry);//保持屏幕唤醒

    for (size_t i = 0; i < inputTargets.size(); i++) {//此时inputTargets中只有一个焦点窗口
        const InputTarget& inputTarget = inputTargets.itemAt(i);

        ssize_t connectionIndex = getConnectionIndexLocked(inputTarget.inputChannel);//此函数的作用是检查此窗口的inputchannel是否已注册,
		//如果mConnectionsByFd中存在对应的connection,则返回索引
        if (connectionIndex >= 0) {
            sp<Connection> connection = mConnectionsByFd.valueAt(connectionIndex);//获取此窗口的connection类的对象
            prepareDispatchCycleLocked(currentTime, connection, eventEntry, &inputTarget);
        }
    }
}

 2.11 prepareDispatchCycleLocked

void InputDispatcher::prepareDispatchCycleLocked(nsecs_t currentTime,const sp<Connection> &connection, EventEntry *eventEntry, const InputTarget *inputTarget)
{

        //不分割,按照原样事件进行入队分发
        enqueueDispatchEntriesLocked(currentTime, connection, eventEntry, inputTarget);
}

 2.12 enqueueDispatchEntriesLocked

void InputDispatcher::enqueueDispatchEntriesLocked(nsecs_t currentTime,const sp<Connection> &connection, EventEntry *eventEntry, const InputTarget *inputTarget)
{
        bool wasEmpty = connection->outboundQueue.isEmpty();//此时是空

        // 调用了六次 enqueueDispatchEntryLocked 函数,仅仅最后一个参数不同,但不是执行六次,
		//而是根据inputTarget的flags和最后一个参数是否相等,如果相等则处理,不相等会返回。
		/**
        enqueueDispatchEntryLocked(connection, eventEntry, inputTarget,
                                   InputTarget::FLAG_DISPATCH_AS_HOVER_EXIT);
        enqueueDispatchEntryLocked(connection, eventEntry, inputTarget,
                                   InputTarget::FLAG_DISPATCH_AS_OUTSIDE);
        enqueueDispatchEntryLocked(connection, eventEntry, inputTarget,
                                   InputTarget::FLAG_DISPATCH_AS_HOVER_ENTER);
		*/
        enqueueDispatchEntryLocked(connection, eventEntry, inputTarget,InputTarget::FLAG_DISPATCH_AS_IS);
		//key事件的flags是InputTarget::FLAG_FOREGROUND | InputTarget::FLAG_DISPATCH_AS_IS,
		//如果是key事件,则只有这个函数执行了,此函数看名字感觉是将entry入队
		/**
        enqueueDispatchEntryLocked(connection, eventEntry, inputTarget,
                                   InputTarget::FLAG_DISPATCH_AS_SLIPPERY_EXIT);
        enqueueDispatchEntryLocked(connection, eventEntry, inputTarget,
                                   InputTarget::FLAG_DISPATCH_AS_SLIPPERY_ENTER);
		*/

        // 当原先的 outbound 队列为空, 且当前 outbound 不为空的情况执行,开始循环分发
        if (wasEmpty && !connection->outboundQueue.isEmpty())
        {
            startDispatchCycleLocked(currentTime, connection);
        }
}

2.13  enqueueDispatchEntryLocked

此函数的作用是:

1.将EventEntry 保存到 DispatchEntry中。

2.判断按键是否是抬起按键,如果按键是抬起按键,则判断之前此按键是否按下过,如果没有,则删除此抬起按键。

3.将DispatchEntry按键事件放入outboundQueue的队列中。

void InputDispatcher::enqueueDispatchEntryLocked(const sp<Connection> &connection, EventEntry *eventEntry, const InputTarget *inputTarget,int32_t dispatchMode)
{
        int32_t inputTargetFlags = inputTarget->flags;//此时的获取的flag是InputTarget::FLAG_FOREGROUND | InputTarget::FLAG_DISPATCH_AS_IS
        inputTargetFlags = (inputTargetFlags & ~InputTarget::FLAG_DISPATCH_MASK) | dispatchMode;
		//FLAG_DISPATCH_MASK是所有的flag集合,dispatchMode此时传入的是原样发送

		//这是一个新的事件。
		//将 EventEntry 转换为 DispatchEntry, 在后面会加入 connection 的 outbound 队列
        DispatchEntry *dispatchEntry = new DispatchEntry(eventEntry, 
                                                         inputTargetFlags, inputTarget->xOffset, inputTarget->yOffset,
                                                         inputTarget->scaleFactor);

        //应用target标志并更新连接的输入状态。
        switch (eventEntry->type)
        {
        case EventEntry::TYPE_KEY:
        {
            KeyEntry *keyEntry = static_cast<KeyEntry *>(eventEntry);
            dispatchEntry->resolvedAction = keyEntry->action;//从keyEntry中设置其action和flag
            dispatchEntry->resolvedFlags = keyEntry->flags;
			//此函数的作用主要是检查一个抬起的按键之前是否有按下过,和将按下的按键添加到一个容器中
			//返回值为ture,代表是抬起的按键,之前有按下过。返回false,则代表up按键无按下过,不一致的事件,则删除
            if (!connection->inputState.trackKey(keyEntry,dispatchEntry->resolvedAction, dispatchEntry->resolvedFlags))
            {
#if DEBUG_DISPATCH_CYCLE
                ALOGD("channel '%s' ~ enqueueDispatchEntryLocked: skipping inconsistent key event",
                      connection->getInputChannelName().c_str());
#endif
                delete dispatchEntry;
                return; // skip the inconsistent event
            }
            break;
        }
        }

        // 我们正在等待此dispatchEntry事件的分发完成
        if (dispatchEntry->hasForegroundTarget())//是要发送到前台,所以是执行的如果此事件是要发送到前台窗口
        {
            incrementPendingForegroundDispatchesLocked(eventEntry);//增加未处理的前台分发数量
        }

        // 将dispatchEntry事件放入connection对象的outboundQueue序列的队尾
        connection->outboundQueue.enqueueAtTail(dispatchEntry);
        traceOutboundQueueLengthLocked(connection);//追踪队列的长度
}
InputDispatcher::DispatchEntry::DispatchEntry(EventEntry* eventEntry,
        int32_t targetFlags, float xOffset, float yOffset, float scaleFactor) :
        seq(nextSeq()),//nextSeq是一个静态函数,里面有一个int值的静态变量,每构造一个DispatchEntry对象,此值就加1,然后赋值给seq
        eventEntry(eventEntry),//保存的EventEntry对象
		targetFlags(targetFlags),//发送的flag
        xOffset(xOffset), //目标窗口的x偏移量
		yOffset(yOffset), //目标窗口的y偏移量
		scaleFactor(scaleFactor),//缩放比例
        deliveryTime(0), 
		resolvedAction(0), 
		resolvedFlags(0) 
		{
			eventEntry->refCount += 1;
		}
bool InputDispatcher::InputState::trackKey(const KeyEntry* entry,
        int32_t action, int32_t flags) {
    switch (action) {
	/**
    case AKEY_EVENT_ACTION_UP: {
        if (entry->flags & AKEY_EVENT_FLAG_FALLBACK) {
            for (size_t i = 0; i < mFallbackKeys.size(); ) {
                if (mFallbackKeys.valueAt(i) == entry->keyCode) {
                    mFallbackKeys.removeItemsAt(i);
                } else {
                    i += 1;
                }
            }
        }
        ssize_t index = findKeyMemento(entry);//如果从容器中找到此案件之前按下的事件,则移除按下的事件,并返回ture
        if (index >= 0) {
            mKeyMementos.removeAt(index);//mKeyMementos存储了所有按下还未抬起的key事件
            return true;
        }
        //我们不能只放弃按键事件,因为这会阻止创建弹出窗口,当按键被按下时,弹出窗口会自动显示,然后在按键被释放时被关闭。
		//问题是弹出窗口将不会收到原始的向下键,因此向上键将被视为与其观察到的状态不一致。
		//我们也许可以通过合成一个向下键来处理这个问题,但这会导致其他问题。
		//因此,现在,允许分发不一致的按键up事件。

#if DEBUG_OUTBOUND_EVENT_DETAILS
        ALOGD("Dropping inconsistent key up event: deviceId=%d, source=%08x, "
                "keyCode=%d, scanCode=%d",
                entry->deviceId, entry->source, entry->keyCode, entry->scanCode);
#endif
        return false;
        
        return true;
    }
	*/

    case AKEY_EVENT_ACTION_DOWN: {//如果按键是按下,则用findKeyMemento查找是否已经有此按键按下的事件,如果有,
	//则表示产生了连续两个按下的事件,故移除上一个按下的按键,并通过addKeyMemento函数将这次按下的按键保存到容器中
        ssize_t index = findKeyMemento(entry);
        if (index >= 0) {
            mKeyMementos.removeAt(index);
        }
        addKeyMemento(entry, flags);
        return true;
    }

    default:
        return true;
    }
}
void InputDispatcher::InputState::addKeyMemento(const KeyEntry* entry, int32_t flags) {
    mKeyMementos.push();
    KeyMemento& memento = mKeyMementos.editTop();
    memento.deviceId = entry->deviceId;
    memento.source = entry->source;
    memento.keyCode = entry->keyCode;
    memento.scanCode = entry->scanCode;
    memento.metaState = entry->metaState;
    memento.flags = flags;
    memento.downTime = entry->downTime;
    memento.policyFlags = entry->policyFlags;
}
inline bool hasForegroundTarget() const {
            return targetFlags & InputTarget::FLAG_FOREGROUND;
}
void InputDispatcher::incrementPendingForegroundDispatchesLocked(EventEntry* entry) {
    InjectionState* injectionState = entry->injectionState;
	/*
    if (injectionState) {//默认是null
        injectionState->pendingForegroundDispatches += 1;//pendingForegroundDispatches默认值是0,表示正在进行的前台分发的数量
    }*/
}

 2.14 startDispatchCycleLocked

void InputDispatcher::startDispatchCycleLocked(nsecs_t currentTime,const sp<Connection>& connection) {
	//当connection的状态正常且outboundQueue不为空时
    while (connection->status == Connection::STATUS_NORMAL
            && !connection->outboundQueue.isEmpty()) {
        DispatchEntry* dispatchEntry = connection->outboundQueue.head;//取出队列头部的dispatchEntry事件
        dispatchEntry->deliveryTime = currentTime;//完成发送的时间

        // Publish the event.
        status_t status;
        EventEntry* eventEntry = dispatchEntry->eventEntry;//取出EventEntry类型的事件
        switch (eventEntry->type) {
        case EventEntry::TYPE_KEY: {
            KeyEntry* keyEntry = static_cast<KeyEntry*>(eventEntry);

            // Publish the key event.
            status = connection->inputPublisher.publishKeyEvent(dispatchEntry->seq,//DispatchEntry是一个链表,seq表示发送事件的序列号。
                    keyEntry->deviceId, keyEntry->source,
                    dispatchEntry->resolvedAction, dispatchEntry->resolvedFlags,
                    keyEntry->keyCode, keyEntry->scanCode,
                    keyEntry->metaState, keyEntry->repeatCount, keyEntry->downTime,
                    keyEntry->eventTime);
					//调用了Connection类中的InputPublisher类对象的publishKeyEvent方法。
					//参数分析:
					//dispatchEntry->seq,请求序列,seq表示发送事件的序列号。
					//keyEntry->deviceId,设备id
					//keyEntry->source,是KeyboardInputMapper初始化时赋值的,
					//其值是INPUT_DEVICE_CLASS_KEYBOARD,INPUT_DEVICE_CLASS_DPAD从,INPUT_DEVICE_CLASS_GAMEPAD的某一个
					//dispatchEntry->resolvedAction=keyEntry->action,表示按下或者抬起
					//dispatchEntry->resolvedFlags,表示flag策略,此时应该是POLICY_FLAG_PASS_TO_USER
					//keyEntry->keyCode,对应的按键码
					//keyEntry->scanCode,按键码对应的扫描码
            break;
        }
        }

        // 检查发送的结果
        if (status) {
            if (status == WOULD_BLOCK) {
                if (connection->waitQueue.isEmpty()) {
                    ALOGE("channel '%s' ~ Could not publish event because the pipe is full. "
                            "This is unexpected because the wait queue is empty, so the pipe "
                            "should be empty and we shouldn't have any problems writing an "
                            "event to it, status=%d", connection->getInputChannelName().c_str(),
                            status);
                    abortBrokenDispatchCycleLocked(currentTime, connection, true /*notify*/);
                } else {
                    // Pipe is full and we are waiting for the app to finish process some events
                    // before sending more events to it.

                    connection->inputPublisherBlocked = true;
                }
            } else {
                ALOGE("channel '%s' ~ Could not publish event due to an unexpected error, "
                        "status=%d", connection->getInputChannelName().c_str(), status);
                abortBrokenDispatchCycleLocked(currentTime, connection, true /*notify*/);
            }
            return;
        }

        // Re-enqueue the event on the wait queue.
        connection->outboundQueue.dequeue(dispatchEntry);//发送完成后,将此dispatchEntry退出outboundQueue队列
        traceOutboundQueueLengthLocked(connection);//追踪outboundQueue队列长度
        connection->waitQueue.enqueueAtTail(dispatchEntry);//将此dispatchEntry入队到waitQueue队列中
        traceWaitQueueLengthLocked(connection);//追踪waitQueue队列的长度
    }
}

2.15 InputPublisher::publishKeyEvent

主要作用是:

1.将消息封装成InputMessage,然后通过InputChannel发送消息。

status_t InputPublisher::publishKeyEvent(
        uint32_t seq,
        int32_t deviceId,
        int32_t source,
        int32_t action,
        int32_t flags,
        int32_t keyCode,
        int32_t scanCode,
        int32_t metaState,
        int32_t repeatCount,
        nsecs_t downTime,
        nsecs_t eventTime) {
    if (!seq) {
        ALOGE("Attempted to publish a key event with sequence number 0.");
        return BAD_VALUE;
    }

    InputMessage msg;
    msg.header.type = InputMessage::TYPE_KEY;//表示按键事件
    msg.body.key.seq = seq;//消息的序列号
    msg.body.key.deviceId = deviceId;//设备id
    msg.body.key.source = source;//表示键盘等
    msg.body.key.action = action;//按下或者抬起
    msg.body.key.flags = flags;//表示policy,发送给应用
    msg.body.key.keyCode = keyCode;
    msg.body.key.scanCode = scanCode;
    msg.body.key.metaState = metaState;//控制键的状态
    msg.body.key.repeatCount = repeatCount;
    msg.body.key.downTime = downTime;
    msg.body.key.eventTime = eventTime;
    return mChannel->sendMessage(&msg);
}

 2.16 InputChannel::sendMessage

此处便是真正调用socket send接口发送消息的地方。

status_t InputChannel::sendMessage(const InputMessage* msg) {
    const size_t msgLength = msg->size();
    InputMessage cleanMsg;
    msg->getSanitizedCopy(&cleanMsg);//InputMessage字段之间可能有非零字节。
	//强制将整个内存初始化为零,然后仅在每个字段的基础上复制有效字节。
    ssize_t nWrite;
    do {
        nWrite = ::send(mFd, &cleanMsg, msgLength, MSG_DONTWAIT | MSG_NOSIGNAL); 
    } while (nWrite == -1 && errno == EINTR);//如果出错,则一直循环发送等到其成功为止
	//函数说明:send()用来将数据由指定的socket 传给对方主机. 
	//参数s 为已建立好连接的socket. 
	//参数msg 指向发送的数据内容. 
	//参数len 则为数据长度. 
	//参数flags 一般设0, 其他数值定义如下: 
	//MSG_OOB 传送的数据以out-of-band 送出. 
	//MSG_DONTROUTE 取消路由表查询 
	//MSG_DONTWAIT 设置为不可阻断运作 
	//MSG_NOSIGNAL 此动作不愿被SIGPIPE 信号中断. 
	//返回值:成功则返回实际传送出去的字符数, 失败返回-1. 错误原因存于errno.
	//当 mFd 写入消息后,此时会唤醒处于 epoll_wait 状态的应用进程的 UI 线程
    if (nWrite < 0) {//如果发送失败
        int error = errno;
        if (error == EAGAIN || error == EWOULDBLOCK) {
            return WOULD_BLOCK;
        }
        if (error == EPIPE || error == ENOTCONN || error == ECONNREFUSED || error == ECONNRESET) {
            return DEAD_OBJECT;
        }
        return -error;
    }

    if (size_t(nWrite) != msgLength) {

        return DEAD_OBJECT;
    }
    return OK;
}
size_t InputMessage::size() const {
    switch (header.type) {
    case TYPE_KEY:
        return sizeof(Header) + body.key.size();
}
}
//InputMessage字段之间可能有非零字节。强制将整个内存初始化为零,然后仅在每个字段的基础上复制有效字节。
void InputMessage::getSanitizedCopy(InputMessage* msg) const {
    memset(msg, 0, sizeof(*msg));//初始化所有字节为0

    // Write the header
    msg->header.type = header.type;

    // Write the body
    switch(header.type) {
        case InputMessage::TYPE_KEY: {
            // uint32_t seq
            msg->body.key.seq = body.key.seq;
            // nsecs_t eventTime
            msg->body.key.eventTime = body.key.eventTime;
            // int32_t deviceId
            msg->body.key.deviceId = body.key.deviceId;
            // int32_t source
            msg->body.key.source = body.key.source;
            // int32_t displayId
            msg->body.key.displayId = body.key.displayId;
            // int32_t action
            msg->body.key.action = body.key.action;
            // int32_t flags
            msg->body.key.flags = body.key.flags;
            // int32_t keyCode
            msg->body.key.keyCode = body.key.keyCode;
            // int32_t scanCode
            msg->body.key.scanCode = body.key.scanCode;
            // int32_t metaState
            msg->body.key.metaState = body.key.metaState;
            // int32_t repeatCount
            msg->body.key.repeatCount = body.key.repeatCount;
            // nsecs_t downTime
            msg->body.key.downTime = body.key.downTime;
            break;
        }
        default: {
            LOG_FATAL("Unexpected message type %i", header.type);
            break;
        }
    }
}

此处涉及inputchannel的创建过程。

大致讲解一下:

1.应用程序app一定会有activity,然后在打开app时会调用Activity.onCreate,然后经过AMS,再经过WMS,会调用WindowManagerGlobal.addView(),这个函数里面会创建socket对,一个被注册到注册到 InputDispatcher 中,一个返回给app进程的客户端,app进程会监听此socket,当有事件发送到应用程序时,会回调 InputEventReceiver 对应的 native 层对象 NativeInputEventReceiver 的 handleEvent 函数。

此时,我们就通过socket发送按键消息到达了应用端,下一篇android 9】【input】【9.发送按键事件3——Inputchannel的创建过程】我们主要介绍一下应用端是在什么时候创建的sokcet进行的通信。然后在【android 9】【input】【10.发送按键事件4——view的处理流程】,在这一篇主要介绍一下应用层的处理流程。


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

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

相关文章

从零开始手把手Vue3+TypeScript+ElementPlus管理后台项目实战四(引入Axios,并调用第一个接口)

RealWorld接口综述 本项目调用的是RealWorld项目的开放接口。 接口文档如下&#xff1a; https://main--realworld-docs.netlify.app/docs/specs/backend-specs/endpoints https://main--realworld-docs.netlify.app/docs/specs/frontend-specs/swagger RealWorld 是一个适…

GAT1399协议分析(10)--单图像删除

一、官方接口 由于批量删除的接口,图像只能单独删除。 二、wireshark实例 这个接口比较简单,调用request delete即可 文本化: DELETE /VIID/Images/34078100001190001002012024060513561300065 HTTP/1.1 Host: 10.0.201.56:31400 User-Agent: python-requests/2.32.3 Acc…

推荐ChatGPT4.0——Code Copilot辅助编程、Diagrams: Show Me绘制UML图、上传PDF并阅读分析

5月14日凌晨1点、太平洋时间的上午 10 点&#xff0c;OpenAI的GPT-4o的横空出世&#xff0c;再次巩固了其作为行业颠覆者的地位。GPT-4o的发布不仅仅是一个产品的揭晓&#xff0c;它更像是向世界宣告AI技术已迈入了一个全新的纪元&#xff0c;连OpenAI的领航者萨姆奥特曼也不禁…

Redis客户端界面工具QuickRedis

介绍 QuickRedis 是一款 永久免费 的 Redis 可视化管理工具。它支持直连、哨兵、集群模式&#xff0c;支持亿万数量级的 key&#xff0c;还有令人兴奋的 UI。QuickRedis 支持 Windows 、 Mac OS X 和 Linux 下运行。 QuickRedis 是一个效率工具&#xff0c;当别人在努力敲命令…

代码随想录第27天|贪心算法part1

455.分发饼干 先给孩子和饼干排序&#xff0c;每次选取一个最大的饼干给一个最大胃口的孩子&#xff0c;直到饼干分完或者遍历完孩子 class Solution { public:int findContentChildren(vector<int>& g, vector<int>& s) {sort(g.begin(), g.end());sort(…

【vector模拟实现】附加代码讲解

vector模拟实现 一、看源代码简单实现1. push_backcapacity&#xff08;容量&#xff09;sizereserve&#xff08;扩容&#xff09;operator[ ] &#xff08;元素访问&#xff09; 2. pop_back3. itorator&#xff08;迭代器&#xff09;4.insert & erase &#xff08;头插…

逻辑过期解决缓存击穿

我先说一下正常的业务流程&#xff1a;需要查询店铺数据&#xff0c;我们会先从redis中查询&#xff0c;判断是否能命中&#xff0c;若命中说明redis中有需要的数据就直接返回&#xff1b;没有命中就需要去mysql数据库查询&#xff0c;在数据库中查到了就返回数据并把该数据存入…

设置密码重要性!美国一配件制造商因忘设密码影响50 多万客户

1、Cox Biz 身份验证绕过漏洞使数百万台设备暴露于接管 美国一家领先宽带提供商cox的基础架构中存在 API 授权绕过漏洞&#xff0c;如果被利用攻击者不仅可以访问企业客户的个人身份信息 &#xff08;PII&#xff09;&#xff0c;还可以访问 Wi-Fi 密码和连接设备上的信息&…

代码随想录算法训练营Day17|404.左叶子之和 110.平衡二叉树 222.完全二叉树的节点个数

404.左叶子之和 1、这道题需要统计出所有左叶子结点的值的和&#xff0c;首先要明确左叶子节点指的左右孩子节点均为null的左节点。如上图就是4和6. 2.但是光凭叶子结点本身是无法判定左叶子的&#xff0c;因为左右孩子都是null&#xff0c;所以要从上一层节点往下判定。所以判…

华为RH2288H V3服务器iBMC的SSL证书续期

本文对华为RH2288H V3服务器iBMC的SSL证书续期&#xff0c;以避名登录告警提示及主机状态异常。 一、检查现网服务器iBMC的SSL证书到期时间 登录iBMC&#xff0c;点击配置--SSL证书&#xff0c;如下&#xff1a; 可以看到本服务器SSL证书将于今年7月22日到期。 二、联系厂家…

ui自动化中,鼠标操作

from selenium.webdriver import ActionChainsaction ActionChains(driver) # 然后把driver作为参数&#xff0c;实例化一个action对象 练习地址&#xff1a;https://sahitest.com/demo/ 悬停例子&#xff08;百度首页&#xff09; action.move_to_element(ele).perfor…

Cognita RAG:模块化、易用与可扩展的开源框架

Cognita RAG是一个开源框架&#xff0c;它通过模块化设计、用户友好的界面和可扩展性&#xff0c;简化了将领域特定知识整合到通用预训练语言模型中的过程。本文介绍了Cognita的特点、优势、应用场景以及如何帮助开发者构建适合生产环境的RAG应用程序。 文章目录 Cognita RAG介…

HTML基本元素包含HTML表单验证

可将以下代码复制另存为一个HTML文件浏览器打开自己去看看实际使用效果 <!DOCTYPE html> <html> <head> <meta charset"utf-8"><title>测试</title> </head> <body> <h1>很多事</h1> <h1><b&…

【问题分析】WMS无焦点窗口的ANR问题 + transientLaunch介绍【Android 14】

问题描述 Monkey跑出的Camera发生ANR的问题&#xff0c;其实跟Camera无关&#xff0c;任意一个App都会在此场景下发生ANR&#xff0c;场景涉及到Launcher的RecentsActivity界面&#xff0c;和transientLaunch相关。 1 log分析 看问题发生的场景&#xff1a; 1、Camera App的…

硬件产品经理

边端协调管理平台 主页一&#xff1a;模型管理1.1 边侧模型管理 二&#xff1a;配置管理2.1 终端软件配置管理 三&#xff1a;设备管理3.1 区域位置管理3.2 工控机管理&#xff08;其实就是围绕授权&#xff09;3.3 生产设备管理3.4 设备运行管理 四&#xff1a;数据服务4.1 实…

算法:94. 二叉树的中序遍历

给定一个二叉树的根节点 root &#xff0c;返回 它的 中序 遍历 。 示例 1&#xff1a; 输入&#xff1a;root [1,null,2,3] 输出&#xff1a;[1,3,2]示例 2&#xff1a; 输入&#xff1a;root [] 输出&#xff1a;[]示例 3&#xff1a; 输入&#xff1a;root [1] 输出&am…

智慧商砼搅拌车安监运营管理的创新实践

随着城市化进程的加速&#xff0c;商砼搅拌车作为城市建设的重要设备&#xff0c;其安全管理与运营效率直接关系到工程质量和施工进度。近年来&#xff0c;通过引入先进的4G无线视频智能车载终端套件&#xff0c;我们实现了对商砼搅拌车的高精度定位、实时音视频调度、实时油量…

使用 TinyEngine 低代码引擎实现三方物料集成

本文由体验技术团队 TinyEngine 项目成员炽凌创作&#xff0c;欢迎大家实操体验&#xff0c;本体验内容基于 TinyEngine 低代码引擎提供的环境&#xff0c;介绍了如何通过 TinyEngine 低代码引擎实现三方物料集成&#xff0c;帮助开发者快速开发。 知识背景 1.1 TinyEngine 低…

用互斥锁解决缓存击穿

我先说一下正常的业务流程&#xff1a;需要查询店铺数据&#xff0c;我们会先从redis中查询&#xff0c;判断是否能命中&#xff0c;若命中说明redis中有需要的数据就直接返回&#xff1b;没有命中就需要去mysql数据库查询&#xff0c;在数据库中查到了就返回数据并把该数据存入…

燃烧截稿倒计时,NDSS‘25大会即将召开,你的论文准备好了吗?

燃烧截稿倒计时&#xff01;NDSS25大会即将召开&#xff0c;你的论文准备好了吗&#xff1f; 第32届NDSS25(Network and Distributed System Security Symposium)即网络与分布式系统安全研讨会将于2025年2月23日至28日在加利福尼亚州圣地亚哥举行&#xff01; 作为信息安全领域…