当前位置: 首页 > news >正文

安卓InputDispatching Timeout ANR 流程

  • 1 ANR的检测逻辑有两个参与者: 观测者A和被观测者B,当然,这两者是不在同一个线程中的。
  • 2 A在调用B中的逻辑时,同时在A中保存一个标记F,然后做个延时操作C,延时时间设为T,这一步称为: 埋雷
  • 3 B中的逻辑如果被执行到,就会通知A去清除标记F,并且通知A解除C,这一步称为: 拆雷
  • 4 如果C没被拆除,那么在时间T后就会被触发,就会去检测标记F是否还在,如果在,就说明B没有在指定的时间T内完成,那么就提示B发生了ANR,这一步称为: 爆雷
  • 5 由于A和B是在不同线程中的,所以B即使死循环,也不会影响C的检测过程。
    所以,我们可以将ANR更精炼的总结为: 埋雷、拆雷和爆雷三个步骤

InputDispatching Timeout: 输入事件(包括按键和触屏事件)在5秒内无响应,就会弹出 ANR 提示框,供用户选择继续等待程序响应或者关闭这个应用程序(也就是杀掉这个应用程序的进程)。输入超时类的 ANR 可以细分为以下两类:

1)处理消息超时: 这一类是指因为消息处理超时而发生的 ANR,在 log,会看到 “Input dispatching timed out (Waiting because the focused window has not finished processing
the input events that were previously delivered to it.)”

2)无法获取焦点: 这一类通常因为新窗口创建慢或旧窗口退出慢而造成窗口无法获得焦点从而发生 ANR,典型 Log “Reason: Waiting because no window has focus but there is a focused application
that may eventually add a window when it finishes starting up.”

这里不分析没有焦点的情形,只分析处理消息超时流程

在 input子系统中,正常逻辑是:InputDispatcher 负责将输入事件分发给 UI 主线程。UI主线程接收到输入事件后,使用 InputConsumer 来处理事件。经过一系列的 InputStage 完成事件分发后,执行finishInputEvent() 方法来告知 InputDispatcher 事件已经处理完成。InputDispatcher 中使用handleReceiveCallback() 方法来处理 UI 主线程返回的消息,最终将 dispatchEntry事件从等待队列中移除。

http://aospxref.com/android-13.0.0_r3/xref/frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp

3975  void InputDispatcher::notifyMotion(const NotifyMotionArgs* args) {
// 打开下列的开关,会打印对应的log
3976      if (DEBUG_INBOUND_EVENT_DETAILS) {
3977          ALOGD("notifyMotion - id=%" PRIx32 " eventTime=%" PRId64 ", deviceId=%d, source=0x%x, "
3978                "displayId=%" PRId32 ", policyFlags=0x%x, "
3979                "action=0x%x, actionButton=0x%x, flags=0x%x, metaState=0x%x, buttonState=0x%x, "
3980                "edgeFlags=0x%x, xPrecision=%f, yPrecision=%f, xCursorPosition=%f, "
3981                "yCursorPosition=%f, downTime=%" PRId64,
3982                args->id, args->eventTime, args->deviceId, args->source, args->displayId,
3983                args->policyFlags, args->action, args->actionButton, args->flags, args->metaState,
3984                args->buttonState, args->edgeFlags, args->xPrecision, args->yPrecision,
3985                args->xCursorPosition, args->yCursorPosition, args->downTime);
3986          for (uint32_t i = 0; i < args->pointerCount; i++) {
。。。。
4047          // Just enqueue a new motion event.// 创建 MotionEntry 对象
4048          std::unique_ptr<MotionEntry> newEntry =
4049                  std::make_unique<MotionEntry>(args->id, args->eventTime, args->deviceId,
4050                                                args->source, args->displayId, policyFlags,
4051                                                args->action, args->actionButton, args->flags,
4052                                                args->metaState, args->buttonState,
4053                                                args->classification, args->edgeFlags,
4054                                                args->xPrecision, args->yPrecision,
4055                                                args->xCursorPosition, args->yCursorPosition,
4056                                                args->downTime, args->pointerCount,
4057                                                args->pointerProperties, args->pointerCoords);
4058  
4059          if (args->id != android::os::IInputConstants::INVALID_INPUT_EVENT_ID &&
4060              IdGenerator::getSource(args->id) == IdGenerator::Source::INPUT_READER &&
4061              !mInputFilterEnabled) {
4062              const bool isDown = args->action == AMOTION_EVENT_ACTION_DOWN;
4063              mLatencyTracker.trackListener(args->id, isDown, args->eventTime, args->readTime);
4064          }
4065  
// 将 MotionEntry 事件保存到 mInboundQueue 队列中;如果保存之前 mInboundQueue是为空的,则 needWake 为true。
4066          needWake = enqueueInboundEventLocked(std::move(newEntry));
4067          mLock.unlock();
4068      } // release lock
4069  // 这里分析直接去唤醒inputdispatcher 线程
4070      if (needWake) {
4071          mLooper->wake();
4072      }
4073  }

InputDispatcher 是个线程,会循环执行 dispatchOnce 方法

602  void InputDispatcher::dispatchOnce() {
603      nsecs_t nextWakeupTime = LONG_LONG_MAX;// 局部空间,获取同步锁
604      { // acquire lock
605          std::scoped_lock _l(mLock);
606          mDispatcherIsAlive.notify_all();
607  
608          // Run a dispatch loop if there are no pending commands.
609          // The dispatch loop might enqueue commands to run afterwards.// 如果没有 mCommandQueue ,没有 command 命令的话,
// 则执行 dispatchOnceInnerLocked,获取到下一次唤醒的时间:nextWakeupTime
// 1)如果有motion 事件,调用 dispatchOnceInnerLocked 方法
610          if (!haveCommandsLocked()) {
611              dispatchOnceInnerLocked(&nextWakeupTime);
612          }
613  // 执行command 命令,如果有的话,直接返回 true,然后马上唤醒线程
616          if (runCommandsLockedInterruptable()) {
617              nextWakeupTime = LONG_LONG_MIN;
618          }
619  // 处理anr 或者获取到下一次anr 的时间
// 2)获取超时anr 的时间:processAnrsLocked
622          const nsecs_t nextAnrCheck = processAnrsLocked();
623          nextWakeupTime = std::min(nextWakeupTime, nextAnrCheck);
624  
625          // We are about to enter an infinitely long sleep, because we have no commands or
626          // pending or queued events// 如果唤醒时间为最大,则表明没有input 事件,是idle 空闲状态
627          if (nextWakeupTime == LONG_LONG_MAX) {
628              mDispatcherEnteredIdle.notify_all();
629          }
630      } // release lock
631  
632      // Wait for callback or timeout or wake.  (make sure we round up, not down)
633      nsecs_t currentTime = now();
// 获取到唤醒线程的时间
// 调用  mLooper->wake 函数也可以唤醒线程
634      int timeoutMillis = toMillisecondTimeoutDelay(currentTime, nextWakeupTime);
635      mLooper->pollOnce(timeoutMillis);
636  }

1)如果有motion 事件,调用 dispatchOnceInnerLocked 方法

718  void InputDispatcher::dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) {
719      nsecs_t currentTime = now();
。。。
// 初始 mPendingEvent 为null
746      if (!mPendingEvent) {
// 前面notifymotion 增加了 mInboundQueue
747          if (mInboundQueue.empty()) {
。。。
770          } else {
771              // Inbound queue has at least one entry.
// 这里去获取到motion 事件
772              mPendingEvent = mInboundQueue.front();
773              mInboundQueue.pop_front();
774              traceInboundQueueLengthLocked();
775          }
776  
777          // Poke user activity for this event.
778          if (mPendingEvent->policyFlags & POLICY_FLAG_PASS_TO_USER) {
779              pokeUserActivityLocked(*mPendingEvent);
780          }
781      }
。。。
798      switch (mPendingEvent->type) {
868          case EventEntry::Type::MOTION: {
869              std::shared_ptr<MotionEntry> motionEntry =
870                      std::static_pointer_cast<MotionEntry>(mPendingEvent);
871              if (dropReason == DropReason::NOT_DROPPED && isAppSwitchDue) {
872                  dropReason = DropReason::APP_SWITCH;
873              }
874              if (dropReason == DropReason::NOT_DROPPED && isStaleEvent(currentTime, *motionEntry)) {
875                  dropReason = DropReason::STALE;
876              }
877              if (dropReason == DropReason::NOT_DROPPED && mNextUnblockedEvent) {
878                  dropReason = DropReason::BLOCKED;
879              }
// dispatchMotionLocked 方法去分发motion 事件
880              done = dispatchMotionLocked(currentTime, motionEntry, &dropReason, nextWakeupTime);
881              break;
882          }
。。。
// 一般是返回true,表示分发motion 事件到应用了
902      if (done) {
903          if (dropReason != DropReason::NOT_DROPPED) {
904              dropInboundEventLocked(*mPendingEvent, dropReason);
905          }
906          mLastDropReason = dropReason;
907  
// 返回true,则重新设置 mPendingEvent 为null
908          releasePendingEventLocked();
// 设置 nextWakeupTime  为最小,马上唤醒
909          *nextWakeupTime = LONG_LONG_MIN; // force next poll to wake up immediately
910      }
911  }

// dispatchMotionLocked 方法去分发motion 事件

1634  bool InputDispatcher::dispatchMotionLocked(nsecs_t currentTime, std::shared_ptr<MotionEntry> entry,
1635                                             DropReason* dropReason, nsecs_t* nextWakeupTime) {
// 会打印对应的trace
1636      ATRACE_CALL();
。。。
1652      const bool isPointerEvent = isFromSource(entry->source, AINPUT_SOURCE_CLASS_POINTER);
1653  
1654      // Identify targets.
1655      std::vector<InputTarget> inputTargets;
1656  
1657      bool conflictingPointerActions = false;
1658      InputEventInjectionResult injectionResult;
1659      if (isPointerEvent) {
1660          // Pointer event.  (eg. touchscreen)// 去根据xy 的坐标点,去找到触摸的 window
1661          injectionResult =
1662                  findTouchedWindowTargetsLocked(currentTime, *entry, inputTargets, nextWakeupTime,
1663                                                 &conflictingPointerActions);
。。。
// 增加到全局触摸事件
1687      addGlobalMonitoringTargetsLocked(inputTargets, getTargetDisplayId(*entry));
1688  
1689      // Dispatch the motion.
1690      if (conflictingPointerActions) {
1691          CancelationOptions options(CancelationOptions::CANCEL_POINTER_EVENTS,
1692                                     "conflicting pointer actions");
1693          synthesizeCancelationEventsForAllConnectionsLocked(options);
1694      }
// 分发event
1695      dispatchEventLocked(currentTime, entry, inputTargets);
1696      return true;
1697  }

// 分发event

1753  void InputDispatcher::dispatchEventLocked(nsecs_t currentTime,
1754                                            std::shared_ptr<EventEntry> eventEntry,
1755                                            const std::vector<InputTarget>& inputTargets) {
1756      ATRACE_CALL();
1757      if (DEBUG_DISPATCH_CYCLE) {
1758          ALOGD("dispatchEventToCurrentInputTargets");
1759      }
1760  
1761      updateInteractionTokensLocked(*eventEntry, inputTargets);
1762  
1763      ALOG_ASSERT(eventEntry->dispatchInProgress); // should already have been set to true
1764  
1765      pokeUserActivityLocked(*eventEntry);
1766  
1767      for (const InputTarget& inputTarget : inputTargets) {// 找到对应的窗口的 connection
1768          sp<Connection> connection =
1769                  getConnectionLocked(inputTarget.inputChannel->getConnectionToken());
1770          if (connection != nullptr) {
1771              prepareDispatchCycleLocked(currentTime, connection, eventEntry, inputTarget);
1772          } else {
1773              if (DEBUG_FOCUS) {
1774                  ALOGD("Dropping event delivery to target with channel '%s' because it "
1775                        "is no longer registered with the input dispatcher.",
1776                        inputTarget.inputChannel->getName().c_str());
1777              }
1778          }
1779      }
1780  }

准备分发事件给应用 prepareDispatchCycleLocked

2887  void InputDispatcher::prepareDispatchCycleLocked(nsecs_t currentTime,
2888                                                   const sp<Connection>& connection,
2889                                                   std::shared_ptr<EventEntry> eventEntry,
2890                                                   const InputTarget& inputTarget) {
2891      if (ATRACE_ENABLED()) {
2892          std::string message =
2893                  StringPrintf("prepareDispatchCycleLocked(inputChannel=%s, id=0x%" PRIx32 ")",
2894                               connection->getInputChannelName().c_str(), eventEntry->id);
2895          ATRACE_NAME(message.c_str());
2896      }
。。。。。
2944  
2945      // Not splitting.  Enqueue dispatch entries for the event as is.
2946      enqueueDispatchEntriesLocked(currentTime, connection, eventEntry, inputTarget);
2947  }
2949  void InputDispatcher::enqueueDispatchEntriesLocked(nsecs_t currentTime,
2950                                                     const sp<Connection>& connection,
2951                                                     std::shared_ptr<EventEntry> eventEntry,
2952                                                     const InputTarget& inputTarget) {
2953      if (ATRACE_ENABLED()) {
2954          std::string message =
2955                  StringPrintf("enqueueDispatchEntriesLocked(inputChannel=%s, id=0x%" PRIx32 ")",
2956                               connection->getInputChannelName().c_str(), eventEntry->id);
2957          ATRACE_NAME(message.c_str());
2958      }
2959  
2960      bool wasEmpty = connection->outboundQueue.empty();
2961  
// 将触摸事件保存到 outboundQueue 中
2962      // Enqueue dispatch entries for the requested modes.
2963      enqueueDispatchEntryLocked(connection, eventEntry, inputTarget,
2964                                 InputTarget::FLAG_DISPATCH_AS_HOVER_EXIT);
2965      enqueueDispatchEntryLocked(connection, eventEntry, inputTarget,
2966                                 InputTarget::FLAG_DISPATCH_AS_OUTSIDE);
2967      enqueueDispatchEntryLocked(connection, eventEntry, inputTarget,
2968                                 InputTarget::FLAG_DISPATCH_AS_HOVER_ENTER);
2969      enqueueDispatchEntryLocked(connection, eventEntry, inputTarget,
2970                                 InputTarget::FLAG_DISPATCH_AS_IS);
2971      enqueueDispatchEntryLocked(connection, eventEntry, inputTarget,
2972                                 InputTarget::FLAG_DISPATCH_AS_SLIPPERY_EXIT);
2973      enqueueDispatchEntryLocked(connection, eventEntry, inputTarget,
2974                                 InputTarget::FLAG_DISPATCH_AS_SLIPPERY_ENTER);
2975  
2976      // If the outbound queue was previously empty, start the dispatch cycle going.
2977      if (wasEmpty && !connection->outboundQueue.empty()) {
// 开始socket 通信去分发给应用 startDispatchCycleLocked
2978          startDispatchCycleLocked(currentTime, connection);
2979      }
2980  }

// 开始socket 通信去分发给应用 startDispatchCycleLocked

3214  void InputDispatcher::startDispatchCycleLocked(nsecs_t currentTime,
3215                                                 const sp<Connection>& connection) {
3216      if (ATRACE_ENABLED()) {
3217          std::string message = StringPrintf("startDispatchCycleLocked(inputChannel=%s)",
3218                                             connection->getInputChannelName().c_str());
3219          ATRACE_NAME(message.c_str());
3220      }
3221      if (DEBUG_DISPATCH_CYCLE) {
3222          ALOGD("channel '%s' ~ startDispatchCycle", connection->getInputChannelName().c_str());
3223      }
3224  
3225      while (connection->status == Connection::Status::NORMAL && !connection->outboundQueue.empty()) {
3226          DispatchEntry* dispatchEntry = connection->outboundQueue.front();
3227          dispatchEntry->deliveryTime = currentTime;
// 获取到应用设置的超时事件,一般为 5 秒
3228          const std::chrono::nanoseconds timeout = getDispatchingTimeoutLocked(connection);
// 设置timeout 的时间
3229          dispatchEntry->timeoutTime = currentTime + timeout.count();
。。。
3252              case EventEntry::Type::MOTION: {
3253                  const MotionEntry& motionEntry = static_cast<const MotionEntry&>(eventEntry);
。。。
// socket 通信发送给 应用,给到应用去处理了
3285                  // Publish the motion event.
3286                  status = connection->inputPublisher
3287                                   .publishMotionEvent(dispatchEntry->seq,
3288                                                       dispatchEntry->resolvedEventId,
3289                                                       motionEntry.deviceId, motionEntry.source,
3290                                                       motionEntry.displayId, std::move(hmac),
3291                                                       dispatchEntry->resolvedAction,
3292                                                       motionEntry.actionButton,
3293                                                       dispatchEntry->resolvedFlags,
3294                                                       motionEntry.edgeFlags, motionEntry.metaState,
3295                                                       motionEntry.buttonState,
3296                                                       motionEntry.classification,
3297                                                       dispatchEntry->transform,
3298                                                       motionEntry.xPrecision, motionEntry.yPrecision,
3299                                                       motionEntry.xCursorPosition,
3300                                                       motionEntry.yCursorPosition,
3301                                                       dispatchEntry->rawTransform,
3302                                                       motionEntry.downTime, motionEntry.eventTime,
3303                                                       motionEntry.pointerCount,
3304                                                       motionEntry.pointerProperties, usingCoords);
3305                  break;
3306              }
。。。。。
3383          // Re-enqueue the event on the wait queue.
// 将触摸事件从 outboundQueue 移除掉
3384          connection->outboundQueue.erase(std::remove(connection->outboundQueue.begin(),
3385                                                      connection->outboundQueue.end(),
3386                                                      dispatchEntry));
3387          traceOutboundQueueLength(*connection);
// 将其增加到 waitQueue 等待队列中
3388          connection->waitQueue.push_back(dispatchEntry);
// 如果应用没有die,则 将anr 超时时间保存到 mAnrTracker 中,还有对应的应用token
3389          if (connection->responsive) {
3390              mAnrTracker.insert(dispatchEntry->timeoutTime,
3391                                 connection->inputChannel->getConnectionToken());
3392          }
3393          traceWaitQueueLength(*connection);
3394      }
3395  }

2)获取超时anr 的时间:processAnrsLocked

622 const nsecs_t nextAnrCheck = processAnrsLocked();
// nextWakeupTime 由前面的分析,返回的是为 LONG_LONG_MIN,所以这里的唤醒时间为马上唤醒
623 nextWakeupTime = std::min(nextWakeupTime, nextAnrCheck);

670  nsecs_t InputDispatcher::processAnrsLocked() {
671      const nsecs_t currentTime = now();
672      nsecs_t nextAnrCheck = LONG_LONG_MAX;// 下列是处理no focus 没有焦点的情况,这里不满足
674      if (mNoFocusedWindowTimeoutTime.has_value() && mAwaitedFocusedApplication != nullptr) {
675          if (currentTime >= *mNoFocusedWindowTimeoutTime) {
676              processNoFocusedWindowAnrLocked();
677              mAwaitedFocusedApplication.reset();
678              mNoFocusedWindowTimeoutTime = std::nullopt;
679              return LONG_LONG_MIN;
680          } else {
681              // Keep waiting. We will drop the event when mNoFocusedWindowTimeoutTime comes.
682              nextAnrCheck = *mNoFocusedWindowTimeoutTime;
683          }
684      }
685  
// 获取到前面保存到anr 的时间,即前面当前的时间 + timeout 5 秒
687      nextAnrCheck = std::min(nextAnrCheck, mAnrTracker.firstTimeout());
// 当前的时间是小于 nextAnrCheck,所以返回 nextAnrCheck的时间
688      if (currentTime < nextAnrCheck) { // most likely scenario
689          return nextAnrCheck;          // everything is normal. Let's check again at nextAnrCheck
690      }
691  
692      // If we reached here, we have an unresponsive connection.
693      sp<Connection> connection = getConnectionLocked(mAnrTracker.firstToken());
694      if (connection == nullptr) {
695          ALOGE("Could not find connection for entry %" PRId64, mAnrTracker.firstTimeout());
696          return nextAnrCheck;
697      }
698      connection->responsive = false;
699      // Stop waking up for this unresponsive connection
700      mAnrTracker.eraseToken(connection->inputChannel->getConnectionToken());
701      onAnrLocked(connection);
702      return LONG_LONG_MIN;
703  }

但是接下有个处理 :这里返回时 MIN 的,即马上唤醒。所以再走一次线程dispatchOnce 方法
nextWakeupTime = std::min(nextWakeupTime, nextAnrCheck);

602  void InputDispatcher::dispatchOnce() {
603      nsecs_t nextWakeupTime = LONG_LONG_MAX;
604      { // acquire lock
605          std::scoped_lock _l(mLock);
606          mDispatcherIsAlive.notify_all();
607  // 这时候没有inboundqueue 了
610          if (!haveCommandsLocked()) {
611              dispatchOnceInnerLocked(&nextWakeupTime);
612          }
613  
// 也没有cammand
616          if (runCommandsLockedInterruptable()) {
617              nextWakeupTime = LONG_LONG_MIN;
618          }
619  
// 这里就可以获取到 anr 的时间了
622          const nsecs_t nextAnrCheck = processAnrsLocked();
623          nextWakeupTime = std::min(nextWakeupTime, nextAnrCheck);
624  
625          // We are about to enter an infinitely long sleep, because we have no commands or
626          // pending or queued events
627          if (nextWakeupTime == LONG_LONG_MAX) {
628              mDispatcherEnteredIdle.notify_all();
629          }
630      } // release lock
631  
633      nsecs_t currentTime = now();
634      int timeoutMillis = toMillisecondTimeoutDelay(currentTime, nextWakeupTime);
// 这里去等待anr 超时的时间
635      mLooper->pollOnce(timeoutMillis);
636  }

3)排雷过程

// 在inputdispatcher 与应用创建连接的时候,会设置socket 通信的callback,执行 handleReceiveCallback 方法

5450  Result<std::unique_ptr<InputChannel>> InputDispatcher::createInputChannel(const std::string& name) {
5451      if (DEBUG_CHANNEL_CREATION) {
5452          ALOGD("channel '%s' ~ createInputChannel", name.c_str());
5453      }
。。。5475          std::function<int(int events)> callback = std::bind(&InputDispatcher::handleReceiveCallback,
5476                                                              this, std::placeholders::_1, token);
5477  
5478          mLooper->addFd(fd, 0, ALOOPER_EVENT_INPUT, new LooperEventCallback(callback), nullptr);
5479      } // release lock
5480  
5481      // Wake the looper because some connections have changed.
5482      mLooper->wake();
5483      return clientChannel;
5484  }

如果应用有发送socket 消息,则执行 handleReceiveCallback 方法

int InputDispatcher::handleReceiveCallback(int events, sp<IBinder> connectionToken) {std::scoped_lock _l(mLock);
// 通过token 获取到对应的connectionsp<Connection> connection = getConnectionLocked(connectionToken);bool notify;
// event 不能为 ALOOPER_EVENT_ERROR 或者 ALOOPER_EVENT_HANGUPif (!(events & (ALOOPER_EVENT_ERROR | ALOOPER_EVENT_HANGUP))) {if (!(events & ALOOPER_EVENT_INPUT)) {ALOGW("channel '%s' ~ Received spurious callback for unhandled poll event.  ""events=0x%x",connection->getInputChannelName().c_str(), events);return 1;}nsecs_t currentTime = now();bool gotOne = false;status_t status = OK;
// 循环操作for (;;) {
// 通过socket 获取到消息Result<InputPublisher::ConsumerResponse> result =connection->inputPublisher.receiveConsumerResponse();if (!result.ok()) {status = result.error().code();break;}
// 如果result 是 Finished,则执行 finishDispatchCycleLockedif (std::holds_alternative<InputPublisher::Finished>(*result)) {const InputPublisher::Finished& finish =std::get<InputPublisher::Finished>(*result);finishDispatchCycleLocked(currentTime, connection, finish.seq, finish.handled,finish.consumeTime);
。。。
// 设置 gotOne = true
gotOne = true;}if (gotOne) {
// finishDispatchCycleLocked 增加了command,这里去执行 commandrunCommandsLockedInterruptable();if (status == WOULD_BLOCK) {return 1;}}

// 如果result 是 Finished,则执行 finishDispatchCycleLocked

void InputDispatcher::finishDispatchCycleLocked(nsecs_t currentTime,const sp<Connection>& connection, uint32_t seq,bool handled, nsecs_t consumeTime) {if (DEBUG_DISPATCH_CYCLE) {ALOGD("channel '%s' ~ finishDispatchCycle - seq=%u, handled=%s",connection->getInputChannelName().c_str(), seq, toString(handled));}if (connection->status == Connection::Status::BROKEN ||connection->status == Connection::Status::ZOMBIE) {return;}// Notify other system components and prepare to start the next dispatch cycle.auto command = [this, currentTime, connection, seq, handled, consumeTime]() REQUIRES(mLock) {doDispatchCycleFinishedCommand(currentTime, connection, seq, handled, consumeTime);};postCommandLocked(std::move(command));
}

前面 runCommandsLockedInterruptable 方法会去执行 doDispatchCycleFinishedCommand 方法

void InputDispatcher::doDispatchCycleFinishedCommand(nsecs_t finishTime,const sp<Connection>& connection, uint32_t seq,bool handled, nsecs_t consumeTime) {// Handle post-event policy actions.std::deque<DispatchEntry*>::iterator dispatchEntryIt = connection->findWaitQueueEntry(seq);if (dispatchEntryIt == connection->waitQueue.end()) {return;}DispatchEntry* dispatchEntry = *dispatchEntryIt;const nsecs_t eventDuration = finishTime - dispatchEntry->deliveryTime;
// 如果应用处理超过 2 秒,则会打印下列的日志if (eventDuration > SLOW_EVENT_PROCESSING_WARNING_TIMEOUT) {ALOGI("%s spent %" PRId64 "ms processing %s", connection->getWindowName().c_str(),ns2ms(eventDuration), dispatchEntry->eventEntry->getDescription().c_str());}
。。。
// 从等待队列中找到 dispatchEntryIt 
dispatchEntryIt = connection->findWaitQueueEntry(seq);if (dispatchEntryIt != connection->waitQueue.end()) {dispatchEntry = *dispatchEntryIt;connection->waitQueue.erase(dispatchEntryIt);const sp<IBinder>& connectionToken = connection->inputChannel->getConnectionToken();
// 从anrtracker 中移除这个超时时间,和connectionmAnrTracker.erase(dispatchEntry->timeoutTime, connectionToken);if (!connection->responsive) {connection->responsive = isConnectionResponsive(*connection);if (connection->responsive) {// The connection was unresponsive, and now it's responsive.processConnectionResponsiveLocked(*connection);}}traceWaitQueueLength(*connection);if (restartEvent && connection->status == Connection::Status::NORMAL) {connection->outboundQueue.push_front(dispatchEntry);traceOutboundQueueLength(*connection);} else {releaseDispatchEntry(dispatchEntry);}}// Start the next dispatch cycle for this connection.
// 如果还有下一个事件的话,则调用 startDispatchCycleLocked 继续去处理startDispatchCycleLocked(now(), connection);
}

则下次执行线程的时候,由于mAnrTracker移除了对应的conenction,则 processAnrsLocked不会触发anr 流程

4)爆雷过程

在anr 时间点触发线程执行:dispatchOnce,然后执行 processAnrsLocked 方法

nsecs_t InputDispatcher::processAnrsLocked() {const nsecs_t currentTime = now();nsecs_t nextAnrCheck = LONG_LONG_MAX;
// 不满足下列的条件if (mNoFocusedWindowTimeoutTime.has_value() && mAwaitedFocusedApplication != nullptr) {if (currentTime >= *mNoFocusedWindowTimeoutTime) {processNoFocusedWindowAnrLocked();mAwaitedFocusedApplication.reset();mNoFocusedWindowTimeoutTime = std::nullopt;return LONG_LONG_MIN;} else {// Keep waiting. We will drop the event when mNoFocusedWindowTimeoutTime comes.nextAnrCheck = *mNoFocusedWindowTimeoutTime;}}// Check if any connection ANRs are duenextAnrCheck = std::min(nextAnrCheck, mAnrTracker.firstTimeout());
// 这里当前的时间点是大于 anr 的时间,即前面没有移除if (currentTime < nextAnrCheck) { // most likely scenarioreturn nextAnrCheck;          // everything is normal. Let's check again at nextAnrCheck}// 执行下列anr 的流程// If we reached here, we have an unresponsive connection.sp<Connection> connection = getConnectionLocked(mAnrTracker.firstToken());if (connection == nullptr) {ALOGE("Could not find connection for entry %" PRId64, mAnrTracker.firstTimeout());return nextAnrCheck;}
// 设置应用回复为 falseconnection->responsive = false;// Stop waking up for this unresponsive connection
// 从anrtracker 中移除,防止进入到线程又再次触发anrmAnrTracker.eraseToken(connection->inputChannel->getConnectionToken());
// 走anr 流程:onAnrLockedonAnrLocked(connection);return LONG_LONG_MIN;
}

// 走anr 流程:onAnrLocked

void InputDispatcher::onAnrLocked(const sp<Connection>& connection) {if (connection == nullptr) {LOG_ALWAYS_FATAL("Caller must check for nullness");}// Since we are allowing the policy to extend the timeout, maybe the waitQueue// is already healthy again. Don't raise ANR in this situationif (connection->waitQueue.empty()) {ALOGI("Not raising ANR because the connection %s has recovered",connection->inputChannel->getName().c_str());return;}
// 从等待队列中获取DispatchEntry* oldestEntry = *connection->waitQueue.begin();
// 获取到等待的时间const nsecs_t currentWait = now() - oldestEntry->deliveryTime;
// 设置input dispatcher 时间分发超时的原因 reasonstd::string reason =android::base::StringPrintf("%s is not responding. Waited %" PRId64 "ms for %s",connection->inputChannel->getName().c_str(),ns2ms(currentWait),oldestEntry->eventEntry->getDescription().c_str());sp<IBinder> connectionToken = connection->inputChannel->getConnectionToken();
// 保存到dump 中updateLastAnrStateLocked(getWindowHandleLocked(connectionToken), reason);processConnectionUnresponsiveLocked(*connection, std::move(reason));// Stop waking up for events on this connection, it is already unresponsivecancelEventsForAnrLocked(connection);
}

processConnectionUnresponsiveLocked 处理anr

void InputDispatcher::processConnectionUnresponsiveLocked(const Connection& connection,std::string reason) {const sp<IBinder>& connectionToken = connection.inputChannel->getConnectionToken();std::optional<int32_t> pid;
// 不满足下列条件if (connection.monitor) {ALOGW("Monitor %s is unresponsive: %s", connection.inputChannel->getName().c_str(),reason.c_str());pid = findMonitorPidByTokenLocked(connectionToken);} else {// The connection is a windowALOGW("Window %s is unresponsive: %s", connection.inputChannel->getName().c_str(),reason.c_str());const sp<WindowInfoHandle> handle = getWindowHandleLocked(connectionToken);if (handle != nullptr) {pid = handle->getInfo()->ownerPid;}}sendWindowUnresponsiveCommandLocked(connectionToken, pid, std::move(reason));
}
void InputDispatcher::sendWindowUnresponsiveCommandLocked(const sp<IBinder>& token,std::optional<int32_t> pid,std::string reason) {
// 将command 保存到queue 中,因为前面会直接返回 MIN,所以线程会马山处理auto command = [this, token, pid, reason = std::move(reason)]() REQUIRES(mLock) {scoped_unlock unlock(mLock);mPolicy->notifyWindowUnresponsive(token, pid, reason);};postCommandLocked(std::move(command));
}

// 通知到界面没有反应 notifyWindowUnresponsive

/frameworks/base/services/core/jni/com_android_server_input_InputManagerService.cpp
// 前面将 NativeInputManager 保存到 了InputManager中

831  void NativeInputManager::notifyWindowUnresponsive(const sp<IBinder>& token,
832                                                    std::optional<int32_t> pid,
833                                                    const std::string& reason) {
834  #if DEBUG_INPUT_DISPATCHER_POLICY
835      ALOGD("notifyWindowUnresponsive");
836  #endif
// 会打印对应的trace:notifyWindowUnresponsive
837      ATRACE_CALL();
838  
839      JNIEnv* env = jniEnv();
840      ScopedLocalFrame localFrame(env);
841  
842      jobject tokenObj = javaObjectForIBinder(env, token);
843      ScopedLocalRef<jstring> reasonObj(env, env->NewStringUTF(reason.c_str()));
844  
// 调用java 层的方法
845      env->CallVoidMethod(mServiceObj, gServiceClassInfo.notifyWindowUnresponsive, tokenObj,
846                          pid.value_or(0), pid.has_value(), reasonObj.get());
847      checkAndClearExceptionFromCallback(env, "notifyWindowUnresponsive");
848  }

/frameworks/base/services/core/java/com/android/server/input/InputManagerService.java

2927      // Native callback
2928      @SuppressWarnings("unused")
2929      private void notifyWindowUnresponsive(IBinder token, int pid, boolean isPidValid,
2930              String reason) {
2931          mWindowManagerCallbacks.notifyWindowUnresponsive(token,
2932                  isPidValid ? OptionalInt.of(pid) : OptionalInt.empty(), reason);
2933      }

/frameworks/base/services/core/java/com/android/server/wm/InputManagerCallback.java

102      @Override
103      public void notifyWindowUnresponsive(@NonNull IBinder token, @NonNull OptionalInt pid,
104              @NonNull String reason) {
105          mService.mAnrController.notifyWindowUnresponsive(token, pid, reason);
106      }

/frameworks/base/services/core/java/com/android/server/wm/AnrController.java

88      void notifyWindowUnresponsive(@NonNull IBinder token, @NonNull OptionalInt pid,
89              @NonNull String reason) {
90          if (notifyWindowUnresponsive(token, reason)) {
91              return;
92          }
93          if (!pid.isPresent()) {
94              Slog.w(TAG_WM, "Failed to notify that window token=" + token + " was unresponsive.");
95              return;
96          }
97          notifyWindowUnresponsive(pid.getAsInt(), reason);
98      }
99  
100      /**
101       * Notify a window identified by its input token was unresponsive.
102       *
103       * @return true if the window was identified by the given input token and the request was
104       *         handled, false otherwise.
105       */
106      private boolean notifyWindowUnresponsive(@NonNull IBinder inputToken, String reason) {
107          preDumpIfLockTooSlow();
108          final int pid;
109          final boolean aboveSystem;
110          final ActivityRecord activity;
111          synchronized (mService.mGlobalLock) {
112              InputTarget target = mService.getInputTargetFromToken(inputToken);
113              if (target == null) {
114                  return false;
115              }
116              WindowState windowState = target.getWindowState();
117              pid = target.getPid();
118              // Blame the activity if the input token belongs to the window. If the target is
119              // embedded, then we will blame the pid instead.
120              activity = (windowState.mInputChannelToken == inputToken)
121                      ? windowState.mActivityRecord : null;// 会打印下列的log
122              Slog.i(TAG_WM, "ANR in " + target + ". Reason:" + reason);
123              aboveSystem = isWindowAboveSystem(windowState);
124              dumpAnrStateLocked(activity, windowState, reason);
125          }
// ActivityRecord  不为空
126          if (activity != null) {
127              activity.inputDispatchingTimedOut(reason, pid);
128          } else {
129              mService.mAmInternal.inputDispatchingTimedOut(pid, aboveSystem, reason);
130          }
131          return true;
132      }

/frameworks/base/services/core/java/com/android/server/wm/ActivityRecord.java

6649      public boolean inputDispatchingTimedOut(String reason, int windowPid) {
6650          ActivityRecord anrActivity;
6651          WindowProcessController anrApp;
6652          boolean blameActivityProcess;
6653          synchronized (mAtmService.mGlobalLock) {
6654              anrActivity = getWaitingHistoryRecordLocked();
6655              anrApp = app;
6656              blameActivityProcess =  hasProcess()
6657                      && (app.getPid() == windowPid || windowPid == INVALID_PID);
6658          }
6659  
6660          if (blameActivityProcess) {
6661              return mAtmService.mAmInternal.inputDispatchingTimedOut(anrApp.mOwner,
6662                      anrActivity.shortComponentName, anrActivity.info.applicationInfo,
6663                      shortComponentName, app, false, reason);

/frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java

17647      boolean inputDispatchingTimedOut(ProcessRecord proc, String activityShortComponentName,
17648              ApplicationInfo aInfo, String parentShortComponentName,
17649              WindowProcessController parentProcess, boolean aboveSystem, String reason) {
17650          if (checkCallingPermission(FILTER_EVENTS) != PackageManager.PERMISSION_GRANTED) {
17651              throw new SecurityException("Requires permission " + FILTER_EVENTS);
17652          }
17653  
17654          final String annotation;
17655          if (reason == null) {
17656              annotation = "Input dispatching timed out";
// 这里增加 Input dispatching timed out
17657          } else {
17658              annotation = "Input dispatching timed out (" + reason + ")";
17659          }
17660  
17661          if (proc != null) {
17662              synchronized (this) {
17663                  if (proc.isDebugging()) {
17664                      return false;
17665                  }
17666  
17667                  if (proc.getActiveInstrumentation() != null) {
17668                      Bundle info = new Bundle();
17669                      info.putString("shortMsg", "keyDispatchingTimedOut");
17670                      info.putString("longMsg", annotation);
17671                      finishInstrumentationLocked(proc, Activity.RESULT_CANCELED, info);
17672                      return true;
17673                  }
17674              }// 这里去弹框
17675              mAnrHelper.appNotResponding(proc, activityShortComponentName, aInfo,
17676                      parentShortComponentName, parentProcess, aboveSystem, annotation);
17677          }
17678  
17679          return true;
17680      }

相关文章:

安卓InputDispatching Timeout ANR 流程

1 ANR的检测逻辑有两个参与者: 观测者A和被观测者B&#xff0c;当然&#xff0c;这两者是不在同一个线程中的。2 A在调用B中的逻辑时&#xff0c;同时在A中保存一个标记F&#xff0c;然后做个延时操作C&#xff0c;延时时间设为T&#xff0c;这一步称为: 埋雷 。3 B中的逻辑如果…...

【Nginx从入门到精通】03 、安装部署-让虚拟机可以联网

文章目录 总结一、配置联网【Minimal 精简版】1.1、查看网络配置1.2、配置ip地址 : 修改配置文件 <font colororange>ifcfg-ens33Stage 1&#xff1a;输入指令Stage 2&#xff1a;修改参数Stage 3&#xff1a;重启网络Stage 4&#xff1a;测试上网 二、配置联网【Everyth…...

java 增强型for循环 详解

Java 增强型 for 循环&#xff08;Enhanced for Loop&#xff09;详解 增强型 for 循环&#xff08;也称为 “for-each” 循环&#xff09;是 Java 从 JDK 5 开始引入的一种便捷循环语法&#xff0c;旨在简化对数组或集合类的迭代操作。 1. 基本语法 语法格式 for (类型 变量…...

浪潮云启操作系统(InLinux) bcache宕机问题分析

前言 本文以一次真实的内核宕机问题为切入点&#xff0c;结合实际操作案例&#xff0c;详细展示了如何利用工具 crash对内核转储&#xff08;kdump&#xff09;进行深入分析和调试的方法。通过对崩溃日志的解读、函数调用栈的梳理、关键地址的定位以及代码逻辑的排查&#xff…...

038集——quadtree(CAD—C#二次开发入门)

效果如下&#xff1a; using Autodesk.AutoCAD.ApplicationServices; using Autodesk.AutoCAD.DatabaseServices; using Autodesk.AutoCAD.EditorInput; using Autodesk.AutoCAD.Geometry; using System; using System.Collections.Generic; using System.Linq; using System.T…...

备赛蓝桥杯--算法题目(1)

1. 链表求和 . - 力扣&#xff08;LeetCode&#xff09; class Solution { public:ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {ListNode *head nullptr, *tail nullptr;int carry 0;while (l1 || l2) {int n1 l1 ? l1->val: 0;int n2 l2 ? l2->val:…...

机器学习100道经典面试题库(二)

机器学习100道经典面试题库&#xff08;31-60&#xff09; 在大规模的语料中&#xff0c;挖掘词的相关性是一个重要的问题。以下哪一个信息不能用于确定两个词的相关性。 A、互信息 B、最大熵 C、卡方检验 D、最大似然比 答案&#xff1a;B 解析&#xff1a;最大熵代表了…...

Unet++改进37:添加KACNConvNDLayer(2024最新改进方法)

本文内容:添加KACNConvNDLayer 目录 论文简介 1.步骤一 2.步骤二 3.步骤三 4.步骤四 论文简介 1.步骤一 新建block/kacn_conv.py文件,添加如下代码: import torch import torch.nn as nn##源码地址:https://github.com/SynodicMonth/ChebyKAN class KACNConvNDLaye…...

基于 Levenberg - Marquardt 法的 BP 网络学习改进算法详解

基于 Levenberg - Marquardt 法的 BP 网络学习改进算法详解 一、引言 BP&#xff08;Back Propagation&#xff09;神经网络在众多领域有着广泛应用&#xff0c;但传统 BP 算法存在收敛速度慢、易陷入局部最优等问题。Levenberg - Marquardt&#xff08;LM&#xff09;算法作…...

MySQL 8.0与PostgreSQL 15.8的性能对比

根据搜索结果&#xff0c;以下是MySQL 8.0与PostgreSQL 15.8的性能对比&#xff1a; MySQL 8.0性能特点&#xff1a; MySQL在处理大量读操作时表现出色&#xff0c;其存储引擎InnoDB提供了行级锁定和高效的事务处理&#xff0c;适用于并发读取的场景。MySQL通过查询缓存来提高读…...

qt连接postgres数据库时 setConnectOptions函数用法

连接选项&#xff0c;而这些选项没有直接的方法对应&#xff0c;你可能需要采用以下策略之一&#xff1a; 由于Qt SQL API的限制&#xff0c;你可能需要采用一些变通方法或查阅相关文档和社区资源以获取最新的信息和最佳实践。如果你确实需要设置特定的连接选项&#xff0c;并且…...

MySQL45讲 第二十七讲 主库故障应对:从库切换策略与 GTID 详解——阅读总结

文章目录 MySQL45讲 第二十七讲 主库故障应对&#xff1a;从库切换策略与 GTID 详解一、一主多从架构与主备切换的挑战&#xff08;一&#xff09;一主多从基本结构&#xff08;二&#xff09;主备切换的复杂性 二、基于位点的主备切换&#xff08;一&#xff09;同步位点的概念…...

JavaWeb笔记整理——Spring Task、WebSocket

目录 SpringTask ​cron表达式 WebSocket SpringTask cron表达式 WebSocket...

基于SpringBoot+RabbitMQ完成应⽤通信

前言&#xff1a; 经过上面俩章学习&#xff0c;我们已经知道Rabbit的使用方式RabbitMQ 七种工作模式介绍_rabbitmq 工作模式-CSDN博客 RabbitMQ的工作队列在Spring Boot中实现&#xff08;详解常⽤的⼯作模式&#xff09;-CSDN博客作为⼀个消息队列,RabbitMQ也可以⽤作应⽤程…...

Flutter踩坑记录(一)debug运行生成的项目,不能手动点击运行

问题 IOS14设备&#xff0c;切后台划掉&#xff0c;二次启动崩溃。 原因 IOS14以上 flutter 不支持debugger模式下的二次启动 。 要二次启动需要以release方式编译工程安装至手机。 操作步骤 清理项目&#xff1a;在命令行中运行flutter clean来清理之前的构建文件。重新构…...

React的hook✅

为什么hook必须在组件内的顶层声明&#xff1f; 这是为了确保每次组件渲染时&#xff0c;Hooks 的调用顺序保持一致。React利用 hook 的调用顺序来跟踪各个 hook 的状态。每当一个函数组件被渲染时&#xff0c;所有的 hook 调用都是按照从上到下的顺序依次执行的。React 内部会…...

2024.5 AAAiGLaM:通过邻域分区和生成子图编码对领域知识图谱对齐的大型语言模型进行微调

GLaM: Fine-Tuning Large Language Models for Domain Knowledge Graph Alignment via Neighborhood Partitioning and Generative Subgraph Encoding 问题 如何将特定领域知识图谱直接整合进大语言模型&#xff08;LLM&#xff09;的表示中&#xff0c;以提高其在图数据上自…...

从熟练Python到入门学习C++(record 6)

基础之基础之最后一节-结构体 1.结构体的定义 结构体相对于自定义的一种新的变量类型。 四种定义方式&#xff0c;推荐第一种&#xff1b;第四种适合大量定义&#xff0c;也适合查找&#xff1b; #include <iostream> using namespace std; #include <string.h>…...

jenkins的安装(War包安装)

‌Jenkins是一个开源的持续集成工具&#xff0c;基于Java开发&#xff0c;主要用于监控持续的软件版本发布和测试项目。‌ 它提供了一个开放易用的平台&#xff0c;使软件项目能够实现持续集成。Jenkins的功能包括持续的软件版本发布和测试项目&#xff0c;以及监控外部调用执行…...

WPS 加载项开发说明wpsjs

wpsjs几个常用的CMD命令&#xff1a; 1.打开cmd输入命令测试版本号 npm -v 2.首次安装nodejs&#xff0c;npm默认国外镜像&#xff0c;包下载较慢时&#xff0c;可切换到国内镜像 //下载速度较慢时可切换国内镜像 npm config set registry https://registry.npmmirror.com …...

【Anomaly Detection论文阅读记录】PaDiM与PatchCore模型的区别与联系

PaDiM与PatchCore模型的区别与联系 背景介绍 PADIM(Pretrained Anomaly Detection via Image Matching)和 PatchCore 都是基于深度学习的异常检测方法,主要用于图像异常检测,尤其是在无监督学习设置下。 PADIM 是一种通过利用预训练的视觉模型(例如,ImageNet预训练的卷…...

uni-app Vue3语法实现微信小程序样式穿透uview-plus框架

1 问题描述 我在用 uni-app vue3 语法开发微信小程序时&#xff0c;在项目中使用了 uview-plus 这一开源 UI 框架。在使用 up-text 组件时&#xff0c;想要给它添加一些样式&#xff0c;之前了解到微信小程序存在样式隔离的问题&#xff0c;也在uview-plus官网-注意事项中找到…...

K8S基础概念和环境搭建

K8S的基础概念 1. 什么是K8S K8S的全称是Kubernetes K8S是一个开源的容器编排平台&#xff0c;用于自动化部署、扩缩、管理容器化应用程序。 2. 集群和节点 集群&#xff1a;K8S将多个机器统筹和管理起来&#xff0c;彼此保持通讯&#xff0c;这样的关系称之为集群。 节点…...

[服务器] 腾讯云服务器免费体验,成功部署网站

文章目录 概要整体架构流程概要 腾讯云服务器免费体验一个月。 整体架构流程 腾讯云服务器体验一个月, 选择预装 CentOS 7.5 首要最重要的是: 添加阿里云镜像。 不然国外源速度慢, 且容易失败。 yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/li…...

vue中el-select 模糊查询下拉两种方式

第一种&#xff1a;先获取所有下拉数据再模糊查询&#xff0c;效果如下 1&#xff0c;页面代码&#xff1a;speciesList是种类列表List, speciesId 是speciesList里面对应的id&#xff0c;filterable是过滤查询标签 <el-form-item label"种类" prop"species…...

深入解析PostgreSQL中的PL/pgSQL语法

在数据库管理系统中&#xff0c;PostgreSQL因其强大的功能和稳定性而受到广泛欢迎。其中&#xff0c;PL/pgSQL作为PostgreSQL的过程化语言&#xff0c;为用户提供了更为灵活和强大的编程能力。本文将深入解析PL/pgSQL的语法&#xff0c;帮助读者更好地掌握这门语言&#xff0c;…...

Vue 3集成海康Web插件实现视频监控

​&#x1f308;个人主页&#xff1a;前端青山 &#x1f525;系列专栏&#xff1a;组件封装篇 &#x1f516;人终将被年少不可得之物困其一生 依旧青山,本期给大家带来组件封装篇专栏内容:Vue 3集成海康Web插件实现视频监控 引言 最近在项目中使用了 Vue 3 结合海康Web插件来实…...

多目标优化算法:多目标蛇鹫优化算法(MOSBOA)求解DTLZ1-DTLZ9,提供完整MATLAB代码

一、蛇鹫优化算法 蛇鹫优化算法&#xff08;Secretary Bird Optimization Algorithm&#xff0c;简称SBOA&#xff09;由Youfa Fu等人于2024年4月发表在《Artificial Intelligence Review》期刊上的一种新型的元启发式算法。该算法旨在解决复杂工程优化问题&#xff0c;特别是…...

机器翻译基础与模型 之三:基于自注意力的模型

基于RNN和CNN的翻译模型&#xff0c;在处理文字序列时有个问题&#xff1a;它们对序列中不同位置之间的依赖关系的建模并不直接。以CNN的为例&#xff0c;如果要对长距离依赖进行描述&#xff0c;需要多层卷积操作&#xff0c;而且不同层之间信息传递也可能有损失&#xff0c;这…...

如何使用PCL处理ROS Bag文件中的点云数据并重新保存 ubuntu20.04

如何使用PCL处理ROS Bag文件中的点云数据并重新保存 要精确地处理ROS bag中的点云数据并使用PCL进行处理&#xff0c;再将处理后的数据保存回新的ROS bag文件&#xff0c;以下方案提供了详细、专业和严谨的步骤。 步骤 1: 环境设置 确保安装了ROS和PCL&#xff0c;并配置好环…...

做苗木选择哪个网站/简单网页设计模板html

题目链接 https://www.nowcoder.com/practice/5af18ba2eb45443aa91a11e848aa6723?tpId37&tqId21237&tPage1&rp&ru/ta/huawei&qru/ta/huawei/question-ranking 题目描述 给定n个字符串&#xff0c;请对n个字符串按照字典序排列。 输入描述: 输入第一行…...

济南网站建设 伍际网络/哪里有免费的网站推广服务

可量化的软件项目质量考核指标说明关键字&#xff1a;软件项目质量考核有一个完整的指标体系&#xff0c;从可行易操作的角度出发&#xff0c;评价一个软件项目质量情况&#xff0c;可以从以下几个方面出发&#xff0c;获取比较客观的评价指标。指标内容说明如下。1 小组考核内…...

怎么做体育直播网站/前端性能优化

以下列出mysql函数的使用&#xff0c;并不完全&#xff0c;涉及到多少写多少。length(str)&#xff1a;返回字符串(str)的字符长度。一个汉字算三个字符&#xff0c;一个数字或字母算一个字符。select length(测试); --6select length(123abc); --6char_length(str)&#xff1a…...

域名怎么制作网站/网站关键词优化案例

Cmake使用总结 Cmake中常见问题:## 标题 ## 1. 下载问题。经常需要从国外下载第三方包&#xff0c;由于网络问题可能会下载失败&#xff01;建议开启VPN进行下载&#xff0c;或者从其他下载源下载后进行手动解压并放到指定路径下。 2. 路径问题。Cmake中往往需要填写路径&am…...

淘宝如何在其他网站做优惠/google关键词

随着课程的学习越来越深入&#xff0c;学期在不知不觉中已经过了三分之二。可是我自己仍觉得在课堂上没有学到什么知识&#xff0c;人家说大学生的学习效率最高的时候是在期末考试前的最后一天晚上&#xff0c;可能也适用于我现在的课程学习状态吧。下月中旬就要开始考四级了&a…...

wordpress进入后台显示500/洛阳网站seo

使用Chrome的开发者工具 怎样打开Chrome的开发者工具&#xff1f;【原文地址】http://www.cnblogs.com/QLeelulu/archive/2011/08/28/2156402.html你可以直接在页面上点击右键&#xff0c;然后选择审查元素&#xff1a;或者在Chrome的工具中找到&#xff1a;或者&#xff0c;你…...