Input事件输入系统之应用MotionEvent触摸事件处理流程
输入事件一般分为KeyEvent按键事件,和MotionEvent触摸事件,也就是我们平时说的touch事件,事件的传递流程相当复杂庞大,
我们先暂时绕开那个庞大的系统来谈谈上层应用如何接收到MotionEvent事件然后处理的,在讲这个之
前我们先提几个相关类:
Window,WindowManager,PhoneWindow,IWindowManager,WindowManagerService,咋一看,这些东西都是与Window有关
的,平时我们用的也蛮多的也就是WindowManager对象,这些东西是怎么如何我们的MotionEvent事件相关联起来的,这就是我们接
下来分析的重点。由于我们就是简单的分析下应用层的输入输出事件的分析,所以Input系统的硬件抽象层我们只是简答的提一
下。等下次的时候在重点分析该流程的通信协议。
由于有的客官已经知道了Activity的启动流程,那么我们就抽出与我们事件传递相关的一点点东西,view的加载流程,结合我们之前
提到的几个window的概念来分析与MotionEvent事件的联系
首先我们来看下Activity的attach函数
final void attach(Context context, ActivityThread aThread,
Instrumentation instr, IBinder token, int ident,
Application application, Intent intent, ActivityInfo info,
CharSequence title, Activity parent, String id,
NonConfigurationInstances lastNonConfigurationInstances,
Configuration config, String referrer, IVoiceInteractor voiceInteractor,
Window window) {
attachBaseContext(context);
mFragments.attachHost( null /*parent*/ );
mWindow = new PhoneWindow( this, window);
w mWindow.setWindowControllerCallback( this);
//很重要的一个接口回调设置,因为事件传入Activity需要这个接口回调的传递,这点后面我们会继续分析。
w mWindow.setCallback( this);
w mWindow.setOnWindowDismissedCallback( this);
w mWindow.getLayoutInflater().setPrivateFactory( this);
。。。。。。。。。。中间省略
w mWindow.setWindowManager(
(WindowManager)context.getSystemService(Context. WINDOW_SERVICE ),
mToken, mComponent.flattenToString(),
(info. flags & ActivityInfo. FLAG_HARDWARE_ACCELERATED ) != 0);
if ( mParent != l null) {
w mWindow.setContainer( mParent.getWindow());
}
mWindowManager = mWindow.getWindowManager();
从上面的函数中,我们可以看到我们上面提过的几个window概念了,
我们先看下我们的mWindow到底是什么东西
private Window mWindow;
由上面的方法里面可知,原来我们是new了一个PhoneWindow对象,而我们的PhoneWindow就是Window抽象类的继承类
那么我们接下来分析activity中的 onCreate 方法 中的setContentView方法
从字面意思上我们可以理解为填充内容的view,那么他是怎么填充进去的,我们继续分析
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
原来他是通过PhoneWindow来调用setContetnView方法的,那么我们继续分析该方法
相信很多同学都知道该方法,其实就是实例化一个 DecoreView ,然后把我们的 contentView 填充到 DecoreView 中
那么我们是这么把这个 decoreviewt 填充到我们的 Window 中的呢,其实接下来我们就要说说我们的 WindowManager 的作用。
那么我们就继续看 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.w 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);
来创建获取的
因为实现了该接口 WindowManagerImpl implements WindowManager
那么我们就来分析下WindowManagerImpl如何实现View的添加的
WindowmanagerImpl.java
private final WindowManagerGlobal mGlobal = WindowManagerGlobal. getInstance ();
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
l 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 new ViewRootImpl(view.getContext(), display);
root = new new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
s mViews.add(view);
s mRoots.add(root);
s mParams.add(wparams);
root.setView(view, wparams, panelParentView);
看到这里大家都明白了ViewRootImpl和View怎么建立关系起来的了,其实View的加载流程我们还有很多没说,在这里我们只关心是怎么和我们的输入事件相关联的,那么我们就看到了最终会把decoreview通过方法setView与我们的输入事件相关联,那么我们继续往下分析,
ViewRootImpl.java
分析了那么多,这个才是我们今天的重点分析的对象,哈哈,绕了怎么多圈,其实分析就是个圈圈圆圆圈圈的过程,废话不多说,我们继续分析
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized ( this) {
。。。。。以上省略,我们跳重点来讲
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对象,这里我们可以把它理解为事件的渠道,我们需要通过渠道
来传递事件
public InputEventReceiver(InputChannel inputChannel, Looper looper) {
if (inputChannel == null ) {
throw new IllegalArgumentException( "inputChannel must not be null" );
}
if (looper == null ) {
throw new IllegalArgumentException( "looper must not be null" );
}
mInputChannel = inputChannel;
mMessageQueue = looper.getQueue();
mReceiverPtr = = nativeInit ( ( new WeakReference( this ),
inputChannel, mMessageQueue );
d mCloseGuard .open( "dispose" );
} }
看到这里我们知道了原来是通用jni来与中间层通信,通过消息队列的方式来传递事件,上层的消息处理就在下面这个方法
// Called from native code.
@SuppressWarnings( ( "unused") )
private void dispatchInputEvent( int seq, InputEvent event) {
mSeqMap .put(event.getSequenceNumber(), seq);
onInputEvent(event);
} }
native层通过消息循环然后发送事件到上层,那么我们继续分析下OnInputEvent方法
@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 0 , e true );
} }
这里有两种消息,一种是键盘消息,一种是触摸消息,我们今天重点分析的是触摸消息,MotionEvent
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 v ( ( mTag , "scheduleProcessInputEvents: mPendingInputEventHead = "
+ ( mPendingInputEventHead != null ? mPendingInputEventHead. . mEvent : "") )
+ + ",mProcessInputEventsScheduled = " + mProcessInputEventsScheduled
+ + ", this = " + s this );
}
if (! mProcessInputEventsScheduled ) {
mKeyEventStatus = = INPUT_DISPATCH_STATE_SCHEDULE_EVENT ; ;
mProcessInputEventsScheduled = true; ;
Message msg = mHandler .obtainMessage( MSG_PROCESS_INPUT_EVENTS );
msg.setAsynchronous( true );
r mHandler .sendMessage(msg);
}
} }
看到Handle,那么我们就找到处理的地方
private void deliverInputEvent (QueuedInputEvent q) {
Trace.asyncTraceBegin(Trace.TRACE_TAG_VIEW, "deliverInputEvent", ,
q. q. mEvent .getSequenceNumber());
if ( ( mInputEventConsistencyVerifier != l null ) {
mInputEventConsistencyVerifier .onInputEvent(q. mEvent , 0 0 );
}
InputStage stage;
if (q.shouldSendToSynthesizer()) {
stage = mSyntheticInputStage; ;
} else { {
stage = q.shouldSkipIme() ? mFirstPostImeInputStage : mFirstInputStage; ;
}
if (stage != null ) {
stage.deliver(q);
} else { {
finishInputEvent(q);
}
} }
好看到这里我们就知道我们的setView前面中的为什么会申明这么多个
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就是我们的Decor
View, 在这里问一个小问题,这个mView一定是我们Activity加载的View吗
我们继续分析 dispatchPointerEvent
View.java
public final boolean dispatchPointerEvent(MotionEvent event) {
//#ifdef LAVA_EDIT
//xiaoming,20161008,modify for ThreeFinger screenshot
boolean isLavaMotionEventFlag = event.getPointerCount()==1 1 &&event.getAction()==MotionEven
t. ACTION_DOWN &&isThreeFingerShotOnorOff();
f if( ( isLavaMotionEventFlag ){
donwn1 = = new new PointF(event.getX(),event.getY());
mLavaMotionEvent = MotionEvent. obtain (SystemClock. uptimeMillis (),SystemClock. uptime
Millis (),
MotionEvent. ACTION_CANCEL , , donwn1. .x x , donwn1. .y y , 0 0 );
f if( ( IS_DEBUG )Log. w w ( ( TAG_XM , , "donwn1: "+ + donwn1 );
}
boolean isInterceptionThreePointFlag = isThreeFingerShotOnorOff()&&( THREE_TOUCH_EVENT ==
event.getPointerCount()|| isThreeFingerShotStart );
f if (isInterceptionThreePointFlag){
dispatchTouchEvent( mLavaMotionEvent );
clearFocus();
onFocusChanged( false , 0 0 , null );
handleThreeTouchEvent(event);
return true; ;
}
//#endif
if (event.isTouchEvent()) {
return return dispatchTouchEvent(event);
} else { {
return return dispatchGenericMotionEvent(event);
} }
如果他是触摸事件,那么我们继续调用触摸事件,其实这里就是一个分支点了, 如果这里是Activity加载进
来的decorview,那么我们就调用Decorview的dispattchTouchEvent()
如果这里是通过WindowManager加载进来的view,例如状态栏,那么他就直接找到对应的view,就不会吧事件传
入到Activity, 其实事件该往哪里传,最终的还是找到对应的view,这就是我们做三指截屏的时候会遇到的bug
,就是在Activity中可以使用三指截屏,但是在锁屏状态下三指截屏无效
废话不多说,我们继续分析代码,这里我们就复杂的把他当做activity的来看待
Decorview.java
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
final Window. Callback cb = w mWindow .getCallback();
if ( ( DBG_MOTION ) {
Log. d d ( ( TAG , , "dispatchTouchEvent = " + ev + ", cb = " + cb + ", destroyed? = "
+ w mWindow .isDestroyed() + ", mFeatureId = " + d mFeatureId );
}
return cb != null && !w mWindow .isDestroyed() && mFeatureId < 0 0
? cb.dispatchTouchEvent(ev) : super .dispatchTouchEvent(ev);
} }
这里我们可就知道我们在Activity中attch的时候mWindow.setCallback,这里我们用到了这个事件回调传
递的callback
Activity.java
*/
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent. ACTION_DOWN ) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true; ;
}
return return onTouchEvent(ev);
} }
我们看到又回到了PhonWindow中的superDispatchTouchEvent()
PhoneWindow.java
我们看到绕了半天又回到DecorView
public boolean superDispatchTouchEvent(MotionEvent event) {
r return super .dispatchTouchEvent(event);
} }
我们之前说过Decorview其实就是一个Framelayout布局,最后我们看到调用了基类的
dispatchTouchEvent()
ViewGroup.java
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (mInputEventConsistencyVerifier != null ) {
mInputEventConsistencyVerifier.onTouchEvent(ev, 1 1 );
}
.........省略
好了分析到这里,剩下的事情就好办了,接下来就是我们的应用层的消息处理机制了
这里就借用网上的一张图来解释吧,父亲传递给自孩子处理,但是前提是父亲不拦截处理,如果父亲拦截处
理了就由父亲处理,这些相信大家都非常清楚了,那么我今天的课程就说完了,有的人会问为什么一个InputMan
aerService服务一个都没提到,在这里不想先提到服务的原因是先由浅大概说下事件传递流程,后续会单独
来讲InputManagerService的作用,所以后续还会继续分析。