TouchEvent从何而来 理清Activity、Window、ViewRootImpl、DecorView
我们都知道在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 }
最后我们来看看为什么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的机会