Android Activity是如何接收到touch事件的(窗口与用户输入系统)
zzdr5763
9年前
<p> </p> <p>在 <a href="http://www.open-open.com/lib/view/open1462415483393.html">《浮窗开发之窗口层级》</a> 这片文章中,开篇提出了三个问题:</p> <p><a href="http://www.open-open.com/lib/view/open1462415483393.html">窗口层级关系(浮窗是如何“浮”的) ?</a></p> <p><a href="http://www.open-open.com/lib/view/open1462927389223.html" rel="nofollow,noindex">浮窗有哪些限制,如何越过用户授权实现浮窗功能?</a></p> <p>Activity是如何接收到touch事件的?</p> <p>前两个问题在前两篇文章中已经分析,在这篇文章中我们以第三个问题为切入点,简单分析一下窗口与用户输入的关系。</p> <h2>Touch事件是如何分发到Activity上来的?</h2> <p><img src="https://simg.open-open.com/show/73319517481ca3f17153dd4c0ea5604d.png"></p> <p>正常的思路是直接去寻找Activity 的dispatchTouchEvent方法,我们看看Activity的dispatchTouchEvent()方法的调用栈,在方法中加入Thread.dumpStack()来查看调用栈。</p> <pre> <code class="language-java">@Override public boolean dispatchTouchEvent(MotionEvent ev) { Thread.dumpStack(); return super.dispatchTouchEvent(ev); }</code></pre> <p>输出:</p> <pre> <code class="language-java">05-09 09:35:39.775 8911-8911/com.yinhan.hunter.dksdk W/System.err: java.lang.Throwable: stack dump 05-09 09:35:39.775 8911-8911/com.yinhan.hunter.dksdk W/System.err: at java.lang.Thread.dumpStack(Thread.java:496) 05-09 09:35:39.775 8911-8911/com.yinhan.hunter.dksdk W/System.err: at com.demo.liuguangli.suspendbox.MainActivity.dispatchTouchEvent(MainActivity.java:65) 05-09 09:35:39.775 8911-8911/com.yinhan.hunter.dksdk W/System.err: at com.android.internal.policy.impl.PhoneWindow$DecorView.dispatchTouchEvent(PhoneWindow.java:1901) 05-09 09:35:39.775 8911-8911/com.yinhan.hunter.dksdk W/System.err: at android.view.View.dispatchPointerEvent(View.java:7426) 05-09 09:35:39.775 8911-8911/com.yinhan.hunter.dksdk W/System.err: at android.view.ViewRootImpl.deliverPointerEvent(ViewRootImpl.java:3220) 05-09 09:35:39.775 8911-8911/com.yinhan.hunter.dksdk W/System.err: at android.view.ViewRootImpl.deliverInputEvent(ViewRootImpl.java:3165) 05-09 09:35:39.775 8911-8911/com.yinhan.hunter.dksdk W/System.err: at android.view.ViewRootImpl.doProcessInputEvents(ViewRootImpl.java:4292) 05-09 09:35:39.775 8911-8911/com.yinhan.hunter.dksdk W/System.err: at android.view.ViewRootImpl.enqueueInputEvent(ViewRootImpl.java:4271) 05-09 09:35:39.775 8911-8911/com.yinhan.hunter.dksdk W/System.err: at android.view.ViewRootImpl$WindowInputEventReceiver.onInputEvent(ViewRootImpl.java:4363) 05-09 09:35:39.775 8911-8911/com.yinhan.hunter.dksdk W/System.err: at android.view.InputEventReceiver.dispatchInputEvent(InputEventReceiver.java:179) 05-09 09:35:39.775 8911-8911/com.yinhan.hunter.dksdk W/System.err: at android.os.MessageQueue.nativePollOnce(Native Method) 05-09 09:35:39.775 8911-8911/com.yinhan.hunter.dksdk W/System.err: at android.os.MessageQueue.next(MessageQueue.java:125) 05-09 09:35:39.775 8911-8911/com.yinhan.hunter.dksdk W/System.err: at android.os.Looper.loop(Looper.java:124) 05-09 09:35:39.775 8911-8911/com.yinhan.hunter.dksdk W/System.err: at android.app.ActivityThread.main(ActivityThread.java:5041) 05-09 09:35:39.775 8911-8911/com.yinhan.hunter.dksdk W/System.err: at java.lang.reflect.Method.invokeNative(Native Method) 05-09 09:35:39.775 8911-8911/com.yinhan.hunter.dksdk W/System.err: at java.lang.reflect.Method.invoke(Method.java:511) 05-09 09:35:39.775 8911-8911/com.yinhan.hunter.dksdk W/System.err: at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:793) 05-09 09:35:39.775 8911-8911/com.yinhan.hunter.dksdk W/System.err: at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:560) 05-09 09:35:39.775 8911-8911/com.yinhan.hunter.dksdk W/System.err: at de.robv.android.xposed.XposedBridge.main(XposedBridge.java:132) 05-09 09:35:39.775 8911-8911/com.yinhan.hunter.dksdk W/System.err: at dalvik.system.NativeStart.main(Native Method)</code></pre> <p>一条粗略的线索:ViewRootImpl-deliverInputEvent ->View.dispatchPointerEvent->PhoneWindow$DecorView.dispatchTouchEvent->MainActivity.dispatchTouchEvent,读者可以根据这个线索去跟踪源码。我们这里先不深入其中细节,先来看看DecorView 到 Activity.dispatchTouchEvent 是如何调用的?</p> <p>在《浮窗开发之窗口层级》一文中,我们有讲到Activity、PhoneWindow、DecorView的关系,我们先来回顾一下:</p> <p><img src="https://simg.open-open.com/show/255a2207bec2ec415cc2bbc33ac5f29d.jpg"></p> <p><img src="https://simg.open-open.com/show/f04dde05ea6e56c6f8d59f239a28ed6d.jpg"></p> <p>再来看看DecorView的 dispatchTouchEvent方法:</p> <pre> <code class="language-java">@Override public boolean dispatchTouchEvent(MotionEvent ev) { final Callback cb = getCallback(); return cb != null && !isDestroyed() && mFeatureId < 0 ? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev); }</code></pre> <p>DecorView 是view的子类重写了dispatchTouchEvent方法,在这个方法中调用 Callback,这个Callback是Window的一个静态内部接口类,Activity实现了这个接口,Activity的dispatchTouchEvent() 方法正是从Callback继承而来。</p> <p><img src="https://simg.open-open.com/show/b1997f5b61a447b60263dbdc21794956.png"></p> <h2>Touch事件是如何分发到浮窗的根视图的?</h2> <p>思路同上:dump出根视图的dispatchTouchEvent()方法调用栈:</p> <pre> <code class="language-java">05-09 11:58:50.623 11447-11447/com.float.mall W/System.err: java.lang.Throwable: stack dump 05-09 11:58:50.623 11447-11447/com.float.mall W/System.err: at java.lang.Thread.dumpStack(Thread.java:496) 05-09 11:58:50.623 11447-11447/com.float.mall W/System.err: at com.jym.floatwinplugin.view.widget.FloatBallView.dispatchTouchEvent(FloatBallView.java:250) 05-09 11:58:50.623 11447-11447/com.float.mall W/System.err: at android.view.View.dispatchPointerEvent(View.java:7426) 05-09 11:58:50.623 11447-11447/com.float.mall W/System.err: at android.view.ViewRootImpl.deliverPointerEvent(ViewRootImpl.java:3220) 05-09 11:58:50.623 11447-11447/com.float.mall W/System.err: at android.view.ViewRootImpl.deliverInputEvent(ViewRootImpl.java:3165) 05-09 11:58:50.623 11447-11447/com.float.mall W/System.err: at android.view.ViewRootImpl.doProcessInputEvents(ViewRootImpl.java:4292) 05-09 11:58:50.623 11447-11447/com.float.mall W/System.err: at android.view.ViewRootImpl.enqueueInputEvent(ViewRootImpl.java:4271) 05-09 11:58:50.623 11447-11447/com.float.mall W/System.err: at android.view.ViewRootImpl$WindowInputEventReceiver.onInputEvent(ViewRootImpl.java:4363) 05-09 11:58:50.623 11447-11447/com.float.mall W/System.err: at android.view.InputEventReceiver.dispatchInputEvent(InputEventReceiver.java:179) 05-09 11:58:50.623 11447-11447/com.float.mall W/System.err: at android.os.MessageQueue.nativePollOnce(Native Method) 05-09 11:58:50.623 11447-11447/com.float.mall W/System.err: at android.os.MessageQueue.next(MessageQueue.java:125) 05-09 11:58:50.623 11447-11447/com.float.mall W/System.err: at android.os.Looper.loop(Looper.java:124) 05-09 11:58:50.623 11447-11447/com.float.mall W/System.err: at android.app.ActivityThread.main(ActivityThread.java:5041) 05-09 11:58:50.623 11447-11447/com.float.mall W/System.err: at java.lang.reflect.Method.invokeNative(Native Method) 05-09 11:58:50.623 11447-11447/com.float.mall W/System.err: at java.lang.reflect.Method.invoke(Method.java:511) 05-09 11:58:50.623 11447-11447/com.float.mall W/System.err: at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:793) 05-09 11:58:50.623 11447-11447/com.float.mall W/System.err: at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:560) 05-09 11:58:50.623 11447-11447/com.float.mall W/System.err: at de.robv.android.xposed.XposedBridge.main(XposedBridge.java:132) 05-09 11:58:50.623 11447-11447/com.float.mall W/System.err: at dalvik.system.NativeStart.main(Native Method)</code></pre> <p>对比一下和Activity的关系,是不是发现有些似曾相识。在《浮窗开发之窗口层级》一文中我们讲过Activity的显示和浮窗的显示本质上是将一个View和对应的LayoutParams</p> <p>添加到WindowManagerService中管理。所以Activity的dispatchTouchEvent方法其实是View传递过来的。</p> <p>我们可以猜测粗略线索是:touch事件-》硬件设备-》某个服务-》 ViewRootImpl --》View。</p> <p><img src="https://simg.open-open.com/show/4d362e7ec578157c602614d6fc10829c.png"></p> <h2>ViewRootImpl是个啥?</h2> <p>我们先来看看ViewRootImpl和View到底有啥关系?首先,看看WindowManager的addView方法,WindowManager是个接口,我们看其实现类WindowMangerImpl的源码:</p> <p> </p> <p> </p> <pre> <code class="language-java">….. private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance(); ….. @Override public void addView(View view, ViewGroup.LayoutParams params) { mGlobal.addView(view, params, mDisplay, mParentWindow); }</code></pre> <p>在来看看WindowManagerGlobal的源码:</p> <p> </p> <p> </p> <pre> <code class="language-java">public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) { ViewRootImpl root; View panelParentView = null; ...这里省略了一堆代码 root =new ViewRootImpl(view.getContext(), display); view.setLayoutParams(wparams); mViews.add(view); mRoots.add(root); mParams.add(wparams); // do this last because it fires off messages to start doing things try{ root.setView(view, wparams, panelParentView); }catch(RuntimeException e) { // BadTokenException or InvalidDisplayException, clean up. synchronized(mLock) { final int index = findViewLocked(view,false); if(index >=0) { removeViewLocked(index,true); } } throw e; } }</code></pre> <p>在这里创建了ViewRootImpl对象,并且把传单下来的view通过setView方法设置到其中的变量,来看看setView的源码:</p> <p>public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {</p> <p>synchronized (this) {</p> <p>if (mView == null) {</p> <p>mView = view;</p> <p>…省略一堆代码</p> <p>}</p> <p>}</p> <p>}</p> <p>由此得到关系图:</p> <p><img src="https://simg.open-open.com/show/1a12856cf456b1fac8d5096e58c3ae0d.png"></p> <p>通过WindowManagerImpl.addView,最终把View添加赋值到了ViewRootImpl的变量mView。ViewRootImpl是View(窗口)和WindowManagerService协议的纽带从MVC的角度来看的话:可以认为View是V,ViewRootImpl是Controller,WindowManagerService是Model。View的绘制、刷新都需要通过ViewRootImpl与WindowManagerService交互,另外View的输入事件(键盘、触摸)也是由ViewRootImpl传递给View的,那么ViewRootImpl是如何监听到用户输入事件的呢?</p> <h2>用户输入与窗口</h2> <p>回忆下上文点到的WindowInputEventReceiver,这是ViewRootImpl的一个内部类,我们dump出来的dispatchTouchEvent最初的地方就是源于这个类,再往下就是MessageQueue、Looper的信息。由此可以推断WindowInputEventReceiver是ViewRootImpl和底层某个服务进行IPC交互的关键,这个服务是什么服务呢?</p> <p>这部分涉及到Anddroid系统的两个重要的模块:图形窗口和用户输入,分别对应的服务是WindowManagerService和InputManagerService。WindowManagerService负责图形窗口(View)的绘制、刷新等事物、InputManagerService管理用户输入事件处理。</p> <p>1、InputManagerService 管理者两个角色InputReader和InputDispatcher 。</p> <p>2、InputReader负责从硬件(EventHub)读取输入信号,转化成为事件,传递给InputDispatcher。</p> <p>3、InputDispatcher将InputReader传递过来的事件分发到对应的场景,例如将touch事件分发到ViewRootImpl。</p> <p>那么InputManagerService(InputDispatcher)是如何将touch事件传递到ViewRootImpl(WindowInputEventReceiver)的呢?</p> <p>用户输入事件处理模型是“生产者-消费者“模型,生产者发生在系统进程中,消费者发生在用户进程中。传递过程由IPC交互,这里的通讯是采用的Socket通讯,消费者需要向生产者”注册“通讯管道,RegisterInputChannel建立连接。在ViewRootImpl的setView()方法中创建了WindowInputEventReceiver,并通过WindowManagerService向InputManagerService注册InputChannel监听输入事件。</p> <p><img src="https://simg.open-open.com/show/ec4ad0f8e92059cee9f8175a3599746b.png"></p> <p>TouchEvent事件传递流程 :</p> <p><img src="https://simg.open-open.com/show/6726cb1c7362a1ab1c50a3680501a88b.png"></p> <h2>参考资料:</h2> <p><a href="/misc/goto?guid=4959672860442353334" rel="nofollow,noindex">《Android 的窗口管理系统</a> 》</p> <p><a href="/misc/goto?guid=4959672860535594750" rel="nofollow,noindex">《Android的用户输入处理》</a></p> <p><a href="/misc/goto?guid=4959672860617937691" rel="nofollow,noindex">《Android中MotionEvent的来源和ViewRootImpl》</a></p> <p> </p> <p>来自: <a href="/misc/goto?guid=4959672860705799353" rel="nofollow">http://www.liuguangli.win/archives/491</a></p>