[关闭]
@rogeryi 2015-01-05T10:35:05.000000Z 字数 10519 阅读 4912

Event Handling of Chromium Android WebView

Android WebView TouchEvent InputMethod

作者: 易旭昕 (@roger2yi)
说明: 访问 Cmd Markdown 版本可以获得最佳阅读体验

Chromium Android WebView 研究系列文章

  1. Debugging of Chromium Android WebView
  2. Threading of Chromium Android WebView
  3. Rendering of Chromium Android WebView
  4. WebView of Chromium Android WebView
  5. Event Handling of Chromium Android WebView
  6. Resources of Chromium Android WebView

本文主要描述 Chromium Android WebView (下文简称 CAW)里面跟事件处理相关的部分,包括触屏事件处理,按键事件处理和输入法。

Touch Event

Touch Event Handling 相关的主要对象

上图展示了跟 Touch Event Handling 相关的一些主要对象,其中黄色标识为 Chromium 在 Android WebView 上的适配对象,蓝色标识为 Content 模块对象,绿色标识是 cc 模块对象。总的说来 Content 模块负责事件的路由,事件最终可能由 Blink 内核处理,或者 Compositor 处理,或者 WebView 处理。

Touch Event 一般包括 Touch Down,Touch Move 和 Touch Up,一系列连续的 Touch Events 构成一个 Gesture,而每个 Gesture 必定以一个 Touch Down 开始。CAW 要处理的 Gestures 包括 Tap,Double Tap,Long Press,Scroll,Fling 和 Two-Finger Pinch(Zoom)。

CAW 和 Chrome for Android 事件处理的代码基本是一样的,ContentViewCore.java 是 Touch Event 的入口,当然,如果 Touch Event 引起了一个网页滚动,在 CAW 上会交给 AwContents.java 处理。

Touch/Gesture Event 处理的中枢是 GententViewGestureHandler.java,它对 Touch Event 进行分派,通过各种手势识别器对手势进行判断,如果需要将 Touch/Gesture Event 发送到 Native 端处理,比如说需要发送给 Render 端的 Blink 内核进行处理,GententViewGestureHandler.java 会通过 MotionEventDelegate.java 回调接口发送相应事件给 ContentViewCore.java,由 ContentViewCore.java 发给 Native 端。

  1. /**
  2. * This is an interface to handle MotionEvent related communication with the native side also
  3. * access some ContentView specific parameters.
  4. */
  5. public interface MotionEventDelegate {
  6. public boolean sendTouchEvent(long timeMs, int action, TouchPoint[] pts);
  7. boolean sendGesture(int type, long timeMs, int x, int y, Bundle extraParams);
  8. }

GententViewGestureHandler.java 有一个重要的成员 mTouchHandlingState 决定 Touch Event 是否需要发送给内核。它的可能状态如下:

  1. // All events are forwarded to the GestureDetector, bypassing Javascript.
  2. private static final int NO_TOUCH_HANDLER = 0;
  3. // All events are forwarded as normal to Javascript, and if unconsumed to the GestureDetector.
  4. // * Activated from the renderer by way of |hasTouchEventHandlers(true)|.
  5. private static final int HAS_TOUCH_HANDLER = 1;
  6. // Events in the current gesture are forwarded to the GestureDetector, bypassing Javascript.
  7. // * Activated if the touch down for the current gesture had no Javascript consumer.
  8. private static final int NO_TOUCH_HANDLER_FOR_GESTURE = 2;
  9. // Events in the current gesture are forwarded to Javascript, and not to the GestureDetector.
  10. // * Activated if *any* touch event in the current sequence was consumed by Javascript.
  11. private static final int JAVASCRIPT_CONSUMING_GESTURE = 3;

mTouchHandlingState 在网页刚开始加载时会被初始化为初始值 NO_TOUCH_HANDLER ,而在网页加载完成后,会根据网页有没有 JS Event Handler 被设置为 NO_TOUCH_HANDLER(没有) 或者 NO_TOUCH_HANDLER_FOR_GESTURE(有)。

如果网页有 JS Event Handler,mTouchHandlingState 在 Touch Down 时会被设置为 HAS_TOUCH_HANDLER,GententViewGestureHandler.java 会将 Touch Event 发送给内核,同时将它放在 mPendingMotionEvents 里面等待内核的回应来决定事件是否需要自己处理,根据内核的回应 mTouchHandlingState 状态会保持 HAS_TOUCH_HANDLER 不变,或被设置为 NO_TOUCH_HANDLER_FOR_GESTURE,或被设置为 JAVASCRIPT_CONSUMING_GESTURE。

  • NO_TOUCH_HANDLER 意味着网页没有 JS Event Handler,GententViewGestureHandler.java 完全不需要把任何 Touch Event 发送给内核,全部自行处理;
  • HAS_TOUCH_HANDLER 意味着 JS Event Handler 需要获得 Touch Event 通知,但是它并不拦截 Touch Event,所以 GententViewGestureHandler.java 需要在发送给内核并收到回应后自己再处理;
  • NO_TOUCH_HANDLER_FOR_GESTURE 意味着点击的网页区域没有 JS Event Handler,GententViewGestureHandler.java 不再需要把 Gesture 后续的 Touch Event 发送给内核,全部自行处理;
  • JAVASCRIPT_CONSUMING_GESTURE 意味着 JS Event Handler 全权处理 Gesture 后续的 Touch Event,GententViewGestureHandler.java 不再自行处理。

上面的处理逻辑似乎有些混乱,虽然正确性并没有问题,也许 CAW 后续会对其进行改善。

需要发送给 Native 端的 Touch/Gesture Event,GententViewGestureHandler.java 会发送给 ContentViewCore.java,ContentViewCore.java 再发送到 ContentViewCoreImpl。ContentViewCoreImpl 会将 Java 端发送过来的数据包装成 Blink 内核所处理的事件对象 blink::WebTouchEvent 和 blink::WebGestureEvent,再发送给 RenderWidgetHostViewAndroid 处理,而 RenderWidgetHostViewAndroid 只是简单地转发给 RenderWidgetHostImpl 处理。

RenderWidgetHostImpl 是 Native 端 Touch/Gesture Event 处理的中枢,它包含一个 InputRouter[Impl] 对象,事件发送给 Render 端内核和内核响应的处理由 InputRouter[Impl] 来负责。InputRouterImpl 包含一个
TouchEventQueue 对象负责所谓的 Throttling and Coalescing,Throttling 是指忽略掉一些不重要的 Touch 事件,Coalescing 是指将多个 Touch 事件合并成一个,这样可以避免太多 Touch 事件内核处理不及而导致阻塞。

  1. // Describes the state of the input event ACK coming back to the browser side.
  2. enum InputEventAckState {
  3. INPUT_EVENT_ACK_STATE_UNKNOWN,
  4. INPUT_EVENT_ACK_STATE_CONSUMED,
  5. INPUT_EVENT_ACK_STATE_NOT_CONSUMED,
  6. INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS,
  7. INPUT_EVENT_ACK_STATE_IGNORED,
  8. INPUT_EVENT_ACK_STATE_MAX = INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS
  9. };

InputRouterImpl 接收到内核对 Touch 事件处理的结果 ack_result 后会发送给 RenderWidgetHostImpl,再通过 RenderWidgetHostViewAndroid,ContentViewCoreImpl,ContentViewCore.java,最后发送到 GententViewGestureHandler.java。GententViewGestureHandler.java 则根据结果改变 mTouchHandlingState 的状态,GententViewGestureHandler.java 的方法 confirmTouchEvent 会被调用,其中 drainAllPendingEventsUntilNextDown 方法会将 mTouchHandlingState 设置为 NO_TOUCH_HANDLER_FOR_GESTURE。

  1. void confirmTouchEvent(int ackResult) {
  2. MotionEvent ackedEvent = mPendingMotionEvents.removeFirst();
  3. switch (ackResult) {
  4. case INPUT_EVENT_ACK_STATE_CONSUMED:
  5. case INPUT_EVENT_ACK_STATE_IGNORED:
  6. mTouchHandlingState = JAVASCRIPT_CONSUMING_GESTURE;
  7. trySendPendingEventsToNative();
  8. break;
  9. case INPUT_EVENT_ACK_STATE_NOT_CONSUMED:
  10. if (mTouchHandlingState != JAVASCRIPT_CONSUMING_GESTURE) {
  11. processTouchEvent(ackedEvent);
  12. }
  13. trySendPendingEventsToNative();
  14. break;
  15. case INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS:
  16. if (mTouchHandlingState != JAVASCRIPT_CONSUMING_GESTURE) {
  17. processTouchEvent(ackedEvent);
  18. }
  19. if (ackedEvent.getActionMasked() == MotionEvent.ACTION_DOWN) {
  20. drainAllPendingEventsUntilNextDown();
  21. } else {
  22. trySendPendingEventsToNative();
  23. }
  24. break;
  25. }
  26. }

Gesture Event Handling

Gesture event flow:

A gesture comes in from Android Java code. Where we would have sent an IPC, we instead send to a new class SyncInputEventFilter, which calls CC directly. There are then three cases:

If the event hit the root layer, CC rejects it and lets WebView-specific code handle scrolling.
If the event hit a sublayer, CC absorbs it and scrolls the sublayer.
If the event hit a slow-scroll region, CC rejects it with HandleOnMainThread and the SyncInputEventFilter sends an IPC to the render process.

引用自 Synchronous compositing for WebView

对于 Gesture Event,InputEventRouter 在发送给 Render 端内核之前,会先给它的 client RenderWidgetHostImpl 一个处理/过滤事件的机会,如果事件被 RenderWidgetHostImpl consumed,这个事件就不会再被发送给 Renderer。

  1. void InputRouterImpl::OfferToHandlers(const WebInputEvent& input_event,
  2. const ui::LatencyInfo& latency_info,
  3. bool is_keyboard_shortcut) {
  4. if (OfferToOverscrollController(input_event, latency_info))
  5. return;
  6. if (OfferToClient(input_event, latency_info))
  7. return;
  8. OfferToRenderer(input_event, latency_info, is_keyboard_shortcut);
  9. ...
  10. }

RenderWidgetHostImpl 会交由 RenderWidgetHostViewAndroid 处理, 在 CAW 上 RenderWidgetHostViewAndroid 则会交由 SynchronousCompositorImpl/SynchronousInputEventFilter 处理,SynchronousInputEventFilter 最终会交由 在 InputHandlerManager 上注册的 InputHandler 处理,而实际上这个 InputHandler 正是 LayerTreeHostImpl。

  1. InputEventAckState RenderWidgetHostViewAndroid::FilterInputEvent(
  2. const blink::WebInputEvent& input_event) {
  3. SynchronousCompositorImpl* compositor =
  4. SynchronousCompositorImpl::FromID(host_->GetProcess()->GetID(),
  5. host_->GetRoutingID());
  6. if (compositor)
  7. return compositor->HandleInputEvent(input_event);
  8. return INPUT_EVENT_ACK_STATE_NOT_CONSUMED;
  9. }

InputHandlerManager 是由 Render 端的 RenderThreadImpl 创建并持有的,所以在 CAW 上它是一个全局的对象,InputHandlerManager 创建时会向它的 client SynchronousInputEventFilter 注册自己。SynchronousInputEventFilter 也是一个全局的对象,它构建了 SynchronousCompositorImpl 和 InputHandlerManager 之间的桥接,将 SynchronousCompositorImpl 发送过来的事件转发给 InputHandlerManager 处理。

  1. void RenderThreadImpl::EnsureWebKitInitialized() {
  2. ...
  3. InputHandlerManagerClient* input_handler_manager_client = NULL;
  4. #if defined(OS_ANDROID)
  5. if (SynchronousCompositorFactory* factory =
  6. SynchronousCompositorFactory::GetInstance()) {
  7. input_handler_manager_client = factory->GetInputHandlerManagerClient();
  8. }
  9. #endif
  10. input_handler_manager_.reset(
  11. new InputHandlerManager(compositor_message_loop_proxy_,
  12. input_handler_manager_client));
  13. ...
  14. }

LayerTreeHost 在 Render 端创建 LayerTreeHostImpl 后会通过 input_handler_weak_ptr_ 持有它,并在网页开始加载时将它注册到 InputHandlerManager 里面。简单地说就是 cc 模块的 LayerTreeHostImpl 以 RenderView 的 routing_id 为 Key 向 Content 模块全局的 InputHandlerManager 注册了一个事件处理器,Content 模块在把事件发送给 Blink 内核之前,先通过 InputHandlerManager 将事件交由 LayerTreeHostImpl 处理/过滤。

固然,在 CAW 单进程模型下面为什么整个处理流程会这么复杂让人有些费解,不过如果把这个流程看做是其它 Platform Configuration 多进程模型下的一个退化版本,CAW 下 SynchronousCompositorImpl 和 SynchronousInputEventFilter 在 UI 线程拦截了事件,然后直接把事件交由 InputHandlerManager 进行同步处理,这样就比较容易理解了。

  1. void SynchronousCompositorImpl::SetInputHandler(
  2. cc::InputHandler* input_handler) {
  3. DCHECK(CalledOnValidThread());
  4. if (input_handler_)
  5. input_handler_->SetRootLayerScrollOffsetDelegate(NULL);
  6. input_handler_ = input_handler;
  7. if (input_handler_)
  8. input_handler_->SetRootLayerScrollOffsetDelegate(this);
  9. }

LayerTreeHostImpl 会处理 Scroll/Pinch Gesture,对于 Scroll Gesture,它会判断这是一个网页滚动还是一个页内可滚动元素的滚动(overflow:scroll,iframe...)。所谓网页滚动实际上就是 Root Layer 的滚动,因为 SynchronousCompositorImpl 会设置自身为 Root Layer 的 LayerScrollOffsetDelegate,所以 Root Layer 会将 Scroll Gesture 交给 SynchronousCompositorImpl,而 SynchronousCompositorImpl 则继续向上传递,通过 InProcessViewRenderer,AwContents,最后交回给 Java 端的 AwContents.java 处理(最终会调用到 View.scrollTo)。如果是页內元素的滚动,则相应的 Layer 会自行改变自身的 Scroll Offset,然后请求重绘,如果这个 Layer 已经滚动到边缘,则这个 Scroll Gesture 会向上传递,直到这个 Layer 的某一个 Ancestor Layer 处理了,或者传递到 Root Layer 为止。

Keyboard Event

Keyboard Event 相比 Touch Event 要简单很多,Java 端的入口仍然是 ContentViewCore.java,它把事件交由 ImeAdapter.java 处理,而 ImeAdapter.java 则通过 Native 端的 ImeAdapterAndroid,RenderWidgetHostViewAndroid,交由 RenderWidgetHostImpl 处理。

RenderWidgetHostImpl 仍然交由 InputRouterImpl 处理,InputRouterImpl 的处理流程跟 Gesture Event 倒是差不多,同样调用方法 FilterAndSendWebInputEvent,不过 Compositor 不会对 Keyboard Event 进行拦截,所以最终是发送给 Render 端的 Blink 内核进行处理。

  1. void InputRouterImpl::SendKeyboardEvent(const NativeWebKeyboardEvent& key_event,
  2. const ui::LatencyInfo& latency_info,
  3. bool is_keyboard_shortcut) {
  4. // Put all WebKeyboardEvent objects in a queue since we can't trust the
  5. // renderer and we need to give something to the HandleKeyboardEvent
  6. // handler.
  7. key_queue_.push_back(key_event);
  8. HISTOGRAM_COUNTS_100("Renderer.KeyboardQueueSize", key_queue_.size());
  9. gesture_event_filter_->FlingHasBeenHalted();
  10. // Only forward the non-native portions of our event.
  11. FilterAndSendWebInputEvent(key_event, latency_info, is_keyboard_shortcut);
  12. }

Input Method

Input Method 相关的主要对象

上图展示了跟 Input Method 相关的一些主要对象,其中黄色标识为 Chromium 在 Android WebView 上的适配对象,蓝色标识为 Content 模块对象。其中 InputMethodManagerWrapper.java 包装了 Android API InputMethodManager,用于弹出/隐藏输入面板,AdapterInputConnection 继承了 Android API InputConnection,用于当弹出输入面板后,跟输入法之间的交互,接收来自输入法的通知。

ImeAdapter.java 是输入法处理的中枢,AdapterInputConnection 通过它和 Native 端的 ImeAdapterAndroid,将输入法事件传递给 RenderWidgetHostViewAndroid,最终由 RenderWidgetHostImpl 或者 InputRouterImpl 发送给 Render 端的 Blink 内核。

  1. void ImeAdapterAndroid::SetComposingText(JNIEnv* env, jobject, jstring text,
  2. int new_cursor_pos) {
  3. RenderWidgetHostImpl* rwhi = GetRenderWidgetHostImpl();
  4. ...
  5. rwhi->ImeSetComposition(text16, underlines, new_cursor_pos, new_cursor_pos);
  6. }
  7. void RenderWidgetHostImpl::ImeSetComposition(
  8. const base::string16& text,
  9. const std::vector<blink::WebCompositionUnderline>& underlines,
  10. int selection_start,
  11. int selection_end) {
  12. Send(new ViewMsg_ImeSetComposition(
  13. GetRoutingID(), text, underlines, selection_start, selection_end));
  14. }

参考索引

  1. Synchronous compositing for WebView
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注