那么InputManager
是如何初始化的呢?这里就要涉及到Java
层级的SystemServer
了,我们知道SystemServer
进程中包含着各种各样的系统服务,比如ActivityManagerService
、WindowManagerService
等等,SystemServer
由zygote
进程启动, 启动过程中对WindowManagerService
和InputManagerService
进行了初始化:
public final class SystemServer {
private void startOtherServices() {
// 初始化 InputManagerService
InputManagerService inputManager = new InputManagerService(context);
// WindowManagerService 持有了 InputManagerService
WindowManagerService wm = WindowManagerService.main(context, inputManager,…);
inputManager.setWindowManagerCallbacks(wm.getInputMonitor());
inputManager.start();
}
}
复制代码
InputManagerService
的构造器中,通过调用native函数,通知native
层级初始化InputManager
:
public class InputManagerService extends IInputManager.Stub {
public InputManagerService(Context context) {
// …通知native层初始化 InputManager
mPtr = nativeInit(this, mContext, mHandler.getLooper().getQueue());
}
// native 函数
private static native long nativeInit(InputManagerService service, Context context, MessageQueue messageQueue);
}
复制代码
SystemServer
会启动窗口管理服务WindowManagerService
,WindowManagerService
在启动的时候就会通过InputManagerService
启动系统输入管理器InputManager
来负责监控键盘消息。
对于本文而言,framework
层级相关如WindowManagerService
(窗口管理服务)、native
层级的源码、SystemServer
亦或者 Binder
跨进程通信并非重点,读者仅需了解 系统服务的启动流程 和 层级关系 即可,参考下图:
3.ViewRootImpl:窗口服务与窗口的纽带
InputManager
将事件分发给当前激活的窗口(Window
)处理,这里我们将前者理解为系统层级的 (窗口)服务,将后者理解为应用层级的 窗口, 因此需要有一个中介负责 服务 和 窗口 之间的通信,于是ViewRootImpl
类应运而生。
ViewRootImpl
作为链接WindowManager
和DecorView
的纽带,同时实现了ViewParent
接口,ViewRootImpl
作为整个控件树的根部,它是View
树正常运作的动力所在,控件的测量、布局、绘制以及输入事件的分发都由ViewRootImpl
控制。
那么ViewRootImpl
是如何被创建和初始化的,而 (窗口)服务 和 窗口 之间的通信又是如何建立的呢?
建立通信
1.ViewRootImpl的创建
既然Android
系统将 (窗口)服务 与 窗口 的通信建立交给了ViewRootImpl
,那么ViewRootImpl
必然持有了两者的依赖,因此了解ViewRootImpl
是如何创建的就非常重要。
我们知道,ActivityThread
负责控制Activity
的启动过程,在ActivityThread.performLaunchActivity()
流程中,ActivityThread
会针对Activity
创建对应的PhoneWindow
和DecorView
实例,而在ActivityThread.handleResumeActivity()
流程中,ActivityThread
会将获取当前Activity
的WindowManager
,并将DecorView
和WindowManager.LayoutParams
(布局参数)作为参数调用addView()
函数:
// 伪代码
public final class ActivityThread {
@Override
public void handleResumeActivity(…){
//…
windowManager.addView(decorView, windowManagerLayoutParams);
}
}
复制代码
WindowManager.addView()
实际上就是对ViewRootImpl
进行了初始化,并执行了setView()
函数:
// 1.WindowManager 的本质实际上是 WindowManagerImpl
public final class WindowManagerImpl implements WindowManager {
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
// 2.实际上调用了 WindowManagerGlobal.addView()
WindowManagerGlobal.getInstance().addView(…);
}
}
public final class WindowManagerGlobal {
public void addView(…) {
// 3.初始化 ViewRootImpl,并执行setView()函数
ViewRootImpl root = new ViewRootImpl(view.getContext(), display);
root.setView(view, wparams, panelParentView);
}
}
public final class ViewRootImpl {
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
// 4.该函数就是控测量(measure)、布局(layout)、绘制(draw)的开始
requestLayout();
// …
// 5.此外还有通过Binder建立通信,这个下文再提
}
}
复制代码
Android
系统的Window
机制并非本文重点,读者可简单理解为ActivityThread.handleResumeActivity()
流程中最终创建了ViewRootImpl
,并通过setView()
函数对DecorView
开始了绘制流程的三个步骤。
2.通信的建立
完成了ViewRootImpl
的创建之后,如何完成系统输入服务和应用程序进程的连接呢?
Android
中Window
和InputManagerService
之间的通信实际上使用的InputChannel
,InputChannel
是一个pipe
,底层实际是通过socket
进行通信。在ViewRootImpl.setView()
过程中,也会同时注册InputChannel
:
public final class InputChannel implements Parcelable { }
复制代码
上文中,我们提到了ViewRootImpl.setView()
函数,在该函数的执行过程中,会在ViewRootImpl
中创建InputChannel
,InputChannel
实现了Parcelable
, 所以它可以通过Binder
传输。具体是通过addDisplay()
将当前window
加入到WindowManagerService
中管理:
public final class ViewRootImpl {
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
requestLayout();
// …
// 创建InputChannel
mInputChannel = new InputChannel();
// 通过Binder在SystemServer进程中完成InputChannel的注册
mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(),
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mInputChannel);
}
}
复制代码
这里涉及到了WindowManagerService
和Binder
跨进程通信,读者不需要纠结于详细的细节,只需了解最终在SystemServer
进程中,WindowManagerService
根据当前的Window
创建了SocketPair
用于跨进程通信,同时并对App
进程中传过来的InputChannel
进行了注册,这之后,ViewRootImpl
里的InputChannel
就指向了正确的InputChannel
, 作为Client
端,其fd
与SystemServer
进程中Server
端的fd
组成SocketPair
, 它们就可以双向通信了。
对该流程感兴趣的读者可以参考 这篇文章。
应用整体的事件分发
App
端与服务端建立了双向通信之后,InputManager
就能够将产生的输入事件从底层硬件分发过来,Android
提供了InputEventReceiver
类,以接收分发这些消息:
public abstract class InputEventReceiver {
// Called from native code.
private void dispatchInputEvent(int seq, InputEvent event, int displayId) {
// …
}
}
复制代码
InputEventReceiver
是一个抽象类,其默认的实现是将接收到的输入事件直接消费掉,因此真正的实现是ViewRootImpl.WindowInputEventReceiver
类:
public final class ViewRootImpl {
final class WindowInputEventReceiver extends InputEventReceiver {
@Override
public void onInputEvent(InputEvent event, int displayId) {
// 将输入事件加入队列
enqueueInputEvent(event, this, 0, true);
}
}
}
复制代码
输入事件加入队列之后,接下来就是对事件的分发了,设计者在这里使用了经典的 责任链 模式:对于一个输入事件的分发而言,必然有其对应的消费者,在这个过程中为了使多个对象都有处理请求的机会,从而避免了请求的发送者和接收者之间的耦合关系。将这些对象串成一条链,并沿着这条链一直传递该请求,直到有对象处理它为止。
InputStage
因此,设计者针对事件分发的整个责任链设计了InputStage
类作为基类,作为责任链中的模版,并实现了若干个子类,为输入事件按顺序分阶段进行分发处理:
// 事件分发不同阶段的基类
abstract class InputStage {
private final InputStage mNext; // 指向事件分发的下一阶段
}
// InputStage的子类,象征事件分发的各个阶段
final class ViewPreImeInputStage extends InputStage {}
final class EarlyPostImeInputStage extends InputStage {}
final class ViewPostImeInputStage extends InputStage {}
final class SyntheticInputStage extends InputStage {}
abstract class AsyncInputStage extends InputStage {}
final class NativePreImeInputStage extends AsyncInputStage {}
final class ImeInputStage extends AsyncInputStage {}
final class NativePostImeInputStage extends AsyncInputStage {}
复制代码
输入事件整体的分发阶段十分复杂,比如当事件分发至SyntheticInputStage
阶段,该阶段为 综合性处理阶段 ,主要针对轨迹球、操作杆、导航面板及未捕获的事件使用键盘进行处理:
final class SyntheticInputStage extends InputStage {
@Override
protected int onProcess(QueuedInputEvent q) {
// 轨迹球
if (…) {
mTrackball.process(event);
return FINISH_HANDLED;
} else if (…) {
// 操作杆
mJoystick.process(event);
return FINISH_HANDLED;
} else if (…) {
// 导航面板
mTouchNavigation.process(event);
return FINISH_HANDLED;
}
// 继续转发事件
return FORWARD;
}
}
复制代码
比如当事件分发至ImeInputStage
阶段,即 输入法事件处理阶段 ,会从事件中过滤出用户输入的字符,如果输入的内容无法被识别,则将输入事件向下一个阶段继续分发:
final class ImeInputStage extends AsyncInputStage {
@Override
protected int onProcess(QueuedInputEvent q) {
if (mLastWasImTarget && !isInLocalFocusMode()) {
// 获取输入法Manager
InputMethodManager imm = InputMethodManager.peekInstance();
final InputEvent event = q.mEvent;
// imm对事件进行分发
int result = imm.dispatchInputEvent(event, q, this, mHandler);
if (result == …) {
// imm消费了该输入事件
return FINISH_HANDLED;
} else {
return FORWARD; // 向下转发
}
}
return FORWARD; // 向下转发
}
}
复制代码
当然还有最熟悉的ViewPostImeInputStage
,即 视图输入处理阶段 ,主要处理按键、轨迹球、手指触摸及一般性的运动事件,触摸事件的分发对象是View,这也正是我们熟悉的 UI层级的事件分发 流程的起点:
final class ViewPostImeInputStage extends InputStage {
private int processPointerEvent(QueuedInputEvent q) {
// 让顶层的View开始事件分发
final MotionEvent event = (MotionEvent)q.mEvent;
boolean handled = mView.dispatchPointerEvent(event);
//…
}
}
复制代码
读到这里读者应该理解了, UI层级的事件分发只是完整事件分发流程的一部分,当输入事件(即使是MotionEvent
)并没有分发到ViewPostImeInputStage
(比如在 综合性处理阶段 就被消费了),那么View
层的事件分发自然无从谈起,这里再将整体的流程图进行展示以方便理解:
组装责任链
现在我们理解了,新分发的事件会通过一个InputStage
的责任链进行整体的事件分发,这意味着,当新的事件到来时,责任链已经组装好了,那么这个责任链是何时进行组装的?
不难得出,对于责任链的组装,最好是在系统服务和Window
建立通信成功的时候,而上文中也提到了,通信的建立是执行在ViewRootImpl.setView()
方法中的,因此在InputChannel
注册成功之后,即可对责任链进行组装:
public final class ViewRootImpl implements ViewParent {
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
// …
// 1.开始根布局的绘制流程
requestLayout();
// 2.通过Binder建立双端的通信
res = mWindowSession.addToDisplay(…)
mInputEventReceiver = new WindowInputEventReceiver(mInputChannel, Looper.myLooper());
// 3.对责任链进行组装
mSyntheticInputStage = new SyntheticInputStage();
InputStage viewPostImeStage = new ViewPostImeInputStage(mSyntheticInputStage);
InputStage nativePostImeStage = new NativePostImeInputStage(viewPostImeStage,
“aq:native-post-ime:” + counterSuffix);
InputStage earlyPostImeStage = new EarlyPostImeInputStage(nativePostImeStage);
InputStage imeStage = new ImeInputStage(earlyPostImeStage,
“aq:ime:” + counterSuffix);
InputStage viewPreImeStage = new ViewPreImeInputStage(imeStage);
InputStage nativePreImeStage = new NativePreImeInputStage(viewPreImeStage,
“aq:native-pre-ime:” + counterSuffix);
mFirstInputStage = nativePreImeStage;
mFirstPostImeInputStage = earlyPostImeStage;
// …
}
}
复制代码
这说明ViewRootImpl.setView()
函数非常重要,该函数也正是ViewRootImpl
本身职责的体现:
- 1.链接
WindowManager
和DecorView
的纽带,更广一点可以说是Window
和View
之间的纽带; - 2.完成
View
的绘制过程,包括measure、layout、draw
过程; - 3.向DecorView分发收到的用户发起的
InputEvent
事件。
最终整体事件分发流程由如下责任链构成:
SyntheticInputStage --> ViewPostImeStage --> NativePostImeStage --> EarlyPostImeStage --> ImeInputStage --> ViewPreImeInputStage --> NativePreImeInputStage
事件分发结果的返回
上文说到,真正从Native
层的InputManager
接收输入事件的是ViewRootImpl
的WindowInputEventReceiver
对象,既然负责输入事件的分发,自然也负责将事件分发的结果反馈给Native
层,作为事件分发的结束:
public final class ViewRootImpl {
final class WindowInputEventReceiver extends InputEventReceiver {
@Override
public void onInputEvent(InputEvent event, int displayId) {
// 【开始】将输入事件加入队列,开始事件分发
enqueueInputEvent(event, this, 0, true);
}
}
}
// ViewRootImpl.WindowInputEventReceiver 是其子类,因此也持有finishInputEvent函数
public abstract class InputEventReceiver {
private static native void nativeFinishInputEvent(long receiverPtr, int seq, boolean handled);
public final void finishInputEvent(InputEvent event, boolean handled) {
//…
// 【结束】调用native层函数,结束应用层的本次事件分发
nativeFinishInputEvent(mReceiverPtr, seq, handled);
}
}
复制代码
ViewPostImeInputStage:UI层事件分发的起点
上文已经提到,UI层级的事件分发 作为 完整事件分发流程的一部分,发生在ViewPostImeInputStage.processPointerEvent
函数中:
final class ViewPostImeInputStage extends InputStage {
private int processPointerEvent(QueuedInputEvent q) {
// 让顶层的View开始事件分发
final MotionEvent event = (MotionEvent)q.mEvent;
boolean handled = mView.dispatchPointerEvent(event);
//…
}
}
复制代码
这个顶层的View
其实就是DecorView
(参见上文 建立通信-ViewRootImpl的创建 小节),读者知道,DecorView
实际上就是Activity
中Window
的根布局,它是一个FrameLayout
。
现在DecorView
执行了dispatchPointerEvent(event)
函数,这是不是就意味着开始了View
的事件分发?
DecorView的双重职责
DecorView
作为View
树的根节点,接收到屏幕触摸事件MotionEvent
时,应该通过递归的方式将事件分发给子View
,这似乎理所当然。但实际设计中,设计者将DecorView
接收到的事件首先分发给了Activity
,Activity
又将事件分发给了其Window
,最终Window
才将事件又交回给了DecorView
,形成了一个小的循环:
// 伪代码
public class DecorView extends FrameLayout {
// 1.将事件分发给Activity
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
return window.getActivity().dispatchTouchEvent(ev)
}
// 4.执行ViewGroup 的 dispatchTouchEvent
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
}
// 2.将事件分发给Window
public class Activity {
public boolean dispatchTouchEvent(MotionEvent ev) {
return getWindow().superDispatchTouchEvent(ev);
}
}
// 3.将事件再次分发给DecorView
public class PhoneWindow extends Window {
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
}
复制代码
事件绕了一个圈子最终回到了DecorView
这里,对于初次阅读这段源码的读者来说,这里的设计平淡无奇,似乎说它莫名其妙也不过分。事实上这里是 面向对象程序设计 中灵活运用 多态 这一特征的有力体现——对于DecorView
而言,它承担了2个职责:
- 1.在接收到输入事件时,
DecorView
不同于其它View
,它需要先将事件转发给最外层的Activity
,使得开发者可以通过重写Activity.onTouchEvent()
函数以达到对当前屏幕触摸事件拦截控制的目的,这里DecorView
履行了自身(根节点)特殊的职责; - 2.从
Window
接收到事件时,作为View
树的根节点,将事件分发给子View
,这里DecorView
履行了一个普通的View
的职责。
实际上,不只是DecorView
,接下来View
层级的事件分发中也运用到了这个技巧,对于ViewGroup
的事件分发来说,其本质是递归思想的体现,在 递流程 中,其本身被视为上游的ViewGroup
,需要自定义dispatchTouchEvent()
函数,并调用child.dispatchTouchEvent(event)
将事件分发给下游的子View
;同时,在 归流程 中,其本身被视为一个View
,需要调用View
自身的方法已决定是否消费该事件(super.dispatchTouchEvent(event)
),并将结果返回上游,直至回归到View
树的根节点,至此整个UI树事件分发流程结束。
同时,读者应该也已理解,平时所说View
层级的事件分发也只是 UI层的事件分发 的一个环节,而 UI层的事件分发 又只是 应用层完整事件分发 的一个小环节,更遑论后者本身又是Native
层和应用层之间的事件分发机制的一部分了。
UI层级事件分发
虽然View
层级之间的事件分发只是 UI层级事件分发 的一个环节,但却是最重要的一个环节,也是本文的重点,上文所有内容都是为本节做系统性的铺垫 ——为了方便阅读,本小节接下来的内容中,事件分发 统一泛指 View层级的事件分发。
1.核心思想
了解 事件分发 的代码流程细节,首先需要了解整个流程的最终目的,那就是 获知事件是否被消费 ,至于事件被哪个角色消费了,怎么被消费的,在外层责任链中的ViewPostImeInputStage
不关心,其更上层ViewRootImpl.WindowInputEventReceiver
不关心,native
层级的InputManager
自然更不会关心了。
因此,设计者设计出了这样一个函数:
// 对事件进行分发
public boolean dispatchTouchEvent(MotionEvent event);
复制代码
对于事件分发结果的接收者而言,其只关心事件是否被消费,因此返回值被定义为了boolean
类型:当返回值为true
,事件被消费,反之则事件未被消费。
上文中我们同样提到了,在ViewGroup
的事件分发过程中,其本身的dispatchTouchEvent(event)
和super.dispatchTouchEvent(event)
完全是两个完全不同的函数,前者履行的是ViewGroup
的职责,负责将事件分发给子View
;后者履行的是View
的职责,负责处理决定事件是否被消费(参见 应用整体的事件分发-DecorView的双重职责 小节)。
因此,对于事件分发整体流程,我们可以进行如下定义:
- 1、
ViewGroup
将事件分发给子View
,当子View
从ViewGroup
中接收到事件,若其有child
,则通过dispatchTouchEvent(event)
再将事件分发给child
…以此类推,直至将事件分发到底部的View
,这也是事件分发的 递流程; - 2、底部的
View
接收到事件时,通过View
自身的dispatchTouchEvent(event)
函数判断是否消费事件: - 2.1 若消费事件,则将结果作为
true
向上层的ViewGroup
返回,ViewGroup
接收到true
,意味着事件已经被消费,因此跳过了是否要消费该事件的判断,直接向上一级继续返回true
,以此类推直到将true
结果通知到最上层的View
节点; - 2.2 若不消费事件,则向上层返回
false
,ViewGroup
接收到false
,意味着事件未被消费,因此其本身执行super.dispatchTouchEvent(event)
——即执行View
本身的dispatchTouchEvent(event)
函数,并将结果向上级返回,以此类推直到将true
结果通知到最上层的View
节点。
对于初次了解事件分发机制或者不熟悉递归思想的读者而言,上述文字似乎晦涩难懂,实际上用代码实现却惊人的简单:
// 伪代码实现
// ViewGroup.dispatchTouchEvent
public boolean dispatchTouchEvent(MotionEvent event) {
boolean consume = false;
// 1.将事件分发给Child
if (hasChild) {
consume = child.dispatchTouchEvent();
}
// 2.若Child不消费该事件,或者没有child,判断自身是否消费该事件
if (!consume) {
consume = super.dispatchTouchEvent();
}
// 3.将结果向上层传递
return consume;
写在最后
由于本文罗列的知识点是根据我自身总结出来的,并且由于本人水平有限,无法全部提及,欢迎大神们能补充~
将来我会对上面的知识点一个一个深入学习,也希望有童鞋跟我一起学习,一起进阶。
提升架构认知不是一蹴而就的,它离不开刻意学习和思考。
**这里,笔者分享一份从架构哲学的层面来剖析的视频及资料分享给大家,**梳理了多年的架构经验,筹备近1个月最新录制的,相信这份视频能给你带来不一样的启发、收获。
最近还在整理并复习一些Android基础知识点,有问题希望大家够指出,谢谢。
希望读到这的您能转发分享和关注一下我,以后还会更新技术干货,谢谢您的支持!
转发+点赞+关注,第一时间获取最新知识点
Android架构师之路很漫长,一起共勉吧!
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门,即可获取!
充~
将来我会对上面的知识点一个一个深入学习,也希望有童鞋跟我一起学习,一起进阶。
提升架构认知不是一蹴而就的,它离不开刻意学习和思考。
**这里,笔者分享一份从架构哲学的层面来剖析的视频及资料分享给大家,**梳理了多年的架构经验,筹备近1个月最新录制的,相信这份视频能给你带来不一样的启发、收获。
[外链图片转存中…(img-UQZ4hQEm-1715356012923)]
[外链图片转存中…(img-u5Stio7H-1715356012924)]
最近还在整理并复习一些Android基础知识点,有问题希望大家够指出,谢谢。
希望读到这的您能转发分享和关注一下我,以后还会更新技术干货,谢谢您的支持!
转发+点赞+关注,第一时间获取最新知识点
Android架构师之路很漫长,一起共勉吧!
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门,即可获取!