Android11 InputDispatcher 分发事件流程分析

在 Android11 InputReader分析 一文中分析到,InputReader将数据放入iq队列后,唤醒InputDispatcher线程,执行InputDispatcher的dispatchOnce方法

//frameworks\native\services\inputflinger\dispatcher\InputDispatcher.cpp
void InputDispatcher::dispatchOnce() {nsecs_t nextWakeupTime = LONG_LONG_MAX;{ // acquire lockstd::scoped_lock _l(mLock);mDispatcherIsAlive.notify_all();// Run a dispatch loop if there are no pending commands.// The dispatch loop might enqueue commands to run afterwards.if (!haveCommandsLocked()) {//此时mCommandQueue中没有命令dispatchOnceInnerLocked(&nextWakeupTime);//1}// Run all pending commands if there are any.// If any commands were run then force the next poll to wake up immediately.if (runCommandsLockedInterruptible()) {//执行mCommandQueue中的命令nextWakeupTime = LONG_LONG_MIN;}//省略
}

继续调用注释1处的dispatchOnceInnerLocked进行处理

//frameworks\native\services\inputflinger\dispatcher\InputDispatcher.cpp
void InputDispatcher::dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) {//省略if (!mPendingEvent) {//省略}else {// Inbound queue has at least one entry.mPendingEvent = mInboundQueue.front();//1mInboundQueue.pop_front();traceInboundQueueLengthLocked();}// Poke user activity for this event.if (mPendingEvent->policyFlags & POLICY_FLAG_PASS_TO_USER) {pokeUserActivityLocked(*mPendingEvent);//这个方法会向mCommandQueue中放入命令,后面在dispatchOnce中,调用	runCommandsLockedInterruptible执行这个命令,通过JNI调用,调用PowerManagerService的userActivityFromNative方法	}ALOG_ASSERT(mPendingEvent != nullptr);bool done = false;DropReason dropReason = DropReason::NOT_DROPPED;//标记事件是否需要抛弃掉,不传给应用窗口if (!(mPendingEvent->policyFlags & POLICY_FLAG_PASS_TO_USER)) {dropReason = DropReason::POLICY;} else if (!mDispatchEnabled) {dropReason = DropReason::DISABLED;}if (mNextUnblockedEvent == mPendingEvent) {mNextUnblockedEvent = nullptr;}switch (mPendingEvent->type) {//省略case EventEntry::Type::MOTION: {MotionEntry* typedEntry = static_cast<MotionEntry*>(mPendingEvent);if (dropReason == DropReason::NOT_DROPPED && isAppSwitchDue) {dropReason = DropReason::APP_SWITCH;}if (dropReason == DropReason::NOT_DROPPED && isStaleEvent(currentTime, *typedEntry)) {dropReason = DropReason::STALE;}if (dropReason == DropReason::NOT_DROPPED && mNextUnblockedEvent) {dropReason = DropReason::BLOCKED;}done = dispatchMotionLocked(currentTime, typedEntry, &dropReason, nextWakeupTime);//2break;}//省略
|

注释1处,先从iq队列中取出事件,对于触摸事件,调用注释2处的dispatchMotionLocked继续处理

//frameworks\native\services\inputflinger\dispatcher\InputDispatcher.cpp
bool InputDispatcher::dispatchMotionLocked(nsecs_t currentTime, MotionEntry* entry,DropReason* dropReason, nsecs_t* nextWakeupTime) {//省略bool isPointerEvent = entry->source & AINPUT_SOURCE_CLASS_POINTER;if (isPointerEvent) {// Pointer event.  (eg. touchscreen)injectionResult =findTouchedWindowTargetsLocked(currentTime, *entry, inputTargets, nextWakeupTime,&conflictingPointerActions);//1}//省略dispatchEventLocked(currentTime, entry, inputTargets);//2return true;}

注释1处,InputDispatcher需要知道,输入事件应该派发给哪个窗口,所以需要找到目标窗口,将其放入inputTargets中,这个流程在文章的后面再分析。注释2处,查找到目标窗口后,调用dispatchEventLocked继续处理

//frameworks\native\services\inputflinger\dispatcher\InputDispatcher.cpp
void InputDispatcher::dispatchEventLocked(nsecs_t currentTime, EventEntry* eventEntry,const std::vector<InputTarget>& inputTargets) {pokeUserActivityLocked(*eventEntry);for (const InputTarget& inputTarget : inputTargets) {//遍历sp<Connection> connection =getConnectionLocked(inputTarget.inputChannel->getConnectionToken());//根据token取出connection if (connection != nullptr) {prepareDispatchCycleLocked(currentTime, connection, eventEntry, inputTarget);//继续处理} else {if (DEBUG_FOCUS) {ALOGD("Dropping event delivery to target with channel '%s' because it ""is no longer registered with the input dispatcher.",inputTarget.inputChannel->getName().c_str());}}}
}

遍历inputTargets查找到对应的connection ,然后使用prepareDispatchCycleLocked继续处理,在prepareDispatchCycleLocked方法中,对于支持触摸事件分离的窗口,则是直接调用enqueueDispatchEntriesLocked来处理

//frameworks\native\services\inputflinger\dispatcher\InputDispatcher.cpp
void InputDispatcher::enqueueDispatchEntriesLocked(nsecs_t currentTime,const sp<Connection>& connection,EventEntry* eventEntry,bool wasEmpty = connection->outboundQueue.empty();// Enqueue dispatch entries for the requested modes.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);//放入队列enqueueDispatchEntryLocked(connection, eventEntry, inputTarget,InputTarget::FLAG_DISPATCH_AS_SLIPPERY_EXIT);enqueueDispatchEntryLocked(connection, eventEntry, inputTarget,InputTarget::FLAG_DISPATCH_AS_SLIPPERY_ENTER);// If the outbound queue was previously empty, start the dispatch cycle going.if (wasEmpty && !connection->outboundQueue.empty()) {startDispatchCycleLocked(currentTime, connection);//分发}
}

首先是将事件放入oq队列,然后调用startDispatchCycleLocked继续处理
放入队列:

//frameworks\native\services\inputflinger\dispatcher\InputDispatcher.cpp
void InputDispatcher::enqueueDispatchEntryLocked(const sp<Connection>& connection,EventEntry* eventEntry,const InputTarget& inputTarget,int32_t dispatchMode) {//省略// Enqueue the dispatch entry.connection->outboundQueue.push_back(dispatchEntry.release());//放入队列traceOutboundQueueLength(connection);//这里就是能在trace中看到oq的原因}

startDispatchCycleLocked

//frameworks\native\services\inputflinger\dispatcher\InputDispatcher.cpp
void InputDispatcher::startDispatchCycleLocked(nsecs_t currentTime,const sp<Connection>& connection) {//省略while (connection->status == Connection::STATUS_NORMAL && !connection->outboundQueue.empty()) {DispatchEntry* dispatchEntry = connection->outboundQueue.front();//从oq中取出事件dispatchEntry->deliveryTime = currentTime;const nsecs_t timeout =getDispatchingTimeoutLocked(connection->inputChannel->getConnectionToken());dispatchEntry->timeoutTime = currentTime + timeout;// Publish the event.status_t status;EventEntry* eventEntry = dispatchEntry->eventEntry;switch (eventEntry->type) {//省略case EventEntry::Type::MOTION: {// Publish the motion event.status = connection->inputPublisher.publishMotionEvent(dispatchEntry->seq,dispatchEntry->resolvedEventId,motionEntry->deviceId, motionEntry->source,motionEntry->displayId, std::move(hmac),dispatchEntry->resolvedAction,motionEntry->actionButton,dispatchEntry->resolvedFlags,motionEntry->edgeFlags, motionEntry->metaState,motionEntry->buttonState,motionEntry->classification, xScale, yScale,xOffset, yOffset, motionEntry->xPrecision,motionEntry->yPrecision,motionEntry->xCursorPosition,motionEntry->yCursorPosition,motionEntry->downTime, motionEntry->eventTime,motionEntry->pointerCount,motionEntry->pointerProperties, usingCoords);//1reportTouchEventForStatistics(*motionEntry);break;}//省略// Re-enqueue the event on the wait queue.connection->outboundQueue.erase(std::remove(connection->outboundQueue.begin(),connection->outboundQueue.end(),dispatchEntry));traceOutboundQueueLength(connection);connection->waitQueue.push_back(dispatchEntry);//放入wq队列traceWaitQueueLength(connection);//这就是trace中可以看到wq的原因}
}

可以看出,该方法主要是从oq队列中取出事件,然后调用connection->inputPublisher的publishMotionEvent方法继续处理,处理完成后,将事件从oq队列中删除,并又添加到wq队列中。
connection的inputPublisher成员指向的是inputChannel,在Android 11 输入系统之InputDispatcher和应用窗口建立联系一文中分析到,使用inputChannel构造connection对象

//frameworks\native\services\inputflinger\dispatcher\Connection.cpp
Connection::Connection(const sp<InputChannel>& inputChannel, bool monitor,const IdGenerator& idGenerator): status(STATUS_NORMAL),inputChannel(inputChannel),monitor(monitor),inputPublisher(inputChannel),//初始化inputPublisherinputState(idGenerator) {}

继续来看InputChannel的publishMotionEvent方法

//frameworks\native\libs\input\InputTransport.cpp
status_t InputPublisher::publishMotionEvent(uint32_t seq, int32_t eventId, int32_t deviceId, int32_t source, int32_t displayId,std::array<uint8_t, 32> hmac, int32_t action, int32_t actionButton, int32_t flags,int32_t edgeFlags, int32_t metaState, int32_t buttonState,MotionClassification classification, float xScale, float yScale, float xOffset,float yOffset, float xPrecision, float yPrecision, float xCursorPosition,float yCursorPosition, nsecs_t downTime, nsecs_t eventTime, uint32_t pointerCount,const PointerProperties* pointerProperties, const PointerCoords* pointerCoords) {//省略InputMessage msg;msg.header.type = InputMessage::Type::MOTION;msg.body.motion.seq = seq;msg.body.motion.eventId = eventId;msg.body.motion.deviceId = deviceId;msg.body.motion.source = source;msg.body.motion.displayId = displayId;msg.body.motion.hmac = std::move(hmac);msg.body.motion.action = action;msg.body.motion.actionButton = actionButton;msg.body.motion.flags = flags;msg.body.motion.edgeFlags = edgeFlags;msg.body.motion.metaState = metaState;msg.body.motion.buttonState = buttonState;msg.body.motion.classification = classification;msg.body.motion.xScale = xScale;msg.body.motion.yScale = yScale;msg.body.motion.xOffset = xOffset;msg.body.motion.yOffset = yOffset;msg.body.motion.xPrecision = xPrecision;msg.body.motion.yPrecision = yPrecision;msg.body.motion.xCursorPosition = xCursorPosition;msg.body.motion.yCursorPosition = yCursorPosition;msg.body.motion.downTime = downTime;msg.body.motion.eventTime = eventTime;msg.body.motion.pointerCount = pointerCount;for (uint32_t i = 0; i < pointerCount; i++) {msg.body.motion.pointers[i].properties.copyFrom(pointerProperties[i]);msg.body.motion.pointers[i].coords.copyFrom(pointerCoords[i]);}return mChannel->sendMessage(&msg);
}

构建InputMessage ,然后调用其sendMessage方法,将数据发送出去

//frameworks\native\libs\input\InputTransport.cpp
status_t InputChannel::sendMessage(const InputMessage* msg) {const size_t msgLength = msg->size();InputMessage cleanMsg;msg->getSanitizedCopy(&cleanMsg);ssize_t nWrite;do {nWrite = ::send(mFd.get(), &cleanMsg, msgLength, MSG_DONTWAIT | MSG_NOSIGNAL);//1} while (nWrite == -1 && errno == EINTR);//省略

注释1处,向fd中写入数据,InputDispatcher就将数据分发出去了。

查找目标窗口的过程

前面提到,InputDispatcher通过findTouchedWindowTargetsLocked方法,来查找到目标窗口,从而决定事件应该分发给谁

//frameworks\native\services\inputflinger\dispatcher\InputDispatcher.cpp
int32_t InputDispatcher::findTouchedWindowTargetsLocked(nsecs_t currentTime,const MotionEntry& entry,std::vector<InputTarget>& inputTargets,nsecs_t* nextWakeupTime,bool* outConflictingPointerActions) {//省略bool newGesture = (maskedAction == AMOTION_EVENT_ACTION_DOWN ||maskedAction == AMOTION_EVENT_ACTION_SCROLL || isHoverAction);//有新手指触摸const bool isFromMouse = entry.source == AINPUT_SOURCE_MOUSE;bool wrongDevice = false;if (newGesture) {//省略tempTouchState.reset();//先清空tempTouchState,然后重新赋值tempTouchState.down = down;tempTouchState.deviceId = entry.deviceId;tempTouchState.source = entry.source;tempTouchState.displayId = displayId;isSplit = false;}//省略if (newGesture || (isSplit && maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN)) {//省略sp<InputWindowHandle> newTouchedWindowHandle =findTouchedWindowAtLocked(displayId, x, y, &tempTouchState,isDown /*addOutsideTargets*/, true /*addPortalWindows*/);//1//省略if (newTouchedWindowHandle != nullptr) {// Set target flags.int32_t targetFlags = InputTarget::FLAG_FOREGROUND | InputTarget::FLAG_DISPATCH_AS_IS;if (isSplit) {targetFlags |= InputTarget::FLAG_SPLIT;}if (isWindowObscuredAtPointLocked(newTouchedWindowHandle, x, y)) {targetFlags |= InputTarget::FLAG_WINDOW_IS_OBSCURED;} else if (isWindowObscuredLocked(newTouchedWindowHandle)) {targetFlags |= InputTarget::FLAG_WINDOW_IS_PARTIALLY_OBSCURED;}//省略tempTouchState.addOrUpdateWindow(newTouchedWindowHandle, targetFlags, pointerIds);//2}//省略for (const TouchedWindow& touchedWindow : tempTouchState.windows) {//3addWindowTargetLocked(touchedWindow.windowHandle, touchedWindow.targetFlags,touchedWindow.pointerIds, inputTargets);}}
}

注释1处通过findTouchedWindowAtLocked方法查找到InputWindowHandle,注释2处将改InputWindowHandle添加到TouchedWindow中,并添加到tempTouchState的windows集合。上面省略了部分代码,还有其他的满足条件的InputWindowHandle,也会添加进来。注释3处遍历tempTouchState的windows,取出其中的TouchedWindow调用addWindowTargetLocked,来填充inputTargets集合。

先来看看findTouchedWindowAtLocked方法是怎么查找到符合条件的InputWindowHandle

//frameworks\native\services\inputflinger\dispatcher\InputDispatcher.cpp
sp<InputWindowHandle> InputDispatcher::findTouchedWindowAtLocked(int32_t displayId, int32_t x,int32_t y, TouchState* touchState,bool addOutsideTargets,bool addPortalWindows) {//省略// Traverse windows from front to back to find touched window.const std::vector<sp<InputWindowHandle>> windowHandles = getWindowHandlesLocked(displayId);//1for (const sp<InputWindowHandle>& windowHandle : windowHandles) {//2const InputWindowInfo* windowInfo = windowHandle->getInfo();if (windowInfo->displayId == displayId) {int32_t flags = windowInfo->layoutParamsFlags;if (windowInfo->visible) {if (!(flags & InputWindowInfo::FLAG_NOT_TOUCHABLE)) {bool isTouchModal = (flags &(InputWindowInfo::FLAG_NOT_FOCUSABLE |InputWindowInfo::FLAG_NOT_TOUCH_MODAL)) == 0;if (isTouchModal || windowInfo->touchableRegionContainsPoint(x, y)) {int32_t portalToDisplayId = windowInfo->portalToDisplayId;//省略return windowHandle;//省略

注释1处返回的InputWindowHandle集合是根据displayID从mWindowHandlesByDisplay中取出来的,mWindowHandlesByDisplay中的元素是SurfaceFlinger中,调用 updateInputWindowInfo方法,最后调用到InputDispatcher的updateWindowHandlesForDisplayLocked添加的。
注释2处遍历前面得到的InputWindowHandle集合,判断触摸的区域是否是在该InputWindowHandle,如果是,则返回该InputWindowHandle。

查找到InputWindowHandle后,就会将其加入到tempTouchState的windows集合中

//frameworks\native\services\inputflinger\dispatcher\TouchState.cpp
void TouchState::addOrUpdateWindow(const sp<InputWindowHandle>& windowHandle, int32_t targetFlags,BitSet32 pointerIds) {//省略TouchedWindow touchedWindow;//构建TouchedWindowtouchedWindow.windowHandle = windowHandle;touchedWindow.targetFlags = targetFlags;touchedWindow.pointerIds = pointerIds;windows.push_back(touchedWindow);//添加进windows集合
}

最后就是遍历windows集合,根据InputWindowHandle信息来填充inputTargets集合了

//frameworks\native\services\inputflinger\dispatcher\InputDispatcher.cpp
void InputDispatcher::addWindowTargetLocked(const sp<InputWindowHandle>& windowHandle,int32_t targetFlags, BitSet32 pointerIds,std::vector<InputTarget>& inputTargets) {//省略if (it == inputTargets.end()) {InputTarget inputTarget;sp<InputChannel> inputChannel = getInputChannelLocked(windowHandle->getToken());//取出inputChannel inputTarget.inputChannel = inputChannel;inputTarget.flags = targetFlags;inputTarget.globalScaleFactor = windowInfo->globalScaleFactor;inputTargets.push_back(inputTarget);//添加进集合it = inputTargets.end() - 1;}//省略
}

首先是根据windowHandle中的token,取出对应的inputChanel,然后根据inputChanel构造inputTarget并放入集合 中。这样inputTargets集合中就包含了一个个的inputTarget,而inputTarget就包含了窗口对应的inputChannel 信息。

总结

InputDispatcher所做的工作就是从iq队列中取出数据,然后找到目标窗口,进而找到目标窗口对应的connect,将数据放入connection的oq队列,后面取出oq队列的数据并将其通过fd发送出去。分发完成后,将数据移至wq队列。
流程图如下
在这里插入图片描述

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

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

相关文章

【js】将一维数组处理成树形数据并且实现模糊查询

项目中由于数据量不大&#xff0c;后台并未做处理&#xff0c;因此前端拿到返回的Table数据需要处理成树形数据再渲染到表格中 原始数据 const dataList[{"id": 44,"seedlingName": "测试2","seedlingType": "测试2",&quo…

一文读懂开源大数据OLAP

企业需要从海量数据中提取有价值的信息&#xff0c;以支持决策制定和提高运营效率&#xff0c;数据已成为企业最宝贵的资产之一。OLAP&#xff08;在线分析处理&#xff09;技术&#xff0c;作为数据仓库解决方案的核心组成部分&#xff0c;提供了一种强大的工具&#xff0c;帮…

java-springboot项目添加swagger2/Knife4j,附注解

文章目录 添加依赖config工作包中新增SwaggerConfig报错注解 环境&#xff1a; jdk1.8 java8 springboot2.6.13 swagger2.9.2 添加依赖 pom.xml <!-- 添加swagger2--><dependency><groupId>io.springfox</groupId><artifactId>springfo…

【C++】list的使用与模拟实现

&#x1f525;个人主页&#xff1a;北辰水墨 &#x1f525;专栏&#xff1a;C学习仓 本节内容我们来讲解list的使用和模拟实现。 本节难点&#xff1a;list迭代器的模拟实现。 一、list的介绍&#xff1a; 列表 列表是一种序列容器&#xff0c;允许在序列的任何位置进行时间复…

基于springboot+mybatis+vue的项目实战之页面参数传递

如图所示&#xff0c;删除操作可以用按钮实现&#xff0c;也可以用超链接来实现。 1、第一种情况&#xff0c;用按钮实现。 html页面相关&#xff1a; <button type"button" click"deleteId(peot.id)">删除</button> <script>new Vue(…

【算法与数据结构】数组

文章目录 前言数组数组的定义数组的基本操作增加元素删除元素修改元素查找元素 C STL 中的数组arrayvector Python3 中的列表访问更改元素值遍历列表检查列表中是否存在某元素增加元素删除元素拷贝列表总结 Python3 列表的常用操作 参考资料写在最后 前言 本系列专注更新基本数…

从0开始Jmeter接口测试实战

在之前的文章中给大家介绍过接口测试文档和接口测试用例示例&#xff0c;本文基于Jmeter工具给大家介绍一下如何实现接口测试用例&#xff1a;包括发起Http请求&#xff0c;绕过登陆&#xff0c;验证响应。JMeter是Apache组织开发的基于Java的压力测试工具。具有开源免费、框架…

Leetcode—2105. 给植物浇水 II【中等】

2024每日刷题&#xff08;131&#xff09; Leetcode—2105. 给植物浇水 II 实现代码 class Solution { public:int minimumRefill(vector<int>& plants, int capacityA, int capacityB) {int size plants.size();int i 0;int j size - 1;int capA capacityA;in…

【Linux】Linux安装JDK

一、卸载Linux自带的JDK #查询已有的JDK rpm -qa | grep jdk ①将查询到的JDK全部卸载掉 #直接复制一整行的JDK名称 yum -y remove java-1.7.0-openjdk-headless-1.7.0.261-2.6.22.2.el7_8.x86_64 ②卸载完第一个后再次查询 ③继续卸载&#xff0c;卸载完成后再次查询 ④查询…

Flask-大体了解介绍

初识Flask Flask是使用 Python编写的Web微框架。Web框架可以让我们不用关心底层的请求响应处理&#xff0c;更方便高效地编写Web程序。 Flask主要有两个依赖&#xff0c;一个是WSGI&#xff08;Web Server Gateway Interface&#xff0c;Web服务器网关接口&#xff09;工具集…

ICode国际青少年编程竞赛- Python-4级训练场-太阳能板1

ICode国际青少年编程竞赛- Python-4级训练场-太阳能板1 1、 Dev.step(3) Dev.turnRight() Dev.step(2) while Dev.energy < 60:wait() Dev.step(-6)2、 Dev.step(7) while Dev.energy < 90:wait() Dev.step(-1) Dev.turnRight() Dev.step(7)3、 Dev.step(4) Dev.turn…

区块链 | NFT 水印:Review on Watermarking Techniques(三)

&#x1f34d;原文&#xff1a;Review on Watermarking Techniques Aiming Authentication of Digital Image Artistic Works Minted as NFTs into Blockchains 一个 NFT 的水印认证协议 可以引入第三方实体来实现对交易的认证&#xff0c;即通过使用 R S A \mathsf{RSA} RSA…

(十)JSP教程——config对象

config对象是脚本程序配置对象&#xff0c;表示当前JSP页面的配置信息。由于JSP页面通常无需配置&#xff0c;因此该对象在JSP页面中比较少见。 config对象可以读取一些初始化参数的值&#xff0c;而这些参数一般在web.xml配置文件中可以看到&#xff0c;并通过config对象的相应…

国内护眼台灯品牌哪些实用?推荐五款物美价廉的台灯品牌

近年来&#xff0c;我们注意到儿童近视的现象呈现出增多且趋于低龄化的趋势。这一变化&#xff0c;部分原因可以归咎于孩子们越来越多地使用电子产品&#xff0c;另一部分则与他们面临的学业压力增加有关。鉴于此&#xff0c;家长们在挑选儿童学习用品时变得格外谨慎&#xff0…

Sqli-labs第五~八关(布尔盲注)

目录 首先找到他们的闭合方式 操作 总结&#xff1a; 第五关根据页面结果得知是字符型但是和前面四关还是不一样是因为页面虽然有东西。但是只有对于请求对错出现不一样页面其余的就没有了。这个时候我们用联合注入就没有用&#xff0c;因为联合注入是需要页面有回显位。如果…

鸿蒙开发接口Ability框架:【@ohos.application.Want (Want)】

Want Want模块提供系统的基本通信组件的能力。 说明&#xff1a; 本模块首批接口从API version 8 开始支持。后续版本的新增接口&#xff0c;采用上角标单独标记接口的起始版本。 导入模块 import Want from ohos.application.Want; 开发前请熟悉鸿蒙开发指导文档&#xff1…

nginx--rewrite

功能 Nginx服务器利用ngx_http_rewrite_module 模块解析和处理理rewrite请求&#xff0c;此功能依靠PCRE(Perl Compatible Regular Expressions)&#xff0c;因此编译之前要安装PCRE库&#xff0c;rewrite是nginx服务器的重要功能之一&#xff0c;用于实现URL的重写&#xff0…

《Video Mamba Suite》论文笔记(4)Mamba在时空建模中的作用

原文翻译 4.4 Mamba for Spatial-Temporal Modeling Tasks and datasets.最后&#xff0c;我们评估了 Mamba 的时空建模能力。与之前的小节类似&#xff0c;我们在 Epic-Kitchens-100 数据集 [13] 上评估模型在zero-shot多实例检索中的性能。 Baseline and competitor.ViViT…

【网络编程】UDP协议和TCP协议1

UDP协议格式 UDP 报文分为 UDP 报头和 UDP 数据区两部分。报头由 4 个 16 位长&#xff08;2字节&#xff09;字段组成&#xff0c;分别说明该报文的源端口、目的端口、报文长度和校验值。 UDP协议如何将报头和有效载荷分离 UDP报头是一种定长报头&#xff0c;长度为8个字节。…

QCC3071/QCC3081/QCC3083/QCC3084/QCC5171/QCC5181/QCC3091/QCC3095平台LDAC解码

QCC3071/QCC3081/QCC3083/QCC3084/QCC5171/QCC5181/QCC3091/QCC3095平台LDAC解码 LDAC Decoder Evaluation Kit for QCC5181 and QCC5171 (The 5181 Kit) 随着Qualcomm DSP向下开放&#xff0c;QCC3071/QCC3081/QCC3083/QCC3084目前可以可以实现LDAC Decoder。 QCC3071/QCC3…