Android WebKit消息处理(二)Touch事件的分发处理
上一章《Android WebKit消息处理》讲到了Android WebKit主体初始化流程以及消息处理框架的搭建。这一章主要讲讲Android WebKit touch事件的处理与分发。
在WebViewCore初始化过程:
/* Initialize private data within the WebCore thread. */ private void initialize() { /* Initialize our private BrowserFrame class to handle all * frame-related functions. We need to create a new view which * in turn creates a C level FrameView and attaches it to the frame. */ mBrowserFrame = new BrowserFrame(mContext, this, mCallbackProxy, mSettings, mJavascriptInterfaces); mJavascriptInterfaces = null; // Sync the native settings and also create the WebCore thread handler. mSettings.syncSettingsAndCreateHandler(mBrowserFrame); // Create the handler and transfer messages for the IconDatabase WebIconDatabaseClassic.getInstance().createHandler(); // Create the handler for WebStorageClassic WebStorageClassic.getInstance().createHandler(); // Create the handler for GeolocationPermissions. GeolocationPermissionsClassic.getInstance().createHandler(); // ... // ... // The transferMessages call will transfer all pending messages to the // WebCore thread handler. mEventHub.transferMessages(); // Send a message back to WebView to tell it that we have set up the // WebCore thread. if (mWebViewClassic != null) { Message.obtain(mWebViewClassic.mPrivateHandler, WebViewClassic.WEBCORE_INITIALIZED_MSG_ID, mNativeClass, 0).sendToTarget(); } }
在WebViewCore初始化结束之后,会给WebViewClassic发送WEBCORE_INITIALIZED_MSG_ID消息。
case WEBCORE_INITIALIZED_MSG_ID: // nativeCreate sets mNativeClass to a non-zero value String drawableDir = BrowserFrame.getRawResFilename( BrowserFrame.DRAWABLEDIR, mContext); nativeCreate(msg.arg1, drawableDir, ActivityManager.isHighEndGfx()); if (mDelaySetPicture != null) { setNewPicture(mDelaySetPicture, true); mDelaySetPicture = null; } if (mIsPaused) { nativeSetPauseDrawing(mNativeClass, true); } mInputDispatcher = new WebViewInputDispatcher(this, mWebViewCore.getInputDispatcherCallbacks()); break;在WebViewClassic收到来自WebViewCore的初始化完毕的消息之后,会创建WebViewInputDispatcher对象:
mInputDispatcher = new WebViewInputDispatcher(this, mWebViewCore.getInputDispatcherCallbacks());
WebViewClassic的mInputDispatcher对象是touch事件分发处理的核心。下面讲的Touch事件的分发处理绝大部分是在WebViewInputDispatcher中进行的。
WebViewInputDispatcher构造函数的原型如下:
public WebViewInputDispatcher(UiCallbacks uiCallbacks, WebKitCallbacks webKitCallbacks)uiCallbacks: WebViewClassic的mPrivateHandler
webKitCallbacks: WebViewCore的mEventHub.
关于mPrivateHandler和mEventHub的关系图,详见《Android WebKit消息处理》。从构造函数就可以看出,Touch事件的Ui消息将会派遣给WebViwClassic处理,WebCore消息将会派遣给WebViewCore去处理。
WebViewInputDispatcher把touch输入事件分为Ui事件和WebKit事件(就是上面WebCore事件),更具WebViewClassic在创建WebViewInputDispatcher的时候传入的参数,会分别创建自己的mUiHandler和mWebKitHandler。同时保存mUiCallbacks和mWebKitCallbacks以便于分别向WebViewClassic和WebViewCore发送消息:
public WebViewInputDispatcher(UiCallbacks uiCallbacks, WebKitCallbacks webKitCallbacks) { this.mUiCallbacks = uiCallbacks; mUiHandler = new UiHandler(uiCallbacks.getUiLooper()); this.mWebKitCallbacks = webKitCallbacks; mWebKitHandler = new WebKitHandler(webKitCallbacks.getWebKitLooper()); ViewConfiguration config = ViewConfiguration.get(mUiCallbacks.getContext()); mDoubleTapSlopSquared = config.getScaledDoubleTapSlop(); mDoubleTapSlopSquared = (mDoubleTapSlopSquared * mDoubleTapSlopSquared); mTouchSlopSquared = config.getScaledTouchSlop(); mTouchSlopSquared = (mTouchSlopSquared * mTouchSlopSquared); }其中mUiHandler是依附在UI线程上,mWebKitHandler是依附在WebCore线程上。
@Override public boolean onTouchEvent(MotionEvent ev) { if (mNativeClass == 0 || (!mWebView.isClickable() && !mWebView.isLongClickable())) { return false; } if (mInputDispatcher == null) { return false; } if (mWebView.isFocusable() && mWebView.isFocusableInTouchMode() && !mWebView.isFocused()) { mWebView.requestFocus(); } if (mInputDispatcher.postPointerEvent(ev, getScrollX(), getScrollY() - getTitleHeight(), mZoomManager.getInvScale())) { mInputDispatcher.dispatchUiEvents(); return true; } else { Log.w(LOGTAG, "mInputDispatcher rejected the event!"); return false; } }这是Android WebKit的所有touch输入事件的入口。touch事件通过postPointerEvent进入,然后对UI事件和WebKit事件进行分拣,派遣到对应的消息队列。
在消息分拣过程中,会判断touch输入事件是否真的需要派发给WebKit.
private void enqueueEventLocked(DispatchEvent d) { if (!shouldSkipWebKit(d)) { enqueueWebKitEventLocked(d); } else { enqueueUiEventLocked(d); } } private boolean shouldSkipWebKit(DispatchEvent d) { switch (d.mEventType) { case EVENT_TYPE_CLICK: case EVENT_TYPE_HOVER: case EVENT_TYPE_SCROLL: case EVENT_TYPE_HIT_TEST: return false; case EVENT_TYPE_TOUCH: // TODO: This should be cleaned up. We now have WebViewInputDispatcher // and WebViewClassic both checking for slop and doing their own // thing - they should be consolidated. And by consolidated, I mean // WebViewClassic's version should just be deleted. // The reason this is done is because webpages seem to expect // that they only get an ontouchmove if the slop has been exceeded. if (mIsTapCandidate && d.mEvent != null && d.mEvent.getActionMasked() == MotionEvent.ACTION_MOVE) { return true; } return !mPostSendTouchEventsToWebKit || mPostDoNotSendTouchEventsToWebKitUntilNextGesture; } return true; }通常情况下,几乎所有的touch事件都需要派发给WebKit,除非WebKit不需要:
这两种情况都会导致Document::removeAllEventListerners()被调用:
void Document::removeAllEventListeners() { #if ENABLE(TOUCH_EVENTS) Page* ownerPage = page(); if (!m_inPageCache && ownerPage && (m_frame == ownerPage->mainFrame()) && hasListenerType(Document::TOUCH_LISTENER)) { // Inform the Chrome Client that it no longer needs to forward touch // events to WebCore as the document removes all the event listeners. ownerPage->chrome()->client()->needTouchEvents(false); } #endif EventTarget::removeAllEventListeners(); if (DOMWindow* domWindow = this->domWindow()) domWindow->removeAllEventListeners(); for (Node* node = firstChild(); node; node = node->traverseNextNode()) node->removeAllEventListeners(); }
其中:
ownerPage->chrome()->client()->needTouchEvents(false);
会通过JNI调用WebViewCore的needTouchEvents(),从而就会导致上面所说的shouldSkipWebKIt(d)返回ture,是的事件不会被派遣到WebKit而直接被添加到UI的消息队列。这也就是为什么在网页在Load或者切换的过程中,前面一段时间,点击页面不会响应。当然,在Document初始化完毕之后,会调用needTouchEvents(true)告诉WebViewInputDispatcher :Come on! I need your touch. 这样,Touch输入事件就被分别派遣到Ui消息队列和WebKit消息队列。
其实到这里,touch消息分拣已经很清晰简单了:
private final DispatchEventQueue mWebKitDispatchEventQueue = new DispatchEventQueue(); private final TouchStream mWebKitTouchStream = new TouchStream(); private final WebKitCallbacks mWebKitCallbacks; private final WebKitHandler mWebKitHandler;WebViewInputDispatcher定义了专门的mWebKitDispatchEventQueue来处理派发给WebKit(WebCore)的touch输入事件。enequeueWebKitEventLocked最终会将事件添加到mWebKitDispatchEvenetQueue当中,然后调用scheduleWebKitDispatchLocked()发送MSG_DISPATCH_WEBKIT_EVENTS消息给mWebKitHandler去处理事件。
//mWebKitHandler的handleMessage @Override public void handleMessage(Message msg) { switch (msg.what) { case MSG_DISPATCH_WEBKIT_EVENTS: dispatchWebKitEvents(true); break; default: throw new IllegalStateException("Unknown message type: " + msg.what); } } }dispatchWebKitEvenets()才是真正最终对mWebKitDispatcherEventQueue中消息进行分发和处理的:
private void dispatchWebKitEvents(boolean calledFromHandler) { for (;;) { // Get the next event, but leave it in the queue so we can move it to the UI // queue if a timeout occurs. DispatchEvent d; MotionEvent event; final int eventType; int flags; synchronized (mLock) { if (!ENABLE_EVENT_BATCHING) { drainStaleWebKitEventsLocked(); } d = mWebKitDispatchEventQueue.mHead; if (d == null) { if (mWebKitDispatchScheduled) { mWebKitDispatchScheduled = false; if (!calledFromHandler) { mWebKitHandler.removeMessages( WebKitHandler.MSG_DISPATCH_WEBKIT_EVENTS); } } return; } // ... // ... }内部逻辑其实就是一个for循环,不停的取消息,然后根据当前的状态然后处理。
// UI state, tracks events observed by the UI. (guarded by mLock) private final DispatchEventQueue mUiDispatchEventQueue = new DispatchEventQueue(); private final TouchStream mUiTouchStream = new TouchStream(); private final UiCallbacks mUiCallbacks; private final UiHandler mUiHandler; private boolean mUiDispatchScheduled;跟WebKit的消息处理类似,WebViewInputDispatcher也定义了专门处理UI事件的mUiDispatcherQueue,每当有UI的Touch输入事件的时候,enqueueUiEventLocked()的事件最终会被添加到mUiDispatcherQueue中,然后调用scheduleUiDispatchLocked,发送MSG_DISPATCH_UI_EVENTS,然后触发dispatchUiEvents:
/** * Dispatches pending UI events. * Must only be called from the UI thread. * * This method may be used to flush the queue of pending input events * immediately. This method may help to reduce input dispatch latency * if called before certain expensive operations such as drawing. */ public void dispatchUiEvents() { dispatchUiEvents(false); } private void dispatchUiEvents(boolean calledFromHandler) { for (;;) { MotionEvent event; final int eventType; final int flags; synchronized (mLock) { DispatchEvent d = mUiDispatchEventQueue.dequeue(); if (d == null) { if (mUiDispatchScheduled) { mUiDispatchScheduled = false; if (!calledFromHandler) { mUiHandler.removeMessages(UiHandler.MSG_DISPATCH_UI_EVENTS); } } return; } // ... } // ... }同样,会进入一个for循环,不停的取事件然后处理。
篇幅有点长了,看到这里,可能还不是非常清楚,用下面这图概括下:
在所有的WebKit消息被处理的过程中,有些touch事件是需要给Ui进行反馈的,例如高亮,长按弹出菜单等等。具体这些事件会在后续文章中逐一进行解析的。
版权申明:
转载文章请注明原文出处,任何用于商业目的,请联系本人:[email protected]