展会信息港展会大全

Android WebKit消息处理(二)Touch事件的分发处理
来源:互联网   发布日期:2015-10-02 21:27:31   浏览:1923次  

导读:Android WebKit消息处理(二)Touch事件的分发处理 /* Initialize private data within the WebCore thread. ...

Android WebKit消息处理(二)Touch事件的分发处理

/* 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消息处理

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线程上。

由于WebView是继承于View,所以当点击页面的时候,WebView的onTouchEvent会被调用,从而触发WebViewClassic的onTouchEvent:

@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事件进行分拣,派遣到对应的消息队列。

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初始化过程中。在FrameLoader stopLoad之后。Document被析构。

这两种情况都会导致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消息分拣已经很清晰简单了:

WebKit消息的处理

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消息的处理:

// 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进行反馈的,例如高亮,长按弹出菜单等等。具体这些事件会在后续文章中逐一进行解析的。

版权申明:

转载文章请注明原文出处,任何用于商业目的,请联系本人:hyman_tan@126.com

赞助本站

人工智能实验室

相关热词: android开发 教程

AiLab云推荐
展开

热门栏目HotCates

Copyright © 2010-2024 AiLab Team. 人工智能实验室 版权所有    关于我们 | 联系我们 | 广告服务 | 公司动态 | 免责声明 | 隐私条款 | 工作机会 | 展会港