我们都知道在android中,touch事件是从顶层的viewGroup通过dispatchTouchEvent一层一层传给子view的。那么顶层的viewGroup是谁?是不是activity?是谁产生了touch事件?这些问题一直徘徊在我脑中,今天就来理一下。 要知道touch事件的来源,就不得不说到activity、window、viewRootImpl、decorView、windowMangaer等一系列的东西,把它们的关系理顺了,才能搞明白touch事件是怎么来的。

先从activity的attach说起,它用于将activity附到activityThread上:

 1 final void attach(Context context, ActivityThread aThread,
 2         Instrumentation instr, IBinder token, int ident,
 3         Application application, Intent intent, ActivityInfo info,
 4         CharSequence title, Activity parent, String id,
 5         NonConfigurationInstances lastNonConfigurationInstances,
 6         Configuration config, String referrer, IVoiceInteractor voiceInteractor) {
 7     attachBaseContext(context);
 8 
 9     mFragments.attachHost(null /*parent*/);
10 
11     mWindow = new PhoneWindow(this);
12     mWindow.setCallback(this);
13     mWindow.setOnWindowDismissedCallback(this);
14     mWindow.getLayoutInflater().setPrivateFactory(this);
15     if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
16         mWindow.setSoftInputMode(info.softInputMode);
17     }
18     if (info.uiOptions != 0) {
19         mWindow.setUiOptions(info.uiOptions);
20     }
21     mUiThread = Thread.currentThread();
22 
23     mMainThread = aThread;
24 
25     mWindow.setWindowManager(
26             (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
27             mToken, mComponent.flattenToString(),
28             (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
29     if (mParent != null) {
30         mWindow.setContainer(mParent.getWindow());
31     }
32     mWindowManager = mWindow.getWindowManager();
33     mCurrentConfig = config;
34 }

我们关注其中三个操作:

1.new一个PhoneWindow赋值给mWindow
2.将mWindow中的mCallback设为当前activity
3.调用mWindow的setWindowManager

重点关注第三条,在window里面,会有一个WindowManager类型的成员叫做mWindowManager,它是在setWindowManager()里通过WindowManagerService这个binder通过远程调用来创建的一个WindowManagerImpl,而这个WindowManagerImpl内部会通过一个static的WindowManagerGlobal去做所有跟WindowManager有关的操作。 简而言之就是,activity中有个window,window中通过mGlobal来跟WMS交互。

那么我们的DecorView和ViewRootImpl是从哪里来的呢?我们在activity里的onCreate里都要写setContentView,它其实调用的是mWindow的SetContentView:

 1 @Override
 2 public void setContentView(int layoutResID) {
 3     // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
 4     // decor, when theme attributes and the like are crystalized. Do not check the feature
 5     // before this happens.
 6     if (mContentParent == null) {
 7         installDecor();
 8     } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
 9         mContentParent.removeAllViews();
10     }
11 
12     if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
13         final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
14                 getContext());
15         transitionTo(newScene);
16     } else {
17         mLayoutInflater.inflate(layoutResID, mContentParent);
18     }
19     mContentParent.requestApplyInsets();
20     final Callback cb = getCallback();
21     if (cb != null && !isDestroyed()) {
22         cb.onContentChanged();
23     }
24 }

这里会installDecor,里面具体就是将Window的mDecor成员new出来,并给它设置一个系统的layout DecorView有了之后,会通过window内部的mLayoutInfater挨个解析xml文件里的Tag,生成对应的View(在这个过程中,子View会把自己的mParent成员指向父View,这个mParent的作用以后在讲invalidate的时候会提到)

那DecorView有了,并且也跟window关联起来了,剩下的ViewRootImpl是哪儿来的呢? 在activityThread的handleLanuchActivity中会调用handlerResumeActivity,接着进一步调用performResumeActivity,接着进入activity的makevisible方法:

1 void makeVisible() {
2     if (!mWindowAdded) {
3         ViewManager wm = getWindowManager();
4         wm.addView(mDecor, getWindow().getAttributes());
5         mWindowAdded = true;
6     }
7     mDecor.setVisibility(View.VISIBLE);
8 }

这时候,会把mDecor加入到activity的mWindowManager(这两个东西已经在上文创建过了)中,vm.addView实际上是调用的window中的WindowManagerGlobal类型的mGloabal成员的addView:

 1 public void addView(View view, ViewGroup.LayoutParams params,
 2         Display display, Window parentWindow) {
 3 
 4     ViewRootImpl root;
 5     View panelParentView = null;
 6 
 7     synchronized (mLock) {
 8         // Start watching for system property changes.
 9 
10         root = new ViewRootImpl(view.getContext(), display);
11 
12         view.setLayoutParams(wparams);
13 
14         mViews.add(view);
15         mRoots.add(root);
16         mParams.add(wparams);
17     }
18 
19     // do this last because it fires off messages to start doing things
20     try {
21         root.setView(view, wparams, panelParentView);
22     } catch (RuntimeException e) {
23         // BadTokenException or InvalidDisplayException, clean up.
24     }
25 }

这里终于把ViewRootImpl给创建出来啦,并且我们可以看到,在WindowManagerGlobal中会维护三个数组,分别是mViews、mRoots、mParams,也就是说每个window在WindowManagerGlobal里都会有对应的根View(DecorView)、ViewRootImpl、对应的LayoutParam,它是怎么管理的?我们先放一边 最后是root.setView方法,它很重要:

 1 public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
 2     synchronized (this) {
 3         if (mView == null) {
 4             mView = view;
 5             try {
 6                 mOrigWindowType = mWindowAttributes.type;
 7                 mAttachInfo.mRecomputeGlobalAttributes = true;
 8                 collectViewAttributes();
 9                 res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
10                         getHostVisibility(), mDisplay.getDisplayId(),
11                         mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
12                         mAttachInfo.mOutsets, mInputChannel);
13             } catch (RemoteException e) {
14                 mAdded = false;
15                 mView = null;
16                 mAttachInfo.mRootView = null;
17                 mInputChannel = null;
18                 mFallbackEventHandler.setView(null);
19                 unscheduleTraversals();
20                 setAccessibilityFocus(null, null);
21                 throw new RuntimeException("Adding window failed", e);
22             } finally {
23                 if (restore) {
24                     attrs.restore();
25                 }
26             }
27 
28             if (mInputChannel != null) {
29                 if (mInputQueueCallback != null) {
30                     mInputQueue = new InputQueue();
31                     mInputQueueCallback.onInputQueueCreated(mInputQueue);
32                 }
33                 mInputEventReceiver = new WindowInputEventReceiver(mInputChannel,
34                         Looper.myLooper());
35             }
36 
37             view.assignParent(this);
38             
39             // Set up the input pipeline.
40             CharSequence counterSuffix = attrs.getTitle();
41             mSyntheticInputStage = new SyntheticInputStage();
42             InputStage viewPostImeStage = new ViewPostImeInputStage(mSyntheticInputStage);
43             InputStage nativePostImeStage = new NativePostImeInputStage(viewPostImeStage,
44                     "aq:native-post-ime:" + counterSuffix);
45             InputStage earlyPostImeStage = new EarlyPostImeInputStage(nativePostImeStage);
46             InputStage imeStage = new ImeInputStage(earlyPostImeStage,
47                     "aq:ime:" + counterSuffix);
48             InputStage viewPreImeStage = new ViewPreImeInputStage(imeStage);
49             InputStage nativePreImeStage = new NativePreImeInputStage(viewPreImeStage,
50                     "aq:native-pre-ime:" + counterSuffix);
51 
52             mFirstInputStage = nativePreImeStage;
53             mFirstPostImeInputStage = earlyPostImeStage;
54             mPendingInputEventQueueLengthCounterName = "aq:pending:" + counterSuffix;
55         }
56     }
57 }

我们关注以下几点:

  • 将mView指向传入的DecorView,并将DecorView的parent指向自己,也就是ViewRootImpl

  • 通过mWindowSession把一个IWindow.Stub提交给WMS,WindowSession是在WindowManagerGlobal中创建的,姑且理解为WMS和WindowManagerGlobal通信的接口(是一个binder),所以其实WMS是通过这个windowSession管理WindowManagerGlobal的,而mGlobal又管理着这个进程的所有window

  • 创建InputChanal、WindowInputEventReceiver以及各种类型的InputStage,隐约觉得和输入事件有关系~

总结

至此,我们知道了,activity中有个phoneWindow,phoneWindow里有一个DecorView,同时window关联一个windowManager,它的本质是WindowManagerGlobal,它通过windowSession来进行WMS与window的交互,同时也会维护三个数组views、roots、params,其中ViewRootImpl就保存在roots中,同时它里面的mView也就是对应的DecorView,而decor的parent反过来就是这个ViewRootImpl。


下一步我们就要探究TouchEvent是怎么传递给我们的view了 首先要知道所有的TouchEvent都是有硬件捕获,然后通过WMS发送给各个window的,在ViewRootImpl的setView中,通过windowSession调用addToDisplay时,会传入一个InputChannel,看一下它的javaDoc:

1 /**
2  * Creates an uninitialized input channel.
3  * It can be initialized by reading from a Parcel or by transferring the state of
4  * another input channel into this one.
5  */
6 public InputChannel() {
7 }

可以看到它就是用来传输input事件的,同时,setView中会通过这个inputChannel创建一个WindowInputEventReceiver,当接收到一个InputEvent的时候,它会调用onInputEvent回调:

 1 @Override
 2 public void onInputEvent(InputEvent event) {
 3     enqueueInputEvent(event, this, 0, true);
 4 }
 5 
 6 
 7 void enqueueInputEvent(InputEvent event,
 8         InputEventReceiver receiver, int flags, boolean processImmediately) {
 9     adjustInputEventForCompatibility(event);
10     QueuedInputEvent q = obtainQueuedInputEvent(event, receiver, flags);
11 
12     QueuedInputEvent last = mPendingInputEventTail;
13     if (last == null) {
14         mPendingInputEventHead = q;
15         mPendingInputEventTail = q;
16     } else {
17         last.mNext = q;
18         mPendingInputEventTail = q;
19     }
20     mPendingInputEventCount += 1;
21     Trace.traceCounter(Trace.TRACE_TAG_INPUT, mPendingInputEventQueueLengthCounterName,
22             mPendingInputEventCount);
23 
24     if (processImmediately) {
25         doProcessInputEvents();
26     } else {
27         scheduleProcessInputEvents();
28     }
29 }

最终调用enqueueInputEvent,其实就是将这个event加入到一个链表中,然后调用doProcessInputEvents,它会循环取出链表中的InputEvent,然后inputStage的deliver方法,这个InputStage是一个链式的调用,之前在setView中创建了一坨stage,它们一个接着一个串起来,每次forward给下一个stage,直到没有下一个,每个stage调用自己的onProcess来做自己想要做的事,那我们看一下关于将InputEvent传递给View层次的stage是怎么做的:

 1 /**
 2  * Delivers post-ime input events to the view hierarchy.
 3  */
 4 final class ViewPostImeInputStage extends InputStage {
 5     public ViewPostImeInputStage(InputStage next) {
 6         super(next);
 7     }
 8 
 9     @Override
10     protected int onProcess(QueuedInputEvent q) {
11         if (q.mEvent instanceof KeyEvent) {
12             return processKeyEvent(q);
13         } else {
14             // If delivering a new non-key event, make sure the window is
15             // now allowed to start updating.
16             handleDispatchWindowAnimationStopped();
17             final int source = q.mEvent.getSource();
18             if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) {
19                 return processPointerEvent(q);
20             } else if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
21                 return processTrackballEvent(q);
22             } else {
23                 return processGenericMotionEvent(q);
24             }
25         }
26     }
27 }

可以看到,这里会区分这个event的类型,TouchEvent是走processPointerEvent分支:

 1 private int processPointerEvent(QueuedInputEvent q) {
 2     final MotionEvent event = (MotionEvent)q.mEvent;
 3 
 4     mAttachInfo.mUnbufferedDispatchRequested = false;
 5     boolean handled = mView.dispatchPointerEvent(event);
 6     if (mAttachInfo.mUnbufferedDispatchRequested && !mUnbufferedInputDispatch) {
 7         mUnbufferedInputDispatch = true;
 8         if (mConsumeBatchedInputScheduled) {
 9             scheduleConsumeBatchedInputImmediately();
10         }
11     }
12     return handled ? FINISH_HANDLED : FORWARD;
13 }
我就问你还记得大明湖畔ViewRootImpl里面的那个mView吗,没错,她就是我们的DecorView。这样,一个TouchEvent就从WMS通过InputChannel交给了WindowInputReceiver,然后再扔给这个ViewPostImeInputStage,最终到达了DecorView的DispatchTouchEvent里了

最后我们来看看为什么Activity里可以重写onTouchEvent,不能重写onInterceptTouchEvent呢?

我们接着上文的mView.dispatchPointerEvent(event),看看decorView怎么做的:

1 @Override
2 public boolean dispatchTouchEvent(MotionEvent ev) {
3     final Callback cb = getCallback();
4     return cb != null && !isDestroyed() && mFeatureId < 0 ? cb.dispatchTouchEvent(ev)
5             : super.dispatchTouchEvent(ev);
6 }

还记得PhoneWindow里的那个mCallback么,对了,那就是我们的activity啊!看看activity里的dispatchTouchEvent:

 1 /**
 2  * Called to process touch screen events.  You can override this to
 3  * intercept all touch screen events before they are dispatched to the
 4  * window.  Be sure to call this implementation for touch screen events
 5  * that should be handled normally.
 6  *
 7  * @param ev The touch screen event.
 8  *
 9  * @return boolean Return true if this event was consumed.
10  */
11 public boolean dispatchTouchEvent(MotionEvent ev) {
12     if (ev.getAction() == MotionEvent.ACTION_DOWN) {
13         onUserInteraction();
14     }
15     if (getWindow().superDispatchTouchEvent(ev)) {
16         return true;
17     }
18     return onTouchEvent(ev);
19 }

看到它其实是调用了phoneWindow的superDispatchTouchEvent:

1 @Override
2 public boolean superDispatchTouchEvent(MotionEvent event) {
3     return mDecor.superDispatchTouchEvent(event);
4 }

也就是很简单的调用了DecorView的super(也就是ViewGroup)的dispatchEvent

直到最后,如果DecorView没有消耗这个Event,才会调用activity自己的onTouchEvent,饶了一圈,decorView还是会通过ViewGroup的dispatchTouchEvent来分发事件,其实activity的onTouchEvent是给了一个DecorView最后一次处理TouchEvent的机会