android事件处理,对事件的接收处理,(6)
六,对事件的接收,从ViewRootImpl.java 说起。
为什么要从ViewRootImpl说起呢?还记得前面说inputChannel通道的时候,是从添加一个窗口开始的,那么添加窗口就是从ViewRootImpl的setView开启的。
ViewRootImpl.java
public void setView(View view,WindowManager.LayoutParams attrs, View panelParentView) {
mInputChannel= new InputChannel();
//这里是添加窗口的起点。
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(),mDisplay.getDisplayId(),
mAttachInfo.mContentInsets,mAttachInfo.mStableInsets,
mAttachInfo.mOutsets,mInputChannel);
//这里是输入事件的接收端, mInputChannel是作为channel的客户端,这个前面分析过。同时注意第二个参数Looper.myLooper(),后面用调用Looper(cpp)的addFd方法,把mInputChannel对应的文件描述符fd添加到Looper的事件监控列表中。
mInputEventReceiver= new WindowInputEventReceiver(mInputChannel,
Looper.myLooper());
}
需要说明的是setView的第一个参数mWindow,类型是W,即IWindow.Stub的实现类,表示IWindow的的实现端,这个参数作为WMS回调ViewRootImpl的桥梁,也即是WMS通过这个mWindow把事件、消息通知到ViewRootImpl,具体都有那些事件、消息可以参考IWindow.java类中的函数声明。具体是WMS通过IWindow的Bp端把相应事件通知到位于ViewRootImpl侧的IWindow的Bn端。
WindowInputEventReceiver会调用父类InputEventReceiver的构造函数,
InputEventReceiver.java
创建一个绑定到特定输入通道的输入事件的接受者。
public InputEventReceiver(InputChannelinputChannel, Looper looper) {
mInputChannel = inputChannel;
mMessageQueue = looper.getQueue();
mReceiverPtr = nativeInit(new WeakReference
inputChannel, mMessageQueue);
}
android_view_InputEventReceiver.cpp
static jlong nativeInit(JNIEnv* env, jclassclazz, jobject receiverWeak,
jobject inputChannelObj, jobject messageQueueObj){
//先把java对象转成native对象。
sp
inputChannelObj);
sp
messageQueueObj);
//inputChannel ,messageQueue 赋给NativeInputEventReceiver中的变量,
sp
receiverWeak, inputChannel, messageQueue);
}
//当有event时,会调用 handleEvent方法。
intNativeInputEventReceiver::handleEvent(int receiveFd, int events, void* data){
//consumeEvents消费这个事件。
status_tstatus = consumeEvents(env, false /*consumeBatches*/, -1, NULL);
setFdEvents(ALOOPER_EVENT_INPUT);
}
//通过 consumeEvents调用java中的方法 dispatchInputEvent。
status_t NativeInputEventReceiver::consumeEvents(JNIEnv*env,
boolconsumeBatches, nsecs_t frameTime, bool* outConsumedBatch) {
env->CallVoidMethod(receiverObj.get(),
gInputEventReceiverClassInfo.dispatchInputEvent, seq, inputEventObj);
}
InputEventReceiver.java
//从native调用到java层。
private void dispatchInputEvent(int seq,InputEvent event) {
mSeqMap.put(event.getSequenceNumber(), seq);
onInputEvent(event);
}
进一步调用的是子类WindowInputEventReceiver的onInputEvent,
这样就回到了ViewRootImpl.java。
public void onInputEvent(InputEvent event) {
enqueueInputEvent(event,this, 0, true);
}
void enqueueInputEvent(InputEvent event,InputEventReceiver receiver,
intflags, boolean processImmediately)@ViewRootImpl.java{
QueuedInputEventq = obtainQueuedInputEvent(event, receiver, flags);
//是立即处理事件,还是先入队顺序处理。
if (processImmediately) {
doProcessInputEvents();
} else {
scheduleProcessInputEvents();
}
}
我们看入队顺序处理的情况。
privatevoid deliverInputEvent(QueuedInputEvent q) {
//接下来的事件传递,使用的是职责链模式,有多个InputStage的子类依次传递,
InputStage stage = q.shouldSkipIme() ?mFirstPostImeInputStage : mFirstInputStage;
stage.deliver(q);
}
这个职责链关系可以参考setView方法中的设置。
ViewRootImpl.java
publicvoid setView(View view, WindowManager.LayoutParams attrs, ViewpanelParentView){
//定义了事件的传递步骤。
// Set up the input pipeline.
InputStage syntheticInputStage = new SyntheticInputStage();
InputStage viewPostImeStage = new ViewPostImeInputStage(syntheticInputStage); InputStagenativePostImeStage = new NativePostImeInputStage(viewPostImeStage,
“aq” +counterSuffix);
InputStage earlyPostImeStage = new EarlyPostImeInputStage(nativePostImeStage);
InputStage imeStage = new ImeInputStage(earlyPostImeStage,
“aq” + counterSuffix);
InputStage viewPreImeStage = new ViewPreImeInputStage(imeStage);
InputStage nativePreImeStage = new NativePreImeInputStage(viewPreImeStage,
“aq” +counterSuffix);
mFirstInputStage = nativePreImeStage;
mFirstPostImeInputStage =earlyPostImeStage;
}
值得注意的是这里的ImeInputStage会把事件传给输入法,如果输入法进程被异常杀掉了,可能导致事件的传递中断。相关代码如下:
final class ImeInputStage extends AsyncInputStage
implements InputMethodManager.FinishedInputEventCallback {
protected int onProcess(QueuedInputEvent q) {
InputMethodManager imm = InputMethodManager.peekInstance();
final InputEvent event = q.mEvent;
int result = imm.dispatchInputEvent(event, q, this, mHandler);
if (result == InputMethodManager.DISPATCH_NOT_HANDLED) {
//关键点在这里,如果输入法没有处理,记得返回FORWARD要传到下一个inputstage, 早期的android版本中,是返回的FINISH_NOT_HANDLED,就导致事件没能继续分发,被丢弃了。
return FORWARD;
}
return FORWARD;
}
}
也看下输入法这边的代码:
public int dispatchInputEvent(InputEvent event, Object token,
FinishedInputEventCallback callback, Handler handler) @InputMethodManager.java{
if (mCurMethod != null) {
//这里还有一些判断条件省略了,如:是keyevent,是actionDown,是KEYCODE_SYM…
showInputMethodPickerLocked();
return DISPATCH_HANDLED;
}
return DISPATCH_NOT_HANDLED;
}
这个 mCurMethod是IInputMethodSession类型的,如果输入法异常退出,在执行解绑定时,会把mCurMethod置null,那么上面的dispatchInputEvent就返回了DISPATCH_NOT_HANDLED,也就是ImeInputStage收到的返回值。
我们直接看最后按键事件是由那个InputStage处理的。
finalclass ViewPostImeInputStage extends InputStage {
protected int onProcess(QueuedInputEventq) {
if (q.mEvent instanceofKeyEvent) {
returnprocessKeyEvent(q); //按键事件
}
}
private int processKeyEvent(QueuedInputEventq) {
final KeyEvent event =(KeyEvent)q.mEvent;
//这里把事件传给ViewTree,view树的根mView是DecorView,
if (mView.dispatchKeyEvent(event)){
returnFINISH_HANDLED;
}
}
}
早期的Android版本,DecorView是PhoneWindow.java的子类,
PhoneWindow.java
privatefinal class DecorView extends FrameLayout implements RootViewSurfaceTaker {
public boolean dispatchKeyEvent(KeyEventevent) {
final Callback cb = getCallback();
boolean handled =cb.dispatchKeyEvent(event);
}
}
Android7.1版本,DecorView.java是单独的文件,在事件处理上区别不大。
DecorView.java
publicboolean dispatchKeyEvent(KeyEvent event) {
final Window.Callback cb =mWindow.getCallback();
final boolean handled = cb != null&& mFeatureId < 0 ? cb.dispatchKeyEvent(event)
: super.dispatchKeyEvent(event);
return isDown ?mWindow.onKeyDown(mFeatureId, event.getKeyCode(), event)
: mWindow.onKeyUp(mFeatureId,event.getKeyCode(), event);
}
这里mWindow是phoneWindow,它的getCallback()返回的Window.java中的接口Callback,既然是接口,就要具体的实现类,这个场景中实现类就是Activity,我们来确认Activity是不是实现了这个接口。
Activity.java
publicclass Activity extends ContextThemeWrapper
implements LayoutInflater.Factory2, Window.Callback,KeyEvent.Callback,
从Activity的继承关系,可以看到Activity实现了Window.Callback, KeyEvent.Callback两个接口。
其中dispatchKeyEvent,dispatchTouchEvent等拦截事件分发的函数是属于Window.Callback接口的;onKeyDown,onKeyLongPress,onKeyUp等最后一级的处理函数是属于KeyEvent.Callback接口的。
所以,接着就从DecorView到了Activity的dispatchKeyEvent函数中。
Activity.java
publicboolean dispatchKeyEvent(KeyEvent event) {
View decor = mDecor;
return event.dispatch(this, decor != null
? decor.getKeyDispatcherState() :null, this);
}
从这里可以看出,应用中Activity可以通过复写dispatchKeyEvent来实现拦截,如果返回true,就说明已经消费了这个事件,不再往下传递,否侧就传给下一个接收者。注意在return中调用了KeyEvent的dispatch方法。
KeyEvent.java
publicfinal boolean dispatch(Callback receiver, DispatcherState state, Objecttarget){
switch (mAction) {
case ACTION_DOWN: {
booleanres = receiver.onKeyDown(mKeyCode, this);
returnres;
}
case ACTION_UP:
returnreceiver.onKeyUp(mKeyCode, this);
}
}
这里通过receiver传给了具体的接收者,可能是应用中的Activity实例,或者更确切的说是实现了KeyEvent.Callback接口的对象。其中的onKeyDown,onKeyUp跟应用中的Activity重写的方法是一致的。
对按键事件的处理相对简单,除了窗口策略phoneWindowManager先行做拦截处理外,就是当前应用本身的处理了。应用本身只要复写一些跟key相关的方法即接受到事件,比如:
public boolean onKeyDown(intkeyCode, KeyEvent event) {
throw new RuntimeException(“Stub!”);
}
public boolean onKeyLongPress(intkeyCode, KeyEvent event) {
throw new RuntimeException(“Stub!”);
}
public boolean onKeyUp(intkeyCode, KeyEvent event) {
throw new RuntimeException(“Stub!”);
}
窗口策略这边的拦截就是通过如下函数:
PhoneWindowManager.java
当应用程序没有处理,就把事件分发到dispatchUnhandledKey,这是由InputDispatch线程调用的。允许你针对这些没有被应用处理的keys,定义默认的行为,比如丢弃,也可以用一个替代的keycode再次分发作为反馈。
public KeyEventdispatchUnhandledKey(WindowState win, KeyEvent event, int policyFlags);
这个方法是在一个key分发给目标窗口强,被inputDispatch线程调用,你可以针对一些keys定义一个处理行为,这个处理方法是不会被应用覆盖的。
public longinterceptKeyBeforeDispatching(WindowState win, KeyEvent event, intpolicyFlags);
这个方法是在key入队前,被input reader线程调用,有些行为需要在这个时刻处理因为它可能影响到设备的电源状态,比如powerkeys,所以,通常情况下在队列中应尽可能少的保存这个key。
public intinterceptKeyBeforeQueueing(KeyEvent event, int policyFlags);
到这里就分析完了按键的接收过程。
下面看下触屏事件的接收处理。
前面的处理过程跟按键是类似的,我们还是从InputStage职责链的最后开始:
finalclass ViewPostImeInputStage extends InputStage {
protected int onProcess(QueuedInputEventq) {
if (q.mEvent instanceof KeyEvent){
//按键事件处理。
return processKeyEvent(q);
}else{
finalint source = q.mEvent.getSource();
if((source & InputDevice.SOURCE_CLASS_POINTER) != 0) {
//pointer事件,触屏事件是从这里开始处理的
returnprocessPointerEvent(q);
}
}
}
}
ViewRootImpl.java
privateint processPointerEvent(QueuedInputEvent q) {
final View eventTarget =
(event.isFromSource(InputDevice.SOURCE_MOUSE)&& mCapturingView != null) ?
mCapturingView : mView;
//把处理权交给Viewtree的根元素mView,
boolean handled =eventTarget.dispatchPointerEvent(event);
}
View.java
publicfinal boolean dispatchPointerEvent(MotionEvent event) {
if (event.isTouchEvent()) {
returndispatchTouchEvent(event);//触屏事件
} else {
returndispatchGenericMotionEvent(event);// GenericMotionEvent事件
}
}
从这里开始,应该把事件传给目标View,这个目标view可能就是View.java,在应用场景下,更多的时候是DecorView,所以进入到DecorView.java中
DecorView.java
publicboolean dispatchTouchEvent(MotionEvent ev) {
final Window.Callback cb =mWindow.getCallback();
return cb != null &&!mWindow.isDestroyed() && mFeatureId < 0
? cb.dispatchTouchEvent(ev) :super.dispatchTouchEvent(ev);
}
根据前面按键事件的分析,这里的mWindow.getCallback();的具体实例是Activity实例。
Activity.java
//子类可以重写这个方法,在分发给窗口前拦截所有的触屏事件。
publicboolean dispatchTouchEvent(MotionEvent ev) {
//把事件分发给窗口。
if(getWindow().superDispatchTouchEvent(ev)) {
return true;
}
}
从窗口还是回到ViewTree,Viewtree中处理分ViewGroup,View,
PhoneWindow.java
publicboolean superDispatchTouchEvent(MotionEvent event) {
returnmDecor.superDispatchTouchEvent(event);
}
先看ViewGroup中的处理,DecorView是继承ViewGroup的,
DecorView.java
publicboolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
接着调用的ViewGroup中的方法dispatchTouchEvent。
ViewGroup.java
publicboolean dispatchTouchEvent(MotionEvent ev) {
boolean handled = false;//判断事件是否被处理
//判断是否是Down事件,down是后续事件的起点,收到Down事件,先清除以前的touch目标,重置所有的状态,准备进入一个新的循环。
if (actionMasked ==MotionEvent.ACTION_DOWN) {
cancelAndClearTouchTargets(ev);
resetTouchState();
}
//是否拦截这个事件,ViewGroup优先考虑拦截的可能行。
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
final boolean disallowIntercept =(mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
//disallowIntercept为false,就是允许拦截,进一步调用 onInterceptTouchEvent来确定是否真正执行拦截,ViewGroup的子类只要重载这个 onInterceptTouchEvent这个函数就可以改变ViewGroup的拦截策略。
if (!disallowIntercept) {
intercepted =onInterceptTouchEvent(ev);
}else{
intercepted = false;
}
}else{
intercepted = true; //不允许拦截
}
//不拦截的情况
if (!canceled && !intercepted) {
//从前往后查找一个可以接收这个事件的子View。
for (int i = childrenCount - 1; i>= 0; i—) {
final View child =getAndVerifyPreorderedView(
preorderedList,children, childIndex);
//判断标准:canViewReceivePointerEvents,这个child是否能接收这个事件,比如其是否可见; isTransformedTouchPointInView,触摸点是否落在了这个child的范围内。
if (!canViewReceivePointerEvents(child)
||!isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
//找到了能接收的子View,就把事件投递给他,如果子View是ViewGroup就会继续调用ViewGroup的dispatchTouchEvent,如果是View,将调用View的dispatchTouchEvent。
if(dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {}
}
}
}
从上看出,ViewGroup对事件的处理,可能自身处理这个事件,也可能继续传递事件给子View。
什么情况下ViewGroup会拦截处理这个事件,起决定作用的是onInterceptTouchEvent,也就是在继承ViewGroup时,只要重载onInterceptTouchEvent,就可以实现拦截,当然也可以重载dispatchTouchEvent,但是dispatchTouchEvent是所有ViewGroup共性的一个表现,还是重载onInterceptTouchEvent这个体现特性的方法好些。
如果onInterceptTouchEvent返回false,那就是不拦截,继续分发给子View,后面的move,up等事件还是会调用onInterceptTouchEvent做判断。
如果onInterceptTouchEvent返回true,当前viewGroup拦截这个事件,那么从这个down事件开启,直到up事件都会投递到这个ViewGroup的onTouchEvent方法中,不再往子View传递,这也意味着你要重载onTouchEvent方法,它是属于View类的。
最后看下View对事件的处理。
View.java
publicboolean dispatchTouchEvent(MotionEvent event) {
if (onFilterTouchEventForSecurity(event)){
//View可以通过setOnTouchListener设置一个事件监听,mOnTouchListener就是那个监听对象,当事件来时,就不调用mOnTouchListener来通知,这种方式的优先级比onTouchEvent要高。
ListenerInfo li = mListenerInfo;
if (li != null &&li.mOnTouchListener != null
&& (mViewFlags& ENABLED_MASK) == ENABLED
&&li.mOnTouchListener.onTouch(this, event)) {
result= true;
}
//如果没有指定OnTouchListener监听,或者View被disabled,或者onTouch返回了false,都会把事件传给onTouchEvent,onTouchEvent也是需要继承类重载的。
if (!result &&onTouchEvent(event)) {
result= true;
}
}
}
//具体处理这个touch事件
publicboolean onTouchEvent(MotionEvent event) {
case MotionEvent.ACTION_DOWN:
//将View对象设置为pressed状态,检查是否会形成一个长按事件,默认一个长按事件的时间是0.5秒,DEFAULT_LONG_PRESS_TIMEOUT = 500;这是通过postDelayed一个Runable来确定的,在这个事件之前如果有up,move时间产生,就会移除这个Runable,否则就需要进行长按事件的处理。
setPressed(true,x, y);
checkForLongClick(0,x, y);
// pointInView判断手势是否超出了view的范围。如果超出了,移除长按检测,View的状态也不再是pressed,
case MotionEvent.ACTION_MOVE:
if (!pointInView(x, y,mTouchSlop)) {
removeLongPressCallback();
setPressed(false);
}
//UP是手势的结束点,这里重点判断是否产生onClick,即调用setOnClickListener设置的监听,要产生一个click事件,一是当前要是Pressed状态,二前面没有执行长按事件。
case MotionEvent.ACTION_UP:
if ((mPrivateFlags &PFLAG_PRESSED) != 0 || prepressed) {
if (!mHasPerformedLongPress&& !mIgnoreNextUpEvent) {
performClick();
}
}
//cancel,这个不是用户主动产生的,是系统自行做出的处理,作为手势的结束,完成一些清理工作。
case MotionEvent.ACTION_CANCEL:
}
到这里事件的处理就分析完了。
还没有评论,来说两句吧...