前语

接触事情已经是陈词滥调的事情了,可是或许大部分童鞋都仅仅知道在ViewTree下的机制,可是事情怎样到ViewTree的,或许处于一种比较空白的状态,这系列文章我期望从InputManagerService开端了解事情是怎样一步一步的传递到View中。

本系列分上下部,上部首要环绕应用层,针对Framework之后的事情分发的这段流程来讲,下部分便是环绕IMS,WMS到InputChannel来讲。

这系列文章会协助到咱们了解成个事情从产生到消费的完好流程:

  1. 了解InputManagerService,了解这个服务到底是做什么的(因篇幅问题而且触及到Framework,本章先不讲这个,下一篇会上)
  2. 了解ViewRootImpl是怎样接纳到InputManagerService发送的事情的
  3. 了解Activity是怎样接纳到事情,怎样从DecorView开端分发事情到ViewTree
  4. 回忆ViewTree的事情分发机制

可是整体介绍我会经过倒序的办法来介绍,越难的东西放在后边,让咱们能够从浅到深去了解事情分发的进程,首先咱们先回忆一下View的接触事情机制吧~

1. 回忆一下View的接触事情机制

Android深入了解触摸事件(一)

看完流程图之后,肯定多多少少唤醒了你对View接触事情机制的回忆,可是没有详细代码感觉仍是有点空虚,接下来咱们就重新对View和ViewGroup,以及分发、阻拦、消费三个办法来一次回忆

1.1 三个办法,两个人物

三个办法:

  1. dispatchTouchEvent(事情分发)
  2. onInterceptTouchEvent(事情阻拦)
  3. onTouchEvent(事情消费)

两个人物:

  1. View
    • dispatchTouchEvent(事情分发)
    • onTouchEvent(事情消费)
  2. ViewGroup
    • dispatchTouchEvent(事情分发)
    • onInterceptTouchEvent(事情阻拦)
    • onTouchEvent(事情消费)

咱们能够看出来,两个人物的功能设计上仍是多多少少有点差异的,View中不包括事情阻拦,乃至都不重视事情分发,只重视事情是怎样消费的,而ViewGroup则需求重视分发、阻拦与消费。

接下来展现一下两个人物与对应办法的伪代码,协助咱们简略了解这两个人物详细的完成:

  • View
    • dispatchTouchEvent
    #View dispatchTouchEvent 伪代码
    public boolean dispatchTouchEvent(MotionEvent event) {
        //对于View来说,由于没有子View了,所以直接调用自己的onTouchEvent
        return onTouchEvent(event);
    }
    
    • onTouchEvent
    #View onTouchEvent 伪代码
    public boolean onTouchEvent(MotionEvent event) {
        //判别有没设置点击事情,有的话默认消费事情
        if(onClickListenr!=null){
            onClickListener.onClick(this);
            return true;
        }
        //判别有没设置Clickable标志位,有的话默认消费该事情
        return isClickable;
    }
    
  • ViewGroup
    • dispatchTouchEvent
    #ViewGroup dispatchTouchEvent 伪代码
    public boolean dispatchTouchEvent(MotionEvent event) {
        //先判别是否阻拦时刻,假如是的话则不分发事情,而且调用自己的onTouchEvent
        if(onInterceptEvent(event)){
            this.onTouchEvent(event);
            return false;
        }else {
            //假如本身不阻拦事情,则开端递归在点击范围内的子控件,调用他们的dispatchTouchEvent分发事情
            for(child in legalChildren){
                //假如子类消费时刻,则返回true,当时办法出栈
                if(child.dispatchTouchEvent(event)){
                    return true;
                }
            }
        }
        //假如本身不阻拦,可是子类又不消费的时分,则调用本身onTouchEvent办法,决定对错消费
        return this.onTouchEvent();
    }
    
    • onInterceptEvent
    #ViewGroup onInterceptTouchEvent 伪代码
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        if (需求阻拦) {
            return true;
        }
        return false;
    }
    
    • onTouchEvent(与View共同就不重复贴了)

Ok,那么流程图已经清晰将大流程论述清楚,也经过伪代码去阐明晰程序详细怎样履行的,当然这儿屏蔽了很多细节,只重视事情的分发、阻拦、消费这三件事来讲,相信结合伪代码来看流程图,很快就能够掌握到ViewTree的事情分发流程是怎样样的。

那么接下来咱们抛出一个疑问,DecorView的事情究竟是谁传给它的呢?接下来咱们狠狠解剖一下!往着进阶的知识点进发!

2. 应用层是怎样接纳接触事情的?

思考了好久这儿要怎样说,还不如直接一个断点,把仓库打出来来的实际!

Android深入了解触摸事件(一)

或许有些童鞋一看就直呼卧槽,没联系,咱们仅仅看看整个事情传递的进程是怎样样的,大部分根本都是担任传递的逻辑,终究说一下,不知道为啥DecorView的superDispatchTouchEvent便是断不了点,只能断在上一步PhoneWindow中。

ok,咱们先看看有哪些类参加了传递事情到DecorView:

  1. PhoneWindow
  2. Activity
  3. DecorView(WTF?居然有自己)
  4. ViewRootImpl
  5. ViewRootImpl(相关内部类)
    1. ViewPostImeInputStage
    2. InputStage
    3. AsyncInputStage
    4. WindowInputEventReciver

依据以上的类这儿会触及几个前置的知识点要先说一下,直接说以上几个类童鞋们会有点懵逼。

咱们要先搞清楚一个核心问题,View是到底是怎样烘托到屏幕的,只要View烘托到屏幕,在用户跟屏幕交互的时分,事情才能精确传递到对应的View里。

为了搞清楚上面的核心问题,咱们要先简略的了解下面的问题。

  1. PhoneWindow是什么,跟Activity的联系是什么?
  2. DecorView是什么时分初始化的?
  3. ViewRootImpl又是什么,跟PhoneWindow的联系是什么?
  4. 了解了上面三点之后,咱们会在ViewRootImpl初始化的进程中,了解到应用层是怎样接纳到WMS传递的接触事情。
  5. 终究知道了怎样注册之后,再了解事情是怎样一层一层往下传递的?

了解完上面4个问题之后,咱们终究再开端剖析第五点,也便是在应用层事情剖析的源头到传递到DecorView的进程。

咱们要带着以上4个疑问来阅览下面的内容,了解它们的联系,终究意图是了解咱们应用层是怎样注册接触事情的接纳的。

2.1 简略介绍PhoneWindow是什么?

PhoneWindow是什么?

  • Window是一个笼统类,详细完成只要一个PhoneWindow类。
  • Window是一个笼统的概念,表示了一个窗口,是View Hierarchy(视图树)的容器;
  • 对Window的操作只能经过WindowManager,终究经IPC由WindowManagerService终究完成;
  • Window不直接办理View Hierarchy,而是经过ViewRootImpl;

咱们再用一张图阐明PhoneWindow与Activity的联系。

Android深入了解触摸事件(一)

再简略说一下PhoneWindow是在Activity的什么时分初始化的

//这儿完成了Window的Callback接口,让Activity具有了事情监听回调办法
public class Activity implements Window.Callback{
    final void attach(Context context,...)
        //省掉部分代码
        mWindow = new PhoneWindow(this, window, activityConfigCallback);
        //这个Callback其间一个要害便是定义了接触事情办法
        mWindow.setCallback(this);
        //为PhoneWindow设置WM
        mWindow.setWindowManager(
                (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
                mToken, mComponent.flattenToString(),
                (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
    }
}
//咱们再来看看这个Callback有哪些要害的办法
 public interface Callback {
 //让Activity具有了接受事情分发的才能
  public boolean dispatchTouchEvent(MotionEvent event);
 }

经过上面代码咱们能够得知,PhoneWindow在Activity的attach办法中进行了初始化,而且Activity作为Window.Callback的身份设置进去,而Window.Callback其间一个需求重写的办法是dispatchTouchEvent,那么能够阐明,Activity的事情分发至少跟PhoneWindow有联系的。

2.2 DecorView在什么时分初始化的?

答案是从Activity的setContentView开端的,咱们一步一步跟。

public class Activity{
    public void setContentView(@LayoutRes int layoutResID) {
        getWindow().setContentView(layoutResID);
        initWindowDecorActionBar();
    }
}

这儿调用了getWindow(),而getWindow其实取得的便是PhoneWindow,那么咱们进入PhoneWindow的setContentView看看。

class PhoneWindow extends Window {
    @Override
    public void setContentView(int layoutResID) {
        //1.假如mContentParent为空,就初始化DecorView
        if (mContentParent == null) {
            installDecor();
        }
        //2.把咱们的布局装载到decorView的content区域
        mLayoutInflater.inflate(layoutResID, mContentParent);
    }
    private void installDecor() {
        //省掉无关代码...
        //1.1假如DecorView为空就生成一个DecorView
        if (mDecor == null) {
            mDecor = generateDecor(-1);
        }
        //1.2假如ContentView为空就从DecordView中获取ContentView
        if (mContentParent == null) {
            mContentParent = generateLayout(mDecor);
        }
    }
}

简略说完了PhoneWindow与DecorView的初始化,总结了以下几点:

  1. 了解了Activity与PhoneWindow与DecorView的联系。
  2. Activity完成dispatchTouchEvent办法是由Window.Callback接口声明的,而且Activit将自己作为Callback传进去PhoneWindow中,这阐明晰Activity的接触事情分发是经过了PhoneWindow。
  3. 了解了DecorView的初始化进程。

2.3 ViewRootImpl

ViewRootImpl是什么,是根据Activity与WindowsManagerService通讯的中介组件,首要担任Activity与WindowsManager的通讯,而且首要担任以下几点:

  1. 经过WindowManager向WindowManagerService注册窗口。
  2. 担任了操控整个布局的测量布局制作,而且将窗口制作的成果传输到WindowManager。
  3. 接纳WindowManagerService的事情回调。
  4. 本篇核心:向WindowManager注册InputChannel,而且经过InputEventReceiver,接纳Channel中的接触事情

ok,那2,3点咱们本篇暂时不关心,咱们关心第一第四点详细的代码完成是怎样样的。

接着咱们就看看ViewRootImpl的初始化,趁便搞清楚以下几件事

  1. ViewRootImpl是怎样注册InputChannel,而且怎样样用InputEventReceiver监听InputChannel,取得事情的。
  2. 搞清楚ViewRootImpl与PhoneWindow的联系。

ViewRootImpl的一切的根源在ActivityThread的handleResumeActivity中开端的。

public final class ActivityThread{
    @Override
    public void handleResumeActivity(ActivityClientRecord r) {
        final Activity a = r.activity;
        if (r.window == null && !a.mFinished && willBeVisible) {
            r.window = r.activity.getWindow();
            View decor = r.window.getDecorView();
            if (a.mVisibleFromClient) {
                if (!a.mWindowAdded) {
                    a.mWindowAdded = true;
                    //上面仅仅获取目标的逻辑,重点是下面这儿会调用WindowManagerGlobal的addView
                    wm.addView(decor);
                } 
            }
        }  
    }
}
//咱们接着wm.addView往下跟
public class WindowManagerGlobal{
    public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow, int userId) {
        ViewRootImpl root;
        synchronized (mLock) {
            //这个IWindowSession其实便是跟WMS通讯用的
            IWindowSession windowlessSession = null;
            if (wparams.token != null) {
                for (int i = 0; i < mWindowlessRoots.size(); i++) {
                    ViewRootImpl maybeParent = mWindowlessRoots.get(i);
                    if (maybeParent.getWindowToken() == wparams.token) {
                        windowlessSession = maybeParent.getWindowSession();
                        break;
                    }
                }
            }
            //创立一个ViewRootImpl目标
            if (windowlessSession == null) {
                root = new ViewRootImpl(view.getContext(), display);
            } else {
                root = new ViewRootImpl(view.getContext(), display,
                        windowlessSession);
            }
            try {
                //调用其ViewRootImpl目标的setView函数
                root.setView(view, wparams, panelParentView, userId);
            } catch (RuntimeException e) {
                throw e;
            }
        }
    }
}

接着便是核心看看ViewRootImpl的setView办法,这儿便是向AMS监听接触事情的要害。

public final class ViewRootImpl{
    //重视该办法
    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,
            int userId) {
        synchronized (this) {
            if (mView == null) {
                //1.我去,总算看到你了,InputChannel,这个便是跟WMS通讯接触事情的目标
                InputChannel inputChannel = null;
                if ((mWindowAttributes.inputFeatures
                        & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
                    //2.进行初始化
                    inputChannel = new InputChannel();
                }
                try {
                    //3.将inputChannel注册到WMS接纳信息
                    res = mWindowSession.addToDisplayAsUser(mWindow,inputChannel,...省掉部分参数);
                } catch (RemoteException e) {
                }
                if (inputChannel != null) {
                    if (mInputQueueCallback != null) {
                        mInputQueue = new InputQueue();
                        mInputQueueCallback.onInputQueueCreated(mInputQueue);
                    }
                    //4.经过WindowInputEventReceiver类来署理接触事情通讯的行为
                    mInputEventReceiver = new WindowInputEventReceiver(inputChannel,
                            Looper.myLooper());
                }
            }
        }
    }
}

这儿源码有点长,以上源码其实便是讲清楚了咱们一开端断点仓库里边,在应用层的接纳到事情的开端WindowInputEventReceiver这个类,是怎样注册的!

3. 知道了注册流程后,就去了解事情怎样传递了!

咱们重新看回这张图,我不会完好的讲完每个调用哈~只会说要害的传递,否则篇幅要爆炸了。

Android深入了解触摸事件(一)

咱们看看一开端的调用,InputEventReceiver,其实便是WindowINputEventReceiver的父类咱们看看其一开端回调的办法。

public abstract class InputEventReceiver {
    //1.这儿其实是调用了子类复写的onInputEvent
    private void dispatchInputEvent(int seq, InputEvent event) {
        mSeqMap.put(event.getSequenceNumber(), seq);
        //1.1咱们往下跟
        onInputEvent(event);
    }
}
final class WindowInputEventReceiver extends InputEventReceiver {
        @Override
        public void onInputEvent(InputEvent event) {
            //...省掉部分代码
            //2.这儿其实是调用了外部类ViewRootImpl的办法
            enqueueInputEvent(event, this, 0, true);
        }
}
class ViewRootImpl{
    void enqueueInputEvent(InputEvent event){
        //...省掉代码
        //3.是否当即履行
        if (processImmediately) {
            //3.1 当即履行
            doProcessInputEvents();
        } else {
            //3.2 推迟履行,终究仍是会履行doProcessInputEvents办法
            scheduleProcessInputEvents();
        }
    }
}

接下来省掉一部分传递流程,有兴趣的童鞋能够去看看源码,我直接用流程图来简化这一段描述

graph TB
    ViewRootImpl.doProcessInputEvents --> ViewRootImpl.deliverInputEvent --> ViewRootImpl.ViewPostImeInputStage#内部类#.onProcess --> ViewRootImpl.ViewPostImeInputStage#内部类#.processPointerEvent 

Ok,然后就到了有关DecorView这儿了,咱们看看ViewRootImpl.ViewPostImeInputStage#内部类#.processPointerEvent 详细完成。

#ViewRootImpl.ViewPostImeInputStage#内部类#.processPointerEvent
private int processPointerEvent(QueuedInputEvent q) {
    final MotionEvent event = (MotionEvent)q.mEvent;
    //...省掉部分代码
    //履行decorView的dispatchPointerEvent,开端分发事情,返回值表示事情是否有view处理
    boolean handled = mView.dispatchPointerEvent(event);
    maybeUpdatePointerIcon(event);
    maybeUpdateTooltip(event);
    mAttachInfo.mHandlingPointerEvent = false;
    return handled ? FINISH_HANDLED : FORWARD;
}

接着看看DecorView代码

//事实上DecorView没完成这个办法,调的是父类View的办法
#View.dispatchPointerEvent
public final boolean dispatchPointerEvent(MotionEvent event) {
    if (event.isTouchEvent()) {//假如是接触事情则是true
        return dispatchTouchEvent(event)
    } else {
        return dispatchGenericMotionEvent(event);
    }
}

接下来看看dispatchTouchEvent办法

#DecorView
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
   //这个mWindow是PhoneWindow,还记得咱们讲PhoneWindow初始化流程吗,这个Callback其实便是Activity,
   final Window.Callback cb = mWindow.getCallback();
   //调用:Activity.dispatchTouchEvent
   return cb != null && !mWindow.isDestroyed() && mFeatureId < 0
                ? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev);
}

总算到了Activity接纳事情了!咱们回到熟悉的当地啦!

#Activity
public boolean dispatchTouchEvent(MotionEvent ev) {
    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
        onUserInteraction();
    }
    //调用PhoneWindow的superDispatchTouchEvent办法,实际上是开端让DecorView
    if (getWindow().superDispatchTouchEvent(ev)) {
        return true;
    }
    //假如没有View消费,则调用Activity自己的onTouchEvent办法
    return onTouchEvent(ev);
}

快到尾声了,咱们再看看PhoneWindow的superDispatchTouchEvent

#PhoneWindow
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
    //看吧,终究便是调用的DecorView的superDispatchTouchEvent
    return mDecor.superDispatchTouchEvent(event);
}

快完毕了,事情来到了DecorView

#DecorView
public boolean superDispatchTouchEvent(MotionEvent event) {
    //完毕了,去到ViewTree的流程了这儿调用的是ViewGroup的dispatchTouchEvent
    return super.dispatchTouchEvent(event);
}

本次接触事情快递到站,回到了第一章的ViewTree接触事情流程!

#ViewGroup dispatchTouchEvent 伪代码
public boolean dispatchTouchEvent(MotionEvent event) {
    //先判别是否阻拦时刻,假如是的话则不分发事情,而且调用自己的onTouchEvent
    if(onInterceptEvent(event)){
        this.onTouchEvent(event);
        return false;
    }else {
        //假如本身不阻拦事情,则开端递归在点击范围内的子控件,调用他们的dispatchTouchEvent分发事情
        for(child in legalChildren){
            //假如子类消费时刻,则返回true,当时办法出栈
            if(child.dispatchTouchEvent(event)){
                return true;
            }
        }
    }
    //假如本身不阻拦,可是子类又不消费的时分,则调用本身onTouchEvent办法,决定对错消费
    return this.onTouchEvent();
}

祝贺,完毕了,可是仅仅完毕了一半,本篇仅仅针对应用层的完好事情传递做一个简易版的流程剖析,可是对WMS、对IMS乃至对InputChannel都没有展开来说,由于篇幅问题,我决定深入了解接触事情这个文章分隔两章来说,至于下一章什么时分写出来,那就要看咱们的点赞和重视啦!

首先做一个总结,本篇文章说了以下几点:

  1. 在第一章中回忆了ViewTree的接触事情机制。
  2. 在第二章中,咱们了解了在Activity与PhoneWindow与DecorView与ViewRootImpl的联系,与他们初始化的进程,而且了解了应用层的事情是经过ViewRootImpl向WMS注册的。
  3. 在第三章中,咱们了解事情是怎样一步一步分发到Activity和DecorView,再从DecorView分发到ViewTree中的。

看到这儿的童鞋估量也不容易了,不妨点个赞,收个藏,鼓舞鼓舞作者趁便鼓舞鼓舞自己看完这篇文章,那么有关Framework层的事情相关,就等待下一章吧!谢谢咱们