android事件处理,对事件的接收处理,(6)

一时失言乱红尘 2022-06-08 07:53 397阅读 0赞

六,对事件的接收,从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(this),

inputChannel, mMessageQueue);

}

android_view_InputEventReceiver.cpp

static jlong nativeInit(JNIEnv* env, jclassclazz, jobject receiverWeak,

jobject inputChannelObj, jobject messageQueueObj){

//先把java对象转成native对象。

sp inputChannel =android_view_InputChannel_getInputChannel(env,

inputChannelObj);

spmessageQueue = android_os_MessageQueue_getMessageQueue(env,

messageQueueObj);

//inputChannel ,messageQueue 赋给NativeInputEventReceiver中的变量,

spreceiver = new NativeInputEventReceiver(env,

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: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;

}

值得注意的是这里的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:

}

到这里事件的处理就分析完了。

发表评论

表情:
评论列表 (有 0 条评论,397人围观)

还没有评论,来说两句吧...

相关阅读

    相关 Android事件处理

      UI编程通常都会伴随事件处理,Android也不例外,它提供了两种方式的事件处理:基于回调的事件处理和基于监听器的事件处理。 对于基于监听器的事件处理而言,主要