前两天华仔给我出了一道难题,我们俩研究了小半天,借着这个契机正好回顾了一下Android事件分发的相关知识点,于是有了这篇文章。
Android事件分发机制大家都非常熟悉(不熟悉的可以参考我的另外一篇博客《细说Android的事件分发机制》),大部分文章对这个过程的描述都是开始于Activity,但是事件是怎么传到Activity的?
这里就涉及到几个重要的部分:
如果要理解事件分发的源头,就需要搞明白他们之间的关系,所以我们先来看看它们到底有什么关系?
Window是我们比较熟悉的,那么它是如何创建的?
我们来看Activity的attach
函数:
@UnsupportedAppUsage
final void attach(...) {
attachBaseContext(context);
...
mWindow = new PhoneWindow(this, window, activityConfigCallback);
mWindow.setWindowControllerCallback(mWindowControllerCallback);
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
...
mWindow.setWindowManager(
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
mToken, mComponent.flattenToString(),
(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
...
mWindowManager = mWindow.getWindowManager();
...
}
我这里只展示一部分关键代码。当我们的activity创建完成后会执行attach
,这是可以看到创建了PhoneWindow,同时也设置了WindowManager。
注意mWindow.setCallback(this);
这行代码,Activity本身实现了Window.Callback
接口:
public class Activity extends ContextThemeWrapper
implements LayoutInflater.Factory2,
Window.Callback, ... {
这里将activity赋值给window的callback,在后面的流程中会发挥作用。
DecorView是整个布局的最顶端的view,也就根布局。它很容易与ViewRoot搞混,ViewRoot其实不是View,后面再来说它。
DecorView是如何创建的,这一切要从setContentView
说起:
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
可以看到执行了window的setContentView
,它的源码:
@Override
public void setContentView(int layoutResID) {
if (mContentParent == null) {
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
...
}
可以看到一开始就执行installDecor
,这里就是初始化DecorView:
private void installDecor() {
mForceDecorInstall = false;
if (mDecor == null) {
mDecor = generateDecor(-1);
...
} else {
mDecor.setWindow(this);
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
...
}
}
可以看到先通过generateDecor
创建了DecorView:
protected DecorView generateDecor(int featureId) {
Context context;
...
return new DecorView(context, featureId, this, getAttributes());
}
创建时将Window也传入了,所以DecorView中保存了一份Window的引用。
然后回到installDecor
代码中,又执行了generateLayout
,这里创建了mContentParent
:
protected ViewGroup generateLayout(DecorView decor) {
...
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
...
return contentParent;
}
可以看到这个mContentParent
就是ID_ANDROID_CONTENT,所以它才是真正装载我们通过setContentView
所设置的布局那个ViewGroup。所以这个层级应该是:
DecorView -> mContentParent -> 实际布局
通过上面可以看出,Window的创建是在attach环节,而DecorView则是在create环节。目前虽然创建了DecorView,但是还没有真正添加到Window中,而且ViewRoot还没有创建出来,这两步实际上是一起的,下面来看一下。
Activity创建完成后会通知AMS,AMS处理一些事务后会通知Activity显示,这样就会执行ActivityThread
的handleResumeActivity()
:
@Override
public void handleResumeActivity(ActivityClientRecord r, boolean finalStateRequest,
boolean isForward, String reason) {
...
if (r.window == null && !a.mFinished && willBeVisible) {
r.window = r.activity.getWindow();
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor = decor;
...
if (a.mVisibleFromClient) {
if (!a.mWindowAdded) {
a.mWindowAdded = true;
wm.addView(decor, l);
} else {
a.onWindowAttributesChanged(l);
}
}
} else if (!willBeVisible) {
...
}
...
}
这里会通过WindowManager
的addView
函数将DecorView添加到屏幕上。WindowManager
的实现类是WindowManagerImpl
,它的函数代码如下:
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyTokens(params);
mGlobal.addView(view, params, mContext.getDisplayNoVerify(), mParentWindow,
mContext.getUserId());
}
实际上是执行了WindowManagerGlobal
的addView
:
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow, int userId) {
...
ViewRootImpl root;
View panelParentView = null;
synchronized (mLock) {
...
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, userId);
} catch (RuntimeException e) {
...
}
}
}
这里我们看到创建了ViewRootImpl
,这就是ViewRoot。然后将DecorView也传入了,这样ViewRoot就持有了DecorView。
那么ViewRoot到底是什么?
我们可以把看看成是管理DecorView的一个类,比如它初始化的时候得到了一个WindowSession:
public ViewRootImpl(Context context, Display display) {
this(context, display, WindowManagerGlobal.getWindowSession(),
false /* useSfChoreographer */);
}
通过WindowSession可以与WMS进行通信实现一些窗口信息的传递。
另外在它的setView中还创建了一个WindowInputEventReceiver
:
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,
int userId) {
synchronized (this) {
if (mView == null) {
...
InputChannel inputChannel = null;
if ((mWindowAttributes.inputFeatures
& WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
inputChannel = new InputChannel();
}
...
try {
...
res = mWindowSession.addToDisplayAsUser(mWindow, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(), userId,
mInsetsController.getRequestedVisibility(), inputChannel, mTempInsets,
mTempControls);
...
} catch (RemoteException e) {
...
} finally {
...
}
...
if (inputChannel != null) {
...
mInputEventReceiver = new WindowInputEventReceiver(inputChannel,
Looper.myLooper());
...
}
}
}
}
这个就是用来接收事件的,下面我们来顺着这个看看事件是如何分发到view的。
上面创建WindowInputEventReceiver
时,可以看到传入了一个InputChannel
,它创建之后又传入了WindowSession,即WMS。InputChannel
就是底层通知上层事件的核心,系统服务捕获到屏幕事件后,会通过它通知到上层,也就是WindowInputEventReceiver
。
所以WindowInputEventReceiver
是整个事件的源头:
final class WindowInputEventReceiver extends InputEventReceiver {
...
@Override
public void onInputEvent(InputEvent event) {
...
if (processedEvents != null) {
if (processedEvents.isEmpty()) {
// InputEvent consumed by mInputCompatProcessor
finishInputEvent(event, true);
} else {
for (int i = 0; i < processedEvents.size(); i++) {
enqueueInputEvent(
processedEvents.get(i), this,
QueuedInputEvent.FLAG_MODIFIED_FOR_COMPATIBILITY, true);
}
}
} else {
enqueueInputEvent(event, this, 0, true);
}
}
...
}
事件进入它的onInputEvent
后会执行enqueueInputEvent
:
void enqueueInputEvent(InputEvent event,
InputEventReceiver receiver, int flags, boolean processImmediately) {
...
if (processImmediately) {
doProcessInputEvents();
} else {
scheduleProcessInputEvents();
}
}
这里通过判断立即执行还是延迟处理,结果差不多,来看立即执行的代码:
void doProcessInputEvents() {
// Deliver all pending input events in the queue.
while (mPendingInputEventHead != null) {
...
deliverInputEvent(q);
}
...
}
进入deliverInputEvent
:
private void deliverInputEvent(QueuedInputEvent q) {
...
try {
...
InputStage stage;
if (q.shouldSendToSynthesizer()) {
stage = mSyntheticInputStage;
} else {
stage = q.shouldSkipIme() ? mFirstPostImeInputStage : mFirstInputStage;
}
...
if (stage != null) {
handleWindowFocusChanged();
stage.deliver(q);
} else {
finishInputEvent(q);
}
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
可以看到通过stage
进行了deliver
,这个stage
是什么?
在setView
的最后有这么一段代码:
// Set up the input pipeline.
CharSequence counterSuffix = attrs.getTitle();
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;
mPendingInputEventQueueLengthCounterName = "aq:pending:" + counterSuffix;
可以看到是一个套一个,我们重点来看ViewPostImeInputStage
这个:
final class ViewPostImeInputStage extends InputStage {
...
@Override
protected int onProcess(QueuedInputEvent q) {
if (q.mEvent instanceof KeyEvent) {
return processKeyEvent(q);
} else {
final int source = q.mEvent.getSource();
if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) {
return processPointerEvent(q);
} else if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
return processTrackballEvent(q);
} else {
return processGenericMotionEvent(q);
}
}
}
...
private int processPointerEvent(QueuedInputEvent q) {
final MotionEvent event = (MotionEvent)q.mEvent;
mAttachInfo.mUnbufferedDispatchRequested = false;
mAttachInfo.mHandlingPointerEvent = true;
boolean handled = mView.dispatchPointerEvent(event);
...
return handled ? FINISH_HANDLED : FORWARD;
}
...
}
InputStage的deliver
最终会通过onProcess
来区分事件处理(这块就不细说了,没意义),其中我们最关心的事件交给了processPointerEvent
来处理。在这里可以看到执行了mView.dispatchPointerEvent(event)
,这个View就是我们提到的DecorView。这样我们总算找到了源头,下面看看是怎么传递下去的。
dispatchPointerEvent
这个函数是View的一个函数,源码:
public final boolean dispatchPointerEvent(MotionEvent event) {
if (event.isTouchEvent()) {
return dispatchTouchEvent(event);
} else {
return dispatchGenericMotionEvent(event);
}
}
到了我们熟悉的dispatchTouchEvent
了,这样直接就将事件分发到各个View了?并不是,来看看DecorView中这块是如何处理的:
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
final Window.Callback cb = mWindow.getCallback();
return cb != null && !mWindow.isDestroyed() && mFeatureId < 0
? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev);
}
可以看到它通过Window获取了callback,然后执行了callback的dispatchTouchEvent
。
不知道大家还是否记得我们一开始分析Window的创建的时候提到过(不记得可以回到文章开始看一下),Activity本身实现了Window.Callback
接口,并设置给了window的callback。所以这里的callback其实就是Activity,这样事件就传递到了Activity:
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
Activity中之间将事件传递给了Window,调用了它的superDispatchTouchEvent
函数,实际上是PhoneWindow的实现:
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
这样就又传递回DecorView了:
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
在DecorView中执行了super.dispatchTouchEvent(event);
,它的父类就是ViewGroup,这样就进入了我们熟悉的ViewGroup分发事件的环节了。
通过上面的分析,我们彻底理解了事件是怎么传递到Activity,然后又如何分发到View上的。
但是这里有一个需要注意的点,就是Window的setCallBack
函数是对外的,我们可以设置一个自定义的CallBack,但是这样导致Activity这个CallBack被挤掉,结果就是Activity无法接收到事件。
那么事件还能不能分发下去了呢?我们来看看:
getWindow().setCallback(new Window.Callback() {
...
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
return false;
}
...
});
这里不论我们返回true还是false,事件都不会分发下去。根据上面分析我们知道,在Activity中是调用了getWindow().superDispatchTouchEvent(event);
才让事件继续分发的。所以这里我们也可以加上这样的代码,当然最好是调用Activity的dispatchTouchEvent
,保证流程的完整。
经过上面的分析,我们知道事件传递路径大致是
ViewRoot -> DecorView -> Activity(Window.CallBack) -> Window -> DecorView -> ViewGroup -> …
后面就是我们熟悉的事件分发流程。可以参考我的另外一篇博客《细说Android的事件分发机制》