浮窗系列之窗口与用户输入系统

在《浮窗开发之窗口层级》这篇文章中,开篇提出了三个问题:

  • 窗口层级关系(浮窗是如何“浮”的)?
  • 浮窗有哪些限制,如何越过用户授权实现浮窗功能?
  • Activity是如何接收到touch事件的?

前两个问题在前两篇文章中已经分析,在这篇文章中我们以第三个问题为切入点,简单分析一下窗口与用户输入的关系。

Touch事件是如何分发到Activity上来的?

正常的思路是直接去寻找Activity 的dispatchTouchEvent方法,我们看看Activity的dispatchTouchEvent()方法的调用栈,在方法中加入Thread.dumpStack()来查看调用栈。

 @Override
  public boolean dispatchTouchEvent(MotionEvent ev) {
    Thread.dumpStack();
    return super.dispatchTouchEvent(ev);
}

输出:

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)

一条粗略的线索:
ViewRootImpl-deliverInputEvent ->View.dispatchPointerEvent->PhoneWindow$DecorView.dispatchTouchEvent->MainActivity.dispatchTouchEvent,读者可以根据这个线索去跟踪源码。我们这里先不深入其中细节,先来看看DecorView 到 Activity.dispatchTouchEvent 是如何调用的?

在《浮窗开发之窗口层级》一文中,我们有讲到Activity、PhoneWindow、DecorView的关系,我们先来回顾一下:

再来看看DecorView的 dispatchTouchEvent方法:

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

DecorView 是view的子类重写了dispatchTouchEvent方法,在这个方法中调用 Callback,这个Callback是Window的一个静态内部接口类,Activity实现了这个接口,Activity的dispatchTouchEvent() 方法正是从Callback继承而来。

Touch事件是如何分发到浮窗的根视图的?

思路同上:dump出根视图的dispatchTouchEvent()方法调用栈:

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)

对比一下和Activity的关系,是不是发现有些似曾相识。在《浮窗开发之窗口层级》一文中我们讲过Activity的显示和浮窗的显示本质上是将一个View和对应的LayoutParams
添加到WindowManagerService中管理。所以Activity的dispatchTouchEvent方法其实是View传递过来的。

我们可以猜测粗略线索是:touch事件-》硬件设备-》某个服务-》 ViewRootImpl --》View。

ViewRootImpl是个啥?

我们先来看看ViewRootImpl和View到底有啥关系?首先,看看WindowManager的addView方法,WindowManager是个接口,我们看其实现类WindowMangerImpl的源码:

…..
private final WindowManagerGlobal mGlobal =     WindowManagerGlobal.getInstance();
…..

@Override
public void addView(View view, ViewGroup.LayoutParams params) {
    mGlobal.addView(view, params, mDisplay, mParentWindow);
}

在来看看WindowManagerGlobal的源码:

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;
  }
}

在这里创建了ViewRootImpl对象,并且把传单下来的view通过setView方法设置到其中的变量,来看看setView的源码:

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
     synchronized (this) {
         if (mView == null) {
             mView = view;
            ...省略一堆代码
         }
     }


由此得到关系图:

通过WindowManagerImpl.addView,最终把View添加赋值到了ViewRootImpl的变量mView。ViewRootImpl是View(窗口)和WindowManagerService协议的纽带
从MVC的角度来看的话:可以认为View是V,ViewRootImpl是Controller,WindowManagerService是Model。View的绘制、刷新都需要通过ViewRootImpl与WindowManagerService交互,另外View的输入事件(键盘、触摸)也是由ViewRootImpl传递给View的,那么ViewRootImpl是如何监听到用户输入事件的呢?

用户输入与窗口

回忆下上文点到的WindowInputEventReceiver,这是ViewRootImpl的一个内部类,我们dump出来的dispatchTouchEvent最初的地方就是源于这个类,再往下就是MessageQueue、Looper的信息。由此可以推断WindowInputEventReceiver是ViewRootImpl和底层某个服务进行IPC交互的关键,这个服务是什么服务呢?

这部分涉及到Anddroid系统的两个重要的模块:图形窗口和用户输入,分别对应的服务是WindowManagerService和InputManagerService。WindowManagerService负责图形窗口(View)的绘制、刷新等事物、InputManagerService管理用户输入事件处理。

1、InputManagerService 管理者两个角色InputReader和InputDispatcher 。
2、InputReader负责从硬件(EventHub)读取输入信号,转化成为事件,传递给InputDispatcher。
3、InputDispatcher将InputReader传递过来的事件分发到对应的场景,例如将touch事件分发到ViewRootImpl。

那么InputManagerService(InputDispatcher)是如何将touch事件传递到ViewRootImpl(WindowInputEventReceiver)的呢?

用户输入事件处理模型是“生产者-消费者“模型,生产者发生在系统进程中,消费者发生在用户进程中。传递过程由IPC交互,这里的通讯是采用的Socket通讯,消费者需要向生产者”注册“通讯管道,RegisterInputChannel建立连接。在ViewRootImpl的setView()方法中创建了WindowInputEventReceiver,并通过WindowManagerService向InputManagerService注册InputChannel监听输入事件。

TouchEvent事件传递流程 :

参考资料:

《Android 的窗口管理系统》
《Android的用户输入处理》
《Android中MotionEvent的来源和ViewRootImpl》

时间: 2024-11-03 19:08:14

浮窗系列之窗口与用户输入系统的相关文章

Activity是如何接收到touch事件的(窗口与用户输入系统)

在<浮窗开发之窗口层级>这片文章中,开篇提出了三个问题: 窗口层级关系(浮窗是如何"浮"的)? 浮窗有哪些限制,如何越过用户授权实现浮窗功能? Activity是如何接收到touch事件的? 前两个问题在前两篇文章中已经分析,在这篇文章中我们以第三个问题为切入点,简单分析一下窗口与用户输入的关系. Touch事件是如何分发到Activity上来的? 正常的思路是直接去寻找Activity 的dispatchTouchEvent方法,我们看看Activity的dispatch

浮窗系列之越过授权使用浮窗

上一片篇文章分析了浮窗系列之Android窗口系统层次.Ativity的窗口和系统窗口的区别,这篇文章我来说说使用系统窗口来实现浮窗的一些限制,我们如何越过这些限制? 简单的浮窗实现 final WindowManager windowManager = getWindowManager(context); //创建自定义浮窗 FloatView hideDialog = new FloatView(context); WindowManager.LayoutParams params = ne

浮窗开发之窗口层级

最近在项目中遇到了这样的需求:需要在特定的其他应用之上悬浮自己的UI交互(拖动.输入等复杂的UI交互),和九游的浮窗类似,不过我们的比九游的体验更好,我们越过了很多授权的限制. 很多人都知道如何去实现一个简单的浮窗,但是却很少有人去深入的研究背后的流程机制,由于项目中浮窗交互比较复杂,遇到了些坑查看了很多资料,故总结浮窗涉及到的知识点: 窗口层级关系(浮窗是如何"浮"的)? 浮窗有哪些限制,如何越过用户授权实现浮窗功能? 窗口与用户输入系统(Activity是如何接收到touch事件的

Core Data浅谈系列之六 : 验证用户输入

在做Web开发时,需要谨记的一条原则是"绝不要相信用户的任何输入"(参见<Essential PHP Security>). 与网页上的表单提交类似,做客户端开发时也应该考虑用户输入,比如可以为UITextField设置代理处理用户实时输入的内容,也可以读取完用户输入再做检查,或者是NSManagedObject的验证功能. 比如,我们可以在Player的实现里提供验证函数:  #define PLAYER_ERROR_DOMAIN @"PLAYER_ERROR_

拍卖出价流程浮窗设计小结

之前在交互周会上,作为项目分享了之前做过的拍卖线的出价流程,由于讲的比较匆忙,所以还是想整理成文字,累积下.做多了日常以后,常常觉得如果不多做字面的积累,很多看似瞬间的累积,很快就会被更多的项目淹没~~ 之所以把这个项目分享出来,是因为觉得我们在工作中所做到的大小日常,99%的都不是"无中生有"的,都具有一定的历史,也一定有一定的痛点,一定也有新的业务变化在里面,如何在短时间内把握这些因素,迅速转化为一个合理的方案,是有一定方法的,虽说方法不一定完美,但希望给大家一些思路. 开始正题,

网页设计中常用的技巧:最常见的固定浮窗设计

文章描述:固定浮窗在设计中的使用. 固定浮窗是网页设计中常用的技巧,指一定区块固定在浏览器的响应位置而不随滚动条的移动而改变位置的设计. 最常见的固定浮窗设计是固定在浏览器底部的弹窗广告,比如新浪首页会有新浪视窗的弹窗广告. 最近当当网首页广告力度很大,其中大部分都是固定在浏览器底部和两边. 由于固定浮窗总是显示在网页内容的最上端,有时候甚至覆盖了用户想要的价值内容,这种设计确实不讨人喜欢.有时候,有的固定浮窗会提供"关闭"按钮,但是这明显不能消除用户的反感情绪,因为在一片广告上找关闭

固定浮窗在设计中的使用

固定浮窗是网页设计中常用的技巧,指一定区块固定在浏览器的响应位置而不随滚动条的移动而改变位置的设计. 最常见的固定浮窗设计是固定在浏览器底部的弹窗广告,比如新浪首页会有新浪视窗的弹窗广告. 最近当当网首页广告力度很大,其中大部分都是固定在浏览器底部和两边. 由于固定浮窗总是显示在网页内容的最上端,有时候甚至覆盖了用户想要的价值内容,这种设计确实不讨人喜欢.有时候,有的固定浮窗会提供"关闭"按钮,但是这明显不能消除用户的反感情绪,因为在一片广告上找关闭,也是一种煎熬,而且关闭按钮总是很小

三星GALAXY A7(A7009)多窗口中将全屏模式转为浮窗模式教程

注意:多窗口中将全屏模式转为浮窗模式是手机支持并且应用程序也要支持,如果有一些应用程序不支持出式窗口的话,那么我们设置也是无效的. 下面我们以这里以"三星应用商店"为例子吧. 1.如下图所示我们在手机中沿屏幕左上角或右上角的对角线方向拖动屏幕的某个角   2.然后我们只要点击弹出式窗口的[圆点]图标可以查看更多选项 如下图所示.    3,进入更多选项之后我们就可以对窗口进行 拖动内容.最小化窗口.最大化窗口.关闭应用程序,这里以点击[最小化窗口]图标为例."三星应用商店&q

三星N9150在多窗口中如何将全屏模式转为浮窗模式

如下所示我们从手机屏幕的对角线的方向然后用手指拖动屏幕的左上角 然后就会直接把窗口转换成弹出式窗口了,如下面小编以"我的剪贴库"为例   2.然后在弹出的窗口界面中我们点击[圆点]就可以查看更多更好选项.    3.之后在打开的图标中我们就可以选择:拖放内容.最小化窗口.最大化窗口.关闭应用程序,这里以点击[最小化窗口]图标为例.    4."我的剪贴库"以一个圆形的浮动图标出现在屏幕上.    好了到了这里关于浮窗模式就介绍完成了,希望例子可以帮助到各位.