news 2026/4/18 16:03:18

展锐平台:修复使用触控笔主动橡皮擦键功能时, InputDispatcher 发生了致命错误(F 级别),导致 system_server 进程崩溃并重启。

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
展锐平台:修复使用触控笔主动橡皮擦键功能时, InputDispatcher 发生了致命错误(F 级别),导致 system_server 进程崩溃并重启。

接上篇文章:展锐平台:记录一次带触控笔的MIPI屏幕调试踩坑过程,在使用触控笔的主动橡皮擦功能时,系统会出现类似重启的现象。之所以说是类似重启,是因为从log中看,整个系统是还在运行的,只是某些服务重启了。

下面看一下问题log的一些分析以及如何解决。

第一次发现问题时,抓的log关键日志如下:

C0BF041 12-05 11:58:26.324 23715 23715 F DEBUG : *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** C0BF042 12-05 11:58:26.324 23715 23715 F DEBUG : Build fingerprint: 'UNISOC/ums9620_1h10_native/ums9620_1h10:14/UP1A.231005.007/eng.a53sw0.20251205.093503:userdebug/test-keys' C0BF043 12-05 11:58:26.324 23715 23715 F DEBUG : Revision: '0' C0BF044 12-05 11:58:26.324 23715 23715 F DEBUG : ABI: 'arm64' C0BF045 12-05 11:58:26.324 23715 23715 F DEBUG : Timestamp: 2025-12-05 11:58:25.490240661+0800 C0BF046 12-05 11:58:26.324 23715 23715 F DEBUG : Process uptime: 189s C0BF047 12-05 11:58:26.324 23715 23715 F DEBUG : Cmdline: system_server C0BF048 12-05 11:58:26.324 23715 23715 F DEBUG : pid: 19505, tid: 19886, name: InputDispatcher >>> system_server <<< C0BF049 12-05 11:58:26.324 23715 23715 F DEBUG : uid: 1000 C0BF04A 12-05 11:58:26.324 23715 23715 F DEBUG : tagged_addr_ctrl: 0000000000000001 (PR_TAGGED_ADDR_ENABLE) C0BF04B 12-05 11:58:26.325 23715 23715 F DEBUG : signal 6 (SIGABRT), code -1 (SI_QUEUE), fault addr -------- C0BF04C 12-05 11:58:26.325 23715 23715 F DEBUG : Abort message: 'Splitting motion events requires a down time to be set for the target on connection c1a807b com.android.launcher3/com.android.launcher3.uioverrides.QuickstepLauncher (server) for MotionEvent(deviceId=3, eventTime=8512674950000, source=TOUCHSCREEN | STYLUS, displayId=0, action=BUTTON_PRESS, actionButton=0x00000020, flags=0x00000000, metaState=0x00000000, buttonState=0x00000020, classification=NONE, edgeFlags=0x00000000, xPrecision=10.0, yPrecision=10.0, xCursorPosition=nan, yCursorPosition=nan, pointers=[0: (1143.9, 566.0)]), policyFlags=0x62000000' C0BF04D 12-05 11:58:26.325 23715 23715 F DEBUG : x0 0000000000000000 x1 0000000000004dae x2 0000000000000006 x3 000000790c15b5f0 C0BF04E 12-05 11:58:26.325 23715 23715 F DEBUG : x4 626f1f3130221f39 x5 626f1f3130221f39 x6 626f1f3130221f39 x7 7f7f7f7f7f7f7f7f C0BF04F 12-05 11:58:26.325 23715 23715 F DEBUG : x8 00000000000000f0 x9 0000007cb82340b0 x10 0000000000000001 x11 0000007cb828ab60 C0BF050 12-05 11:58:26.325 23715 23715 F DEBUG : x12 000000000000ce3a x13 000000007fffffff x14 0000000000327ef2 x15 000000339dfb2aab C0BF051 12-05 11:58:26.325 23715 23715 F DEBUG : x16 0000007cb8304cf8 x17 0000007cb82d35e0 x18 000000790aefe000 x19 0000000000004c31 C0BF052 12-05 11:58:26.325 23715 23715 F DEBUG : x20 0000000000004dae x21 00000000ffffffff x22 000000790c15c000 x23 00000079f6c54000 C0BF053 12-05 11:58:26.325 23715 23715 F DEBUG : x24 b400007a5902fbd0 x25 b400007b89066e70 x26 b400007a69037918 x27 b400007bf9163888 C0BF054 12-05 11:58:26.325 23715 23715 F DEBUG : x28 000000790c15c000 x29 000000790c15b670 C0BF055 12-05 11:58:26.325 23715 23715 F DEBUG : lr 0000007cb827b178 sp 000000790c15b5d0 pc 0000007cb827b1a4 pst 0000000000001000 C0BF056 12-05 11:58:26.325 23715 23715 F DEBUG : 14 total frames C0BF057 12-05 11:58:26.325 23715 23715 F DEBUG : backtrace: C0BF058 12-05 11:58:26.325 23715 23715 F DEBUG : #00 pc 00000000000691a4 /apex/com.android.runtime/lib64/bionic/libc.so (abort+164) (BuildId: c3622b5c0a4cde0d723ea2d964ec71b7) C0BF059 12-05 11:58:26.325 23715 23715 F DEBUG : #01 pc 000000000064ac20 /apex/com.android.art/lib64/libart.so (art::Runtime::Abort(char const*)+1456) (BuildId: b37c98d000c57dbaf28462133142cc42) C0BF05A 12-05 11:58:26.325 23715 23715 F DEBUG : #02 pc 0000000000044730 /apex/com.android.art/lib64/libbase.so (android::base::SetAborter(std::__1::function<void (char const*)>&&)::$_0::__invoke(char const*)+80) (BuildId: 404dcc4830dee81873a1cc7d0bff3046) C0BF05B 12-05 11:58:26.325 23715 23715 F DEBUG : #03 pc 0000000000024d90 /system/lib64/libbase.so (android::base::LogMessage::~LogMessage()+352) (BuildId: 994836fb17568408113cb3be7a6dfaad) C0BF05C 12-05 11:58:26.325 23715 23715 F DEBUG : #04 pc 00000000000a95a0 /system/lib64/libinputflinger.so (android::inputdispatcher::InputDispatcher::prepareDispatchCycleLocked(long, std::__1::shared_ptr<android::inputdispatcher::Connection> const&, std::__1::shared_ptr<android::inputdispatcher::EventEntry>, android::inputdispatcher::InputTarget const&)+2160) (BuildId: 85a971c1a495be3c4e4e6bf83197b4a7) C0BF05D 12-05 11:58:26.325 23715 23715 F DEBUG : #05 pc 00000000000a42b0 /system/lib64/libinputflinger.so (android::inputdispatcher::InputDispatcher::dispatchEventLocked(long, std::__1::shared_ptr<android::inputdispatcher::EventEntry>, std::__1::vector<android::inputdispatcher::InputTarget, std::__1::allocator<android::inputdispatcher::InputTarget> > const&)+384) (BuildId: 85a971c1a495be3c4e4e6bf83197b4a7) C0BF05E 12-05 11:58:26.325 23715 23715 F DEBUG : #06 pc 00000000000a1470 /system/lib64/libinputflinger.so (android::inputdispatcher::InputDispatcher::dispatchMotionLocked(long, std::__1::shared_ptr<android::inputdispatcher::MotionEntry>, android::inputdispatcher::InputDispatcher::DropReason*, long*)+1056) (BuildId: 85a971c1a495be3c4e4e6bf83197b4a7) C0BF05F 12-05 11:58:26.325 23715 23715 F DEBUG : #07 pc 000000000009dc94 /system/lib64/libinputflinger.so (android::inputdispatcher::InputDispatcher::dispatchOnceInnerLocked(long*)+1668) (BuildId: 85a971c1a495be3c4e4e6bf83197b4a7) C0BF060 12-05 11:58:26.326 23715 23715 F DEBUG : #08 pc 000000000009d564 /system/lib64/libinputflinger.so (android::inputdispatcher::InputDispatcher::dispatchOnce()+84) (BuildId: 85a971c1a495be3c4e4e6bf83197b4a7) C0BF061 12-05 11:58:26.326 23715 23715 F DEBUG : #09 pc 0000000000013db8 /system/lib64/libinputflinger_base.so (android::(anonymous namespace)::InputThreadImpl::threadLoop()+24) (BuildId: 4ac03616f24d0925d9b9ffb9a048dcd5) C0BF062 12-05 11:58:26.326 23715 23715 F DEBUG : #10 pc 000000000001530c /system/lib64/libutils.so (android::Thread::_threadLoop(void*)+284) (BuildId: e1aa88e73f8cbe876b1da77e01ae24a5) C0BF063 12-05 11:58:26.326 23715 23715 F DEBUG : #11 pc 00000000000ee19c /system/lib64/libandroid_runtime.so (android::AndroidRuntime::javaThreadShell(void*)+140) (BuildId: f03947044b57438629212d4a2dbc07a6) C0BF064 12-05 11:58:26.326 23715 23715 F DEBUG : #12 pc 00000000000d6e3c /apex/com.android.runtime/lib64/bionic/libc.so (__pthread_start(void*)+204) (BuildId: c3622b5c0a4cde0d723ea2d964ec71b7) C0BF065 12-05 11:58:26.326 23715 23715 F DEBUG : #13 pc 000000000006ab00 /apex/com.android.runtime/lib64/bionic/libc.so (__start_thread+64) (BuildId: c3622b5c0a4cde0d723ea2d964ec71b7)

可以看到,是InputDispatcher在分割动作时,缺少了down time,导致system_server崩溃然后重启了。注意到此时的动作是:action=BUTTON_PRESS,也就是按下触控笔的主动橡皮擦键。

在源码中查找分析:frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp

// Split a motion event if needed. if (inputTarget.flags.test(InputTarget::Flags::SPLIT)) { LOG_ALWAYS_FATAL_IF(eventEntry->type != EventEntry::Type::MOTION, "Entry type %s should not have Flags::SPLIT", ftl::enum_string(eventEntry->type).c_str()); const MotionEntry& originalMotionEntry = static_cast<const MotionEntry&>(*eventEntry); if (inputTarget.pointerIds.count() != originalMotionEntry.pointerCount) { if (!inputTarget.firstDownTimeInTarget.has_value()) { logDispatchStateLocked(); LOG(FATAL) << "Splitting motion events requires a down time to be set for the " "target on connection " << connection->getInputChannelName() << " for " << originalMotionEntry.getDescription(); } std::unique_ptr<MotionEntry> splitMotionEntry = splitMotionEvent(originalMotionEntry, inputTarget.pointerIds, inputTarget.firstDownTimeInTarget.value()); if (!splitMotionEntry) { return; // split event was dropped } if (splitMotionEntry->action == AMOTION_EVENT_ACTION_CANCEL) { std::string reason = std::string("reason=pointer cancel on split window"); android_log_event_list(LOGTAG_INPUT_CANCEL) << connection->getInputChannelName().c_str() << reason << LOG_ID_EVENTS; } if (DEBUG_FOCUS || gInputDispatcherLog) { ALOGD("channel '%s' ~ Split motion event.", connection->getInputChannelName().c_str()); logOutboundMotionDetails(" ", *splitMotionEntry); } enqueueDispatchEntriesLocked(currentTime, connection, std::move(splitMotionEntry), inputTarget); return; } }

注意到,在源码中,分割动作时,缺少firstDownTimeInTarget,那么就会发生致命错误。

那么就要看是在哪里设置的firstDownTimeInTarget,为什么现在没有设置firstDownTimeInTarget。

接着查找,发现在InputDispatcher.cpp中有两处可能会设置firstDownTimeInTarget。

frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp 2379 std::vector<InputTarget> InputDispatcher::findTouchedWindowTargetsLocked( 2380 nsecs_t currentTime, const MotionEntry& entry, bool* outConflictingPointerActions, 2381 InputEventInjectionResult& outInjectionResult) { ... 2453 if (newGesture || (isSplit && maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN)) { ... >>set firstDownTimeInTarget 2555 tempTouchState.addOrUpdateWindow(windowHandle, targetFlags, pointerIds, 2556 isDownOrPointerDown 2557 ? std::make_optional(entry.eventTime) 2558 : std::nullopt); 2559 ... 2602 } else { 2603 /* Case 2: Pointer move, up, cancel or non-splittable pointer down. */ ... 2653 if (newTouchedWindowHandle != nullptr && 2654 !haveSameToken(oldTouchedWindowHandle, newTouchedWindowHandle)) { 2655 ALOGD("Touch is slipping out of window %s into window %s in display %" PRId32, 2656 oldTouchedWindowHandle->getName().c_str(), 2657 newTouchedWindowHandle->getName().c_str(), displayId); ... >>set firstDownTimeInTarget 2689 tempTouchState.addOrUpdateWindow(newTouchedWindowHandle, targetFlags, pointerIds, 2690 /frameworks/native/services/inputflinger/dispatcher/TouchState.cpp 65 void TouchState::addOrUpdateWindow(const sp<WindowInfoHandle>& windowHandle, 66 ftl::Flags<InputTarget::Flags> targetFlags, 67 std::bitset<MAX_POINTER_ID + 1> pointerIds, 68 std::optional<nsecs_t> firstDownTimeInTarget) { 69 for (TouchedWindow& touchedWindow : windows) { ... 80 touchedWindow.pointerIds |= pointerIds; 81 if (!touchedWindow.firstDownTimeInTarget.has_value()) { >>set touchedWindow.firstDownTimeInTarget 82 touchedWindow.firstDownTimeInTarget = firstDownTimeInTarget; 83 } 84 return; ... >>set touchedWindow.firstDownTimeInTarget 91 touchedWindow.firstDownTimeInTarget = firstDownTimeInTarget;

添加一些调试log后确认,使用主动橡皮擦功能时,走的是这个if

if (newGesture || (isSplit && maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN)) { ....... ....... const bool isDownOrPointerDown = maskedAction == AMOTION_EVENT_ACTION_DOWN || maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN; // TODO(b/211379801): Currently, even if pointerIds are empty (hover case), we would // still add a window to the touch state. We should avoid doing that, but some of the // later checks ("at least one foreground window") rely on this in order to dispatch // the event properly, so that needs to be updated, possibly by looking at InputTargets. tempTouchState.addOrUpdateWindow(windowHandle, targetFlags, pointerIds, isDownOrPointerDown ? std::make_optional(entry.eventTime) : std::nullopt); ......

通过添加一些调试log后,确认,当使用主动橡皮擦时,isDownOrPointerDown为0,maskedAction=11。查询代码后知道

frameworks/native/include/android/input.h /* One or more buttons have been pressed. */ AMOTION_EVENT_ACTION_BUTTON_PRESS = 11, /* One or more buttons have been released. */ AMOTION_EVENT_ACTION_BUTTON_RELEASE = 12,

至此,我们知道,应该是缺少了对AMOTION_EVENT_ACTION_BUTTON_PRESS和AMOTION_EVENT_ACTION_BUTTON_RELEASE的处理,才导致出错的。

那么在相应的地方不全动作判断是不是就可以了呢?添加以下修改进行测试:

diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp index 657efea445..1f034cb27c 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.cpp +++ b/services/inputflinger/dispatcher/InputDispatcher.cpp @@ -2452,7 +2452,10 @@ std::vector<InputTarget> InputDispatcher::findTouchedWindowTargetsLocked( tempTouchState.clearHoveringPointers(); } - if (newGesture || (isSplit && maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN)) { + ALOGD("wrl: newGesture=%d, isSplit=%d, maskedAction=%d", newGesture, isSplit, maskedAction); + + if (newGesture || (isSplit && (maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN || + maskedAction == AMOTION_EVENT_ACTION_BUTTON_PRESS || maskedAction == AMOTION_EVENT_ACTION_BUTTON_RELEASE))) { /* Case 1: New splittable pointer going down, or need target for hover or scroll. */ const auto [x, y] = resolveTouchedPosition(entry); const int32_t pointerIndex = getMotionEventActionPointerIndex(action); @@ -2548,7 +2551,9 @@ std::vector<InputTarget> InputDispatcher::findTouchedWindowTargetsLocked( } const bool isDownOrPointerDown = maskedAction == AMOTION_EVENT_ACTION_DOWN || - maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN; + maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN || + maskedAction == AMOTION_EVENT_ACTION_BUTTON_PRESS || + maskedAction == AMOTION_EVENT_ACTION_BUTTON_RELEASE; // TODO(b/211379801): Currently, even if pointerIds are empty (hover case), we would // still add a window to the touch state. We should avoid doing that, but some of the

测试后发现,主动橡皮擦功能,还是有小概率导致system_server出错

M01D422 12-11 15:34:50.647 1160 1310 D InputDispatcher: wrl: newGesture=0, isSplit=1, maskedAction=10 M01D423 12-11 15:34:50.647 1160 1310 I InputDispatcher: else Case2 isFromMouse=0, tempTouchState.down=1, tempTouchState.windows.empty() = 0, tempDisplayId=0, displayId=0 M01D424 12-11 15:34:50.648 1160 1310 F system_server: Assertion failed: (pointerIds & newPointerIds).any()

maskedAction=10对应的动作是AMOTION_EVENT_ACTION_HOVER_EXIT = 10, 为什么在处理这个动作时会出错呢?

通过添加log,发现当出现log中的情况时,下面的代码都会执行。而在addWindowTargetLocked函数中也会有addPointers的操作。因此,就会执行两次addPointers操作,重复添加同一个pointerIds,进而导致报错。

// Update dispatching for hover enter and exit. { std::vector<TouchedWindow> hoveringWindows = getHoveringWindowsLocked(oldState, tempTouchState, entry); for (const TouchedWindow& touchedWindow : hoveringWindows) { std::optional<InputTarget> target = createInputTargetLocked(touchedWindow.windowHandle, touchedWindow.targetFlags, touchedWindow.firstDownTimeInTarget); if (!target) { continue; } // Hardcode to single hovering pointer for now. std::bitset<MAX_POINTER_ID + 1> pointerIds; pointerIds.set(entry.pointerProperties[0].id); target->addPointers(pointerIds, touchedWindow.windowHandle->getInfo()->transform); targets.push_back(*target); } } ...... // Output targets from the touch state. for (const TouchedWindow& touchedWindow : tempTouchState.windows) { if (touchedWindow.pointerIds.none() && !touchedWindow.hasHoveringPointers(entry.deviceId)) { // Windows with hovering pointers are getting persisted inside TouchState. // Do not send this event to those windows. continue; } addWindowTargetLocked(touchedWindow.windowHandle, touchedWindow.targetFlags, touchedWindow.pointerIds, touchedWindow.firstDownTimeInTarget, targets); }

那么是不是可以在// Update dispatching for hover enter and exit这一段处理完了 AMOTION_EVENT_ACTION_HOVER_EXIT动作后,添加一个flag,在// Output targets from the touch state这一段不进行addPointers操作。

修改如下:

@@ -2760,6 +2765,7 @@ std::vector<InputTarget> InputDispatcher::findTouchedWindowTargetsLocked( } // Update dispatching for hover enter and exit. + bool hover_exit_flag = false; { std::vector<TouchedWindow> hoveringWindows = getHoveringWindowsLocked(oldState, tempTouchState, entry); @@ -2775,6 +2781,8 @@ std::vector<InputTarget> InputDispatcher::findTouchedWindowTargetsLocked( pointerIds.set(entry.pointerProperties[0].id); target->addPointers(pointerIds, touchedWindow.windowHandle->getInfo()->transform); targets.push_back(*target); + if (maskedAction == AMOTION_EVENT_ACTION_HOVER_EXIT) + hover_exit_flag = true; } } @@ -2834,6 +2842,12 @@ std::vector<InputTarget> InputDispatcher::findTouchedWindowTargetsLocked( continue; } + // 添加检查:如果是悬浮事件,并且这个窗口已经通过hovering路径处理过了,就跳过 + if (hover_exit_flag) { + ALOGD("wrl: hover_exit_flag is true, skip this window."); + continue; + } + addWindowTargetLocked(touchedWindow.windowHandle, touchedWindow.targetFlags, touchedWindow.pointerIds, touchedWindow.firstDownTimeInTarget, targets);

OK,经过测试,这种修改是可以解决该问题的。

这里在提供另外一种方法,也是有效果的。即在分割动作时,判断动作类型,对于 BUTTON_PRESS/BUTTON_RELEASE 动作且处于悬停状态,不进行分割。

修改如下:

diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp index 657efea445..2bf7b4bec2 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.cpp +++ b/services/inputflinger/dispatcher/InputDispatcher.cpp @@ -3401,7 +3401,13 @@ void InputDispatcher::prepareDispatchCycleLocked(nsecs_t currentTime, ftl::enum_string(eventEntry->type).c_str()); const MotionEntry& originalMotionEntry = static_cast<const MotionEntry&>(*eventEntry); - if (inputTarget.pointerIds.count() != originalMotionEntry.pointerCount) { + // 新增:对于 BUTTON_PRESS/BUTTON_RELEASE 动作且处于悬停状态,不进行分割 + ALOGD("wrl: count=%zu, pointerCount=%d", inputTarget.pointerIds.count(), originalMotionEntry.pointerCount); + const int32_t maskedAction = MotionEvent::getActionMasked(originalMotionEntry.action); + const bool isHoverButtonAction = (maskedAction == AMOTION_EVENT_ACTION_BUTTON_PRESS || + maskedAction == AMOTION_EVENT_ACTION_BUTTON_RELEASE) && + originalMotionEntry.pointerCount == 1; // 单指针悬停 + if (inputTarget.pointerIds.count() != originalMotionEntry.pointerCount && !isHoverButtonAction) { if (!inputTarget.firstDownTimeInTarget.has_value()) { logDispatchStateLocked(); LOG(FATAL) << "Splitting motion events requires a down time to be set for the " @@ -3428,7 +3434,12 @@ void InputDispatcher::prepareDispatchCycleLocked(nsecs_t currentTime, enqueueDispatchEntriesLocked(currentTime, connection, std::move(splitMotionEntry), inputTarget); return; - } + } else if (isHoverButtonAction) { + // 对于悬停按钮事件,直接使用原始事件,不进行分割 + ALOGD("wrl: isHoverButtonAction"); + enqueueDispatchEntriesLocked(currentTime, connection, eventEntry, inputTarget); + return; + } } // Not splitting. Enqueue dispatch entries for the event as is.
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/16 12:53:27

OpenAI gpt-oss-20b发布:部署与优化全指南

OpenAI gpt-oss-20b部署与优化实战指南 你有没有遇到过这样的困境&#xff1a;想用大模型做本地推理&#xff0c;却发现动辄上百GB显存需求根本无法落地&#xff1f;或者企业希望私有化部署AI能力&#xff0c;却被闭源模型的授权限制卡住脖子&#xff1f;就在最近&#xff0c;O…

作者头像 李华
网站建设 2026/4/17 12:30:56

适当过滤Window event log 输入Splunk

1: 如果window server 比较多的话,那么eventlog 是会很多的,那么可以根据event code 来过滤,具体的设置: 先去DS (deployment server 上去查到这个index 的inputs.conf 文件,然后 index=abc EventCode IN (4658,4656,4690) | timechart span=1m count by EventCode 可以…

作者头像 李华
网站建设 2026/4/17 9:08:42

【企业级数据治理新范式】:基于混合检索的Dify数据源管理实战手册

第一章&#xff1a;企业级数据治理的演进与挑战随着数字化转型的深入&#xff0c;企业级数据治理已从传统的数据管理演变为支撑业务决策、合规运营和智能化创新的核心战略。早期的数据治理主要聚焦于数据质量与元数据管理&#xff0c;而如今则需应对多源异构数据、实时处理需求…

作者头像 李华
网站建设 2026/4/18 10:56:56

【Dify音视频开发秘籍】:突破1.7.0版本音频时长限制的3大核心技术

第一章&#xff1a;Dify 1.7.0 的音频时长限制Dify 1.7.0 版本在处理音频输入时引入了明确的时长约束机制&#xff0c;旨在优化系统资源调度并提升响应效率。该版本默认将单次上传或处理的音频文件时长上限设定为 300 秒&#xff08;即 5 分钟&#xff09;&#xff0c;超出此限…

作者头像 李华
网站建设 2026/4/17 12:20:52

【R Shiny多模态缓存策略】:揭秘高性能交互式应用背后的底层优化逻辑

第一章&#xff1a;R Shiny多模态缓存策略的核心价值在构建交互式数据应用时&#xff0c;R Shiny 常面临计算密集型操作带来的性能瓶颈。多模态缓存策略通过整合内存、磁盘与外部存储机制&#xff0c;显著提升响应速度并降低重复计算开销。缓存机制的类型对比 内存缓存&#xf…

作者头像 李华