前语
接触事情已经是陈词滥调的事情了,可是或许大部分童鞋都仅仅知道在ViewTree下的机制,可是事情怎样到ViewTree的,或许处于一种比较空白的状态,这系列文章我期望从InputManagerService开端了解事情是怎样一步一步的传递到View中。
本系列分上下部,上部首要环绕应用层,针对Framework之后的事情分发的这段流程来讲,下部分便是环绕IMS,WMS到InputChannel来讲。
这系列文章会协助到咱们了解成个事情从产生到消费的完好流程:
- 了解InputManagerService,了解这个服务到底是做什么的(因篇幅问题而且触及到Framework,本章先不讲这个,下一篇会上)
- 了解ViewRootImpl是怎样接纳到InputManagerService发送的事情的
- 了解Activity是怎样接纳到事情,怎样从DecorView开端分发事情到ViewTree
- 回忆ViewTree的事情分发机制
可是整体介绍我会经过倒序的办法来介绍,越难的东西放在后边,让咱们能够从浅到深去了解事情分发的进程,首先咱们先回忆一下View的接触事情机制吧~
1. 回忆一下View的接触事情机制
看完流程图之后,肯定多多少少唤醒了你对View接触事情机制的回忆,可是没有详细代码感觉仍是有点空虚,接下来咱们就重新对View和ViewGroup,以及分发、阻拦、消费三个办法来一次回忆
1.1 三个办法,两个人物
三个办法:
- dispatchTouchEvent(事情分发)
- onInterceptTouchEvent(事情阻拦)
- onTouchEvent(事情消费)
两个人物:
- View
- dispatchTouchEvent(事情分发)
- onTouchEvent(事情消费)
- 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. 应用层是怎样接纳接触事情的?
思考了好久这儿要怎样说,还不如直接一个断点,把仓库打出来来的实际!
或许有些童鞋一看就直呼卧槽,没联系,咱们仅仅看看整个事情传递的进程是怎样样的,大部分根本都是担任传递的逻辑,终究说一下,不知道为啥DecorView的superDispatchTouchEvent便是断不了点,只能断在上一步PhoneWindow中。
ok,咱们先看看有哪些类参加了传递事情到DecorView:
- PhoneWindow
- Activity
- DecorView(WTF?居然有自己)
- ViewRootImpl
- ViewRootImpl(相关内部类)
- ViewPostImeInputStage
- InputStage
- AsyncInputStage
- WindowInputEventReciver
依据以上的类这儿会触及几个前置的知识点要先说一下,直接说以上几个类童鞋们会有点懵逼。
咱们要先搞清楚一个核心问题,View是到底是怎样烘托到屏幕的,只要View烘托到屏幕,在用户跟屏幕交互的时分,事情才能精确传递到对应的View里。
为了搞清楚上面的核心问题,咱们要先简略的了解下面的问题。
- PhoneWindow是什么,跟Activity的联系是什么?
- DecorView是什么时分初始化的?
- ViewRootImpl又是什么,跟PhoneWindow的联系是什么?
- 了解了上面三点之后,咱们会在ViewRootImpl初始化的进程中,了解到应用层是怎样接纳到WMS传递的接触事情。
- 终究知道了怎样注册之后,再了解事情是怎样一层一层往下传递的?
了解完上面4个问题之后,咱们终究再开端剖析第五点,也便是在应用层事情剖析的源头到传递到DecorView的进程。
咱们要带着以上4个疑问来阅览下面的内容,了解它们的联系,终究意图是了解咱们应用层是怎样注册接触事情的接纳的。
2.1 简略介绍PhoneWindow是什么?
PhoneWindow是什么?
- Window是一个笼统类,详细完成只要一个PhoneWindow类。
- Window是一个笼统的概念,表示了一个窗口,是View Hierarchy(视图树)的容器;
- 对Window的操作只能经过WindowManager,终究经IPC由WindowManagerService终究完成;
- Window不直接办理View Hierarchy,而是经过ViewRootImpl;
咱们再用一张图阐明PhoneWindow与Activity的联系。
再简略说一下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的初始化,总结了以下几点:
- 了解了Activity与PhoneWindow与DecorView的联系。
- Activity完成dispatchTouchEvent办法是由Window.Callback接口声明的,而且Activit将自己作为Callback传进去PhoneWindow中,这阐明晰Activity的接触事情分发是经过了PhoneWindow。
- 了解了DecorView的初始化进程。
2.3 ViewRootImpl
ViewRootImpl是什么,是根据Activity与WindowsManagerService通讯的中介组件,首要担任Activity与WindowsManager的通讯,而且首要担任以下几点:
- 经过WindowManager向WindowManagerService注册窗口。
- 担任了操控整个布局的测量布局制作,而且将窗口制作的成果传输到WindowManager。
- 接纳WindowManagerService的事情回调。
- 本篇核心:向WindowManager注册InputChannel,而且经过InputEventReceiver,接纳Channel中的接触事情
ok,那2,3点咱们本篇暂时不关心,咱们关心第一第四点详细的代码完成是怎样样的。
接着咱们就看看ViewRootImpl的初始化,趁便搞清楚以下几件事
- ViewRootImpl是怎样注册InputChannel,而且怎样样用InputEventReceiver监听InputChannel,取得事情的。
- 搞清楚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. 知道了注册流程后,就去了解事情怎样传递了!
咱们重新看回这张图,我不会完好的讲完每个调用哈~只会说要害的传递,否则篇幅要爆炸了。
咱们看看一开端的调用,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都没有展开来说,由于篇幅问题,我决定深入了解接触事情这个文章分隔两章来说,至于下一章什么时分写出来,那就要看咱们的点赞和重视啦!
首先做一个总结,本篇文章说了以下几点:
- 在第一章中回忆了ViewTree的接触事情机制。
- 在第二章中,咱们了解了在Activity与PhoneWindow与DecorView与ViewRootImpl的联系,与他们初始化的进程,而且了解了应用层的事情是经过ViewRootImpl向WMS注册的。
- 在第三章中,咱们了解事情是怎样一步一步分发到Activity和DecorView,再从DecorView分发到ViewTree中的。
看到这儿的童鞋估量也不容易了,不妨点个赞,收个藏,鼓舞鼓舞作者趁便鼓舞鼓舞自己看完这篇文章,那么有关Framework层的事情相关,就等待下一章吧!谢谢咱们