Androidタッチメッセージ処理
19217 ワード
1. WindowInputEventReceiver.onInputEvent() ----ViewRootImpl.java
InputDispatchからpublishの1つのEentイベントが発生すると、WindowInputEventReceiver.onInputEventはコールバック関数として呼び出されます.
1.2. ViewRootImpl.enqueueInputEvent()
eventをQueeuedInputEventとしてリストキューの最後に配置し、processImmediately==trueであればdoProcessInputEvents()を直接実行してこのeventイベントを直ちに処理し、そうでなければscheduleProcessInputEventsを呼び出してhanderを介してメインスレッドのLooperにEentを入れる.
2. ViewRootImpl.deliverInputEvent()
最後にdeliverInputEventを呼び出してイベントを配布し、KeyEentであればdeliverKeyEent()を呼び出し、同様にTouchであればPointer EventはdeliverPointerEventを呼び出す.
2.1 ViewRootImpl.deliverPointerEvent()
1. もしmView==null|!mAddedは、fininshInputEventを直接呼び出してInputDispatcherに伝えます.
2. ActionDownであればensureTouchMode(true)を介してWMSにWindowState対応のtouch modeを設定するように伝え、ensureTouchModelyを呼び出してhandle the changeを行う
3. toucheventならmLastTouchPointで今回のPointのPositionを記録してpossible drag-initiationに使います
4. mView.dispatchPointerEvent(event); DecorViewを通じてdispatchPointerEventに行く.
5. Eventが処理されるかどうかにかかわらずfinishInputEvent(q,true)が呼び出される.InputDispatcherに伝えます.
2.1.4 DecorView.dispatchPointerEvent
DecorView継承FrameLayoutも間接的にViewGroup,Viewを継承している.
1. 親ビューのdispatchPointerEvent()を呼び出し、自分のdispatchTouchEventを呼び出します.
2. dispatchTouchEventでcallbackを呼び出す.dispatchTouchEvent、ここのCallbackはActivityオブジェクトです.
2.1.4.2 Activity.dispatchtouchEvent
1. onUserInteraction()は、dispatchの前にいくつかの操作を行います.
2. getwindow().superDispatchouchEventとは、PhoneWindowのsuperDispatchTouchEventを呼び出すことです.PhoneWindowもmDecorViewの対応を直接呼び出す方法です.一方,DoverViewのsuperDispatchTouchEventメソッドでは親のViewGroupを呼び出したdispatchTouchEventである.
3. ActivityのすべてのViewがEventを処理しない場合は、ActivityのonTouchEvent()で処理します.
ここでの呼び出し順は、DoverView->Activity->PhoneWindow->DocerView->ViewGroup
2.1.4.2.1 ViewGroupdispatchTouchEvent(MotionEvent ev)
1. Action_ならDownイベント、それでは前のTouchTargetsとTouchStateをすべてclear落として、mFirstTouchTarget=null
2.onInterceptTouchEvent()は、この関数で現在のViewがこのEventをブロックするかどうかを教え、returnがこのeventを下にdispatchしないようにします
3. Interceptの現在のEventに行かなければ、自分のchild viewsを巡ってTouchのある領域にあるviewを見つけ、見つけたらgetTouchTarget(View)を通じてViewがTouchTargetにあるかどうかを探します.不在の場合はD i s p a t c h T r a n s formedTouchEvent(ev,false,child,idBitsToAssign)を呼び出し、Eventをchild viewに配布します.
4. childがeventイベントを消費していない場合、mFirstTouchTarget==null)は、handled=d i s p a t c h TransformedTouchEvent(ev,canceled,null,TouchTarget.ALL_POINTER_IDS)を呼び出して自分で今回のEventを処理します.パラメータのchildはnullです.
ViewGroupも処理していない場合は、親Viewに遡って処理します.
2.1.4.2.1.3 ViewGroup.dispatchTransformedTouchEvent()
1. 主にchildを呼び出す.dispatchTouchEvent(transformedEvent);イベントを再帰的に伝え、childがView Groupである場合、ステップは上記と同じであり、ViewであればViewを呼び出す.dispatchTouchEvent(MotionEvent event)
2.1.4.2.1.3.1View.dispatchTouchEvent(MotionEvent event)
1. TouchListenerがあれば登録されたTouchListenerのコールバック関数onTouchイベントを呼び出して直接返します.これは私たちがtouch eventを受信したいときにlistenerを書く理由です.
2. TouchListenerがない場合、デフォルトのonTouchEventイベントが呼び出されます.
2.1.4.2.1.4 Activity.onTouchEvent(MotionEvent event)
すべてのViewがTouchEventを処理しない場合、最後にActivityによって処理されます.
mWindowがそのためにこのEventがcloseを落とすかどうかを判断し、closeでなければreturn falseになります.
最後にFinishInputEvent(false)を呼び出し、この処理されていないEventを返します.
InputDispatchからpublishの1つのEentイベントが発生すると、WindowInputEventReceiver.onInputEventはコールバック関数として呼び出されます.
@Override
public void onInputEvent(InputEvent event) {
enqueueInputEvent(event, this, 0, true);
}
1.2. ViewRootImpl.enqueueInputEvent()
eventをQueeuedInputEventとしてリストキューの最後に配置し、processImmediately==trueであればdoProcessInputEvents()を直接実行してこのeventイベントを直ちに処理し、そうでなければscheduleProcessInputEventsを呼び出してhanderを介してメインスレッドのLooperにEentを入れる.
void enqueueInputEvent(InputEvent event,
InputEventReceiver receiver, int flags, boolean processImmediately) {
QueuedInputEvent q = obtainQueuedInputEvent(event, receiver, flags);
// Always enqueue the input event in order, regardless of its time stamp.
// We do this because the application or the IME may inject key events
// in response to touch events and we want to ensure that the injected keys
// are processed in the order they were received and we cannot trust that
// the time stamp of injected events are monotonic.
QueuedInputEvent last = mFirstPendingInputEvent;
if (last == null) {
mFirstPendingInputEvent = q;
} else {
while (last.mNext != null) {
last = last.mNext;
}
last.mNext = q;
}
if (processImmediately) {
doProcessInputEvents();
} else {
scheduleProcessInputEvents();
}
}
2. ViewRootImpl.deliverInputEvent()
最後にdeliverInputEventを呼び出してイベントを配布し、KeyEentであればdeliverKeyEent()を呼び出し、同様にTouchであればPointer EventはdeliverPointerEventを呼び出す.
private void deliverInputEvent(QueuedInputEvent q) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "deliverInputEvent");
try {
if (q.mEvent instanceof KeyEvent) {
deliverKeyEvent(q);
} else {
final int source = q.mEvent.getSource();
if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) {
deliverPointerEvent(q);
} else if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
deliverTrackballEvent(q);
} else {
deliverGenericMotionEvent(q);
}
}
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
2.1 ViewRootImpl.deliverPointerEvent()
1. もしmView==null|!mAddedは、fininshInputEventを直接呼び出してInputDispatcherに伝えます.
2. ActionDownであればensureTouchMode(true)を介してWMSにWindowState対応のtouch modeを設定するように伝え、ensureTouchModelyを呼び出してhandle the changeを行う
3. toucheventならmLastTouchPointで今回のPointのPositionを記録してpossible drag-initiationに使います
4. mView.dispatchPointerEvent(event); DecorViewを通じてdispatchPointerEventに行く.
5. Eventが処理されるかどうかにかかわらずfinishInputEvent(q,true)が呼び出される.InputDispatcherに伝えます.
private void deliverPointerEvent(QueuedInputEvent q) {
final MotionEvent event = (MotionEvent)q.mEvent;
final boolean isTouchEvent = event.isTouchEvent();
if (mInputEventConsistencyVerifier != null) {
if (isTouchEvent) {
mInputEventConsistencyVerifier.onTouchEvent(event, 0);
} else {
mInputEventConsistencyVerifier.onGenericMotionEvent(event, 0);
}
}
// If there is no view, then the event will not be handled.
if (mView == null || !mAdded) {
finishInputEvent(q, false);
return;
}
// Translate the pointer event for compatibility, if needed.
if (mTranslator != null) {
mTranslator.translateEventInScreenToAppWindow(event);
}
// Enter touch mode on down or scroll.
final int action = event.getAction();
if (action == MotionEvent.ACTION_DOWN || action == MotionEvent.ACTION_SCROLL) {
ensureTouchMode(true);
}
// Offset the scroll position.
if (mCurScrollY != 0) {
event.offsetLocation(0, mCurScrollY);
}
if (MEASURE_LATENCY) {
lt.sample("A Dispatching PointerEvents", System.nanoTime() - event.getEventTimeNano());
}
// Remember the touch position for possible drag-initiation.
if (isTouchEvent) {
mLastTouchPoint.x = event.getRawX();
mLastTouchPoint.y = event.getRawY();
}
// Dispatch touch to view hierarchy.
boolean handled = mView.dispatchPointerEvent(event);
if (MEASURE_LATENCY) {
lt.sample("B Dispatched PointerEvents ", System.nanoTime() - event.getEventTimeNano());
}
if (handled) {
finishInputEvent(q, true);
return;
}
// Pointer event was unhandled.
finishInputEvent(q, false);
}
2.1.4 DecorView.dispatchPointerEvent
DecorView継承FrameLayoutも間接的にViewGroup,Viewを継承している.
1. 親ビューのdispatchPointerEvent()を呼び出し、自分のdispatchTouchEventを呼び出します.
2. dispatchTouchEventでcallbackを呼び出す.dispatchTouchEvent、ここのCallbackはActivityオブジェクトです.
public final boolean dispatchPointerEvent(MotionEvent event) {
if (event.isTouchEvent()) {
return dispatchTouchEvent(event);
} else {
return dispatchGenericMotionEvent(event);
}
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
final Callback cb = getCallback();
return cb != null && !isDestroyed() && mFeatureId < 0 ? cb.dispatchTouchEvent(ev)
: super.dispatchTouchEvent(ev);
}
2.1.4.2 Activity.dispatchtouchEvent
1. onUserInteraction()は、dispatchの前にいくつかの操作を行います.
2. getwindow().superDispatchouchEventとは、PhoneWindowのsuperDispatchTouchEventを呼び出すことです.PhoneWindowもmDecorViewの対応を直接呼び出す方法です.一方,DoverViewのsuperDispatchTouchEventメソッドでは親のViewGroupを呼び出したdispatchTouchEventである.
3. ActivityのすべてのViewがEventを処理しない場合は、ActivityのonTouchEvent()で処理します.
ここでの呼び出し順は、DoverView->Activity->PhoneWindow->DocerView->ViewGroup
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
2.1.4.2.1 ViewGroupdispatchTouchEvent(MotionEvent ev)
1. Action_ならDownイベント、それでは前のTouchTargetsとTouchStateをすべてclear落として、mFirstTouchTarget=null
2.onInterceptTouchEvent()は、この関数で現在のViewがこのEventをブロックするかどうかを教え、returnがこのeventを下にdispatchしないようにします
3. Interceptの現在のEventに行かなければ、自分のchild viewsを巡ってTouchのある領域にあるviewを見つけ、見つけたらgetTouchTarget(View)を通じてViewがTouchTargetにあるかどうかを探します.不在の場合はD i s p a t c h T r a n s formedTouchEvent(ev,false,child,idBitsToAssign)を呼び出し、Eventをchild viewに配布します.
4. childがeventイベントを消費していない場合、mFirstTouchTarget==null)は、handled=d i s p a t c h TransformedTouchEvent(ev,canceled,null,TouchTarget.ALL_POINTER_IDS)を呼び出して自分で今回のEventを処理します.パラメータのchildはnullです.
ViewGroupも処理していない場合は、親Viewに遡って処理します.
public boolean dispatchTouchEvent(MotionEvent ev) {
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
}
boolean handled = false;
if (onFilterTouchEventForSecurity(ev)) {
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
// Handle an initial down.
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Throw away all previous state when starting a new touch gesture.
// The framework may have dropped the up or cancel event for the previous gesture
// due to an app switch, ANR, or some other state change.
cancelAndClearTouchTargets(ev);
resetTouchState();
}
// Check for interception.
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
} else {
... ...
}
// Check for cancelation.
final boolean canceled = resetCancelNextUpFlag(this)
|| actionMasked == MotionEvent.ACTION_CANCEL;
// Update list of touch targets for pointer down, if needed.
final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
TouchTarget newTouchTarget = null;
boolean alreadyDispatchedToNewTouchTarget = false;
if (!canceled && !intercepted) {
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
final int actionIndex = ev.getActionIndex(); // always 0 for down
final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
: TouchTarget.ALL_POINTER_IDS;
// Clean up earlier touch targets for this pointer id in case they
// have become out of sync.
removePointersFromTouchTargets(idBitsToAssign);
final int childrenCount = mChildrenCount;
if (childrenCount != 0) {
// Find a child that can receive the event.
// Scan children from front to back.
final View[] children = mChildren;
final float x = ev.getX(actionIndex);
final float y = ev.getY(actionIndex);
for (int i = childrenCount - 1; i >= 0; i--) {
final View child = children[i];
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
continue;
}
newTouchTarget = getTouchTarget(child); // getTouchTarget View TouchTarget 。
if (newTouchTarget != null) {
// Child is already receiving touch within its bounds.
// Give it the new pointer in addition to the ones it is handling.
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
}
resetCancelNextUpFlag(child);
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// Child wants to receive touch within its bounds.
mLastTouchDownTime = ev.getDownTime();
mLastTouchDownIndex = i;
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
}
}
if (newTouchTarget == null && mFirstTouchTarget != null) {
// Did not find a child to receive the event.
// Assign the pointer to the least recently added target.
newTouchTarget = mFirstTouchTarget;
while (newTouchTarget.next != null) {
newTouchTarget = newTouchTarget.next;
}
newTouchTarget.pointerIdBits |= idBitsToAssign;
}
}
}
// Dispatch to touch targets.
if (mFirstTouchTarget == null) {
// No touch targets so treat this as an ordinary view.
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
// Dispatch to touch targets, excluding the new touch target if we already
// dispatched to it. Cancel touch targets if necessary.
TouchTarget predecessor = null;
TouchTarget target = mFirstTouchTarget;
while (target != null) {
final TouchTarget next = target.next;
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
} else {
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}
if (cancelChild) {
if (predecessor == null) {
mFirstTouchTarget = next;
} else {
predecessor.next = next;
}
target.recycle();
target = next;
continue;
}
}
predecessor = target;
target = next;
}
}
// Update list of touch targets for pointer up or cancel, if needed.
if (canceled
|| actionMasked == MotionEvent.ACTION_UP
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
resetTouchState();
} else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
final int actionIndex = ev.getActionIndex();
final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);
removePointersFromTouchTargets(idBitsToRemove);
}
}
if (!handled && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
}
return handled;
}
2.1.4.2.1.3 ViewGroup.dispatchTransformedTouchEvent()
1. 主にchildを呼び出す.dispatchTouchEvent(transformedEvent);イベントを再帰的に伝え、childがView Groupである場合、ステップは上記と同じであり、ViewであればViewを呼び出す.dispatchTouchEvent(MotionEvent event)
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
// Calculate the number of pointers to deliver.
final int oldPointerIdBits = event.getPointerIdBits();
final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;
// If the number of pointers is the same and we don't need to perform any fancy
// irreversible transformations, then we can reuse the motion event for this
// dispatch as long as we are careful to revert any changes we make.
// Otherwise we need to make a copy.
final MotionEvent transformedEvent;
if (newPointerIdBits == oldPointerIdBits) {
if (child == null || child.hasIdentityMatrix()) {
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
event.offsetLocation(offsetX, offsetY);
handled = child.dispatchTouchEvent(event);
event.offsetLocation(-offsetX, -offsetY);
}
return handled;
}
transformedEvent = MotionEvent.obtain(event);
} else {
transformedEvent = event.split(newPointerIdBits);
}
// Perform any necessary transformations and dispatch.
if (child == null) {
handled = super.dispatchTouchEvent(transformedEvent);
} else {
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
transformedEvent.offsetLocation(offsetX, offsetY);
if (! child.hasIdentityMatrix()) {
transformedEvent.transform(child.getInverseMatrix());
}
handled = child.dispatchTouchEvent(transformedEvent);
}
// Done.
transformedEvent.recycle();
return handled;
}
2.1.4.2.1.3.1View.dispatchTouchEvent(MotionEvent event)
1. TouchListenerがあれば登録されたTouchListenerのコールバック関数onTouchイベントを呼び出して直接返します.これは私たちがtouch eventを受信したいときにlistenerを書く理由です.
2. TouchListenerがない場合、デフォルトのonTouchEventイベントが呼び出されます.
public boolean dispatchTouchEvent(MotionEvent event) {
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(event, 0);
}
if (onFilterTouchEventForSecurity(event)) {
//noinspection SimplifiableIfStatement
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
return true;
}
if (onTouchEvent(event)) {
return true;
}
}
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
}
return false;
}
2.1.4.2.1.4 Activity.onTouchEvent(MotionEvent event)
すべてのViewがTouchEventを処理しない場合、最後にActivityによって処理されます.
mWindowがそのためにこのEventがcloseを落とすかどうかを判断し、closeでなければreturn falseになります.
最後にFinishInputEvent(false)を呼び出し、この処理されていないEventを返します.
public boolean onTouchEvent(MotionEvent event) {
if (mWindow.shouldCloseOnTouch(this, event)) {
finish();
return true;
}
return false;
}