Input事件输入系统之应用MotionEvent触摸事件处理流程
输入事件一般分为KeyEvent按键事件,和MotionEvent触摸事件,也就是我们平时说的touch事件,事件的传递流程相当复杂庞大,今天一节课的内容也说不了多少,我们先占时绕开那个庞大的系统来谈谈上层应用如何接收到MotionEvent事件然后处理的,在讲这个之前我们先提几个相关类:
Window,WindowManager,PhoneWindow,IWindowManager,WindowManagerService,咋一看,这些东西都是与Window有关的,平时我们用的也蛮多的也就是WindowManager对象,这些东西是怎么如何我们的MotionEvent事件相关联起来的,这就是我们接下来分析的重点。由于本课程我们就是简单的分析下应用层的输入输出事件的分析,所以Input系统的硬件抽象层我们只是简答的提一下。等下次专题的时候在重点分析该流程的通信协议。
由于有的同学已经知道了Activity的启动流程,那么我们就抽出与我们事件传递相关的一点点东西,view的加载流程,结合我们之前提到的几个window的概念来分析与MotionEvent事件的联系
首先我们来看下Activity的attach函数
。。。。。。。。。。中间省略
mWindow.setWindowManager(
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
mToken, mComponent.flattenToString(),
(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
if (mParent != null) {
mWindow.setContainer(mParent.getWindow());
}
mWindowManager = mWindow.getWindowManager();
private Window mWindow;
由上面的方法里面可知,原来我们是new了一个PhoneWindow对象,而我们的PhoneWindow就是Window抽象类的继承类,注意我们标注为红色的地方是很重要的一个接口回调设置,因为事件传入Activity需要这个接口回调的传递,这点后面我们会继续分析。
那么我们接下来分析activity中的onCreate方法 中的setContentView方法
从字面意思上我们可以理解为填充内容的view,那么他是怎么填充进去的,我们继续分析
public void setContentView(@LayoutRes int layoutResID) {原来他是通过PhoneWindow来调用setContetnView方法的,那么我们继续分析该方法
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
@Override public void setContentView(int layoutResID) { // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window // decor, when theme attributes and the like are crystalized. Do not check the feature // before this happens. if (mContentParent == null) { installDecor(); } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) { mContentParent.removeAllViews(); } if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) { final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID, getContext()); transitionTo(newScene); } else { mLayoutInflater.inflate(layoutResID, mContentParent); } mContentParent.requestApplyInsets(); final Callback cb = getCallback(); if (cb != null && !isDestroyed()) { cb.onContentChanged(); } mContentParentExplicitlySet = true; }
相信很多同学都知道该方法,其实就是实例化一个DecoreView,然后把我们的contentView 填充到DecoreView中,这个过程其实我们也是一笔带过,不做重点分析
那么我们是这么把这个decoreviewt填充到我们的Window中的呢,其实接下来我们就要说说我们的WindowManager的作用,我们今天重点不是分析该WMS的作用,我们也一笔带过。
那么我们就继续看view填充过程,在我们的Activity调用OnResume之前我们需要调用主线程ActivityThread的handleOnresume来把我们的View填充到window中,废话不多说我们来分析下:
ActivityThread.java
final void handleResumeActivity(IBinder token,
boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
if (r.window == null && !a.mFinished && willBeVisible) {
final Activity a = r.activity;
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;
l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
l.softInputMode |= forwardBit;
if (r.mPreserveWindow) {
a.mWindowAdded = true;
r.mPreserveWindow = false;
// Normally the ViewRoot sets up callbacks with the Activity
// in addView->ViewRootImpl#setView. If we are instead reusing
// the decor view we have to notify the view root that the
// callbacks may have changed.
ViewRootImpl impl = decor.getViewRootImpl();
if (impl != null) {
impl.notifyChildRebuilt();
}
}
if (a.mVisibleFromClient && !a.mWindowAdded) {
a.mWindowAdded = true;
wm.addView(decor, l);
}
我们分析上的方法通过分析可知,我们需要拿到WindowManager对象,拿到DecorView然后addView方法填充进来。
我们之前知道mWindowMnagaer中的对象是通
mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);来创建获取的
因为实现了该接口 WindowManagerImplimplements WindowManager
那么我们就来分析下WindowManagerImpl如何实现View的添加的
WindowmanagerImpl.java
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
原来是通过调用WindowManagerGlobal来添加进去的
那么我们继续分析WindowmanagerImpl
WindowManagerImpl.java
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
ViewRootImpl root;
View panelParentView = null;
root = new ViewRootImpl(view.getContext(), display);
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
root.setView(view, wparams, panelParentView);
ViewRootImpl.java
分析了那么多,这个才是我们今天的重点分析的对象,哈哈,绕了怎么多圈,其实分析就是个圈圈圆圆圈圈的过程,废话不多说,我们继续分析
。。。。。以上省略,我们跳重点来讲
mInputEventReceiver = new WindowInputEventReceiver(mInputChannel,
Looper.myLooper());
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;
上面的代码为什么如此重要,因为他是我们事件传递的上下层精华的部分,
首先我们就大概分析下他是怎么和中间层通信然后把事件传递给ViewRootImpl的
这里我们就是简单的分析下
我们看到WindowInputEventReceiver构造方法中有个InputChannel对象,这里我们可以把它理解为事件的渠道,我们需要通过渠道来传递事件
// Called from native code.
@SuppressWarnings("unused")
private void dispatchInputEvent(int seq, InputEvent event) {
mSeqMap.put(event.getSequenceNumber(), seq);
onInputEvent(event);
}
@Override
public void onInputEvent(InputEvent event) {
/// M: record current key event and motion event to dump input event info for
/// ANR analysis. @{
if (event instanceof KeyEvent) {
mCurrentKeyEvent = (KeyEvent) event;
mKeyEventStartTime = System.currentTimeMillis();
mKeyEventStatus = INPUT_DISPATCH_STATE_STARTED;
} else {
mCurrentMotion = (MotionEvent) event;
mMotionEventStartTime = System.currentTimeMillis();
mMotionEventStatus = INPUT_DISPATCH_STATE_STARTED;
}
/// @}
enqueueInputEvent(event, this, 0, true);
}
void enqueueInputEvent(InputEvent event,
InputEventReceiver receiver, int flags, boolean processImmediately) {
adjustInputEventForCompatibility(event);
QueuedInputEvent q = obtainQueuedInputEvent(event, receiver, flags);
if (processImmediately) {}
doProcessInputEvents();
} else {
scheduleProcessInputEvents();
}
private void scheduleProcessInputEvents() {
if (DEBUG_INPUT || DEBUG_KEY || DEBUG_MOTION) {
Log.v(mTag, "scheduleProcessInputEvents: mPendingInputEventHead = "
+ (mPendingInputEventHead != null ? mPendingInputEventHead.mEvent : "")
+ ",mProcessInputEventsScheduled = " + mProcessInputEventsScheduled
+ ", this = " + this);
}
if (!mProcessInputEventsScheduled) {
mKeyEventStatus = INPUT_DISPATCH_STATE_SCHEDULE_EVENT;
mProcessInputEventsScheduled = true;
Message msg = mHandler.obtainMessage(MSG_PROCESS_INPUT_EVENTS);
msg.setAsynchronous(true);
mHandler.sendMessage(msg);
}
}
private void deliverInputEvent(QueuedInputEvent q) {好看到这里我们就知道我们的setView前面中的为什么会申明这么多个
Trace.asyncTraceBegin(Trace.TRACE_TAG_VIEW, "deliverInputEvent",
q.mEvent.getSequenceNumber());
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onInputEvent(q.mEvent, 0);
}
InputStage stage;
if (q.shouldSendToSynthesizer()) {
stage = mSyntheticInputStage;
} else {
stage = q.shouldSkipIme() ? mFirstPostImeInputStage : mFirstInputStage;
}
if (stage != null) {
stage.deliver(q);
} else {
finishInputEvent(q);
}
}
InputStage 的导出基层类,这里他是很巧妙的应用了一种设计模式,管道模式,也就是说我们如果处理了就增加阀门,不处理我们就继续让他流出,这里我们就不重点分析设计模式问题,继续我们最终会调动至
private int processPointerEvent(QueuedInputEvent q) {
final MotionEvent event = (MotionEvent)q.mEvent;
mAttachInfo.mUnbufferedDispatchRequested = false;
final View eventTarget =
(event.isFromSource(InputDevice.SOURCE_MOUSE) && mCapturingView != null) ?
mCapturingView : mView;
mAttachInfo.mHandlingPointerEvent = true;
boolean handled = eventTarget.dispatchPointerEvent(event);
if (handled && DEBUG_DEFAULT) {
Log.v(mTag, "App handle pointer event: event = " + event
+ ", eventTarget = " + eventTarget
+ ", mCapturingView = " + mCapturingView + ", mView = " + mView
+ ", this = " + this);
}
重点来了,我们看到了我们之前为什么要分析Activity加载View流程,这里我们就用到了mView就是我们的DecorView,在这里问一个小问题,这个mView一定是我们Activity加载的View吗
我们继续分析dispatchPointerEvent
View.java
public final boolean dispatchPointerEvent(MotionEvent event) {
//#ifdef LAVA_EDIT
if (event.isTouchEvent()) {
return dispatchTouchEvent(event);
} else {
return dispatchGenericMotionEvent(event);
}
如果他是触摸事件,那么我们继续调用触摸事件,其实这里就是一个分支点了,如果这里是Activity加载进来的decorview,那么我们就调用Decorview的dispattchTouchEvent()
如果这里是通过WindowManager加载进来的view,例如状态栏,那么他就直接找到对应的view,就不会吧事件传入到Activity, 其实事件该往哪里传,最终的还是找到对应的view
废话不多说,我们继续分析代码,这里我们就复杂的把他当做activity的来看待
Decorview.java
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
final Window.Callback cb = mWindow.getCallback();
if (DBG_MOTION) {
Log.d(TAG, "dispatchTouchEvent = " + ev + ", cb = " + cb + ", destroyed? = "
+ mWindow.isDestroyed() + ", mFeatureId = " + mFeatureId);
}
return cb != null && !mWindow.isDestroyed() && mFeatureId < 0
? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev);
}
这里我们可就知道我们在Activity中attch的时候mWindow.setCallback,这里我们用到了这个事件回调传递的callback
Activity.java
*/我们看到又回到了PhonWindow中的superDispatchTouchEvent()
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
PhoneWindow.java
public boolean superDispatchTouchEvent(MotionEvent event) { return super.dispatchTouchEvent(event); }
我们看到绕了半天又回到DecorView
我们之前说过Decorview其实就是一个Framelayout布局,最后我们看到调用了基类的dispatchTouchEvent()
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
ViewGroup.java
.........省略
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
}
好了分析到这里,剩下的事情就好办了,接下来就是我们的应用层的消息处理机制了
这里就借用网上的一张图来解释吧,父亲传递给自孩子处理,但是前提是父亲不拦截处理,如果父亲拦截处理了就由父亲处理,这些相信大家都非常清楚了,那么我今天的课程就说完了,有的人会问为什么一个InputManaerService服务一个都没提到,在这里不想先提到服务的原因是先由浅大概说下事件传递流程,后续我们会单独开个专题来讲InputManagerService的作用,所以后续还会继续分析。