Android Touch事件派发过程详解

Android事件投递(主要以触摸事件为主)的工作流程分为4个部分,如图所示: 

 (1) 采集 
即对“硬件源”所产生的原始信息进行收集的过程。它需要Linux内核驱动的支持,Android系统则是通过/dev/input/下的节点来访问当前发生的事件。 
(2) 前期处理 
上一步采集到的信息为“原始数据”,这其中一部分内容对应程序而言并不是“必须”的,而且格式上也相对烦琐,所以需要先经过前期的提炼和转化。 
(3)WMS分配 
WindowManagerService是窗口的大主管,同时也是InputEvent的派发者。WMS记录了当前系统中所有窗口的完整状态信息,所以只有它才能判断出应该把事件投递给哪一个具体的应用进程进行处理。其派发策略也因事件类型的不同而有所差异,以触摸消息为例,则要先计算出触摸点落在哪个区域,然后才能传递给相应的窗口单元。 
(4)应用程序处理 
应用开发人员的工作主要体现在这一部分。经过前面的几个步骤的传递,应用程序端收到的事件已经相对“可理解”“好处理”了,接下来要做的就是充分利用这些事件来实现软件的功能。

1、InputManagerService(IMS)诞生

IMS服务的创建过程是在SystemServer进程启动的时候实例化的,并注册到ServiceManager中去,这个服务对外主要是用来提供一些输入设备的信息的作用。 
/frameworks/base/services/java/com/android/server/SystemServer.java

 

private void startOtherServices() {
    ...
    //新建IMS对象
    inputManager = new InputManagerService(context);
    wm = WindowManagerService.main(context, inputManager,
            mFactoryTestMode != FactoryTest.FACTORY_TEST_LOW_LEVEL,
            !mFirstBoot, mOnlyCore);
    ServiceManager.addService(Context.WINDOW_SERVICE, wm);
    //将IMS发布给ServiceManager,以便其他人可以访问IMS提供的接口
    ServiceManager.addService(Context.INPUT_SERVICE, inputManager);
    mActivityManagerService.setWindowManager(wm);
    //设置向WMS发起回调Callback对象
    inputManager.setWindowManagerCallbacks(wm.getInputMonitor());
    //启动IMS
    inputManager.start();
    ...
}

InputManagerService和WindowManagerService有密切的联系,也是触摸事件处理的时候同时涉及两个服务,InputManagerService主要负责事件(以触摸事件为例)的采集,而WindowManagerService负责找到目标窗口。接下来,继续分析InputManagerService如何完成触摸事件的采集。

2、事件的采集

/frameworks/base/services/core/jni/com_android_server_input_InputManagerService.cpp

NativeInputManager::NativeInputManager(jobject contextObj,
        jobject serviceObj, const sp<Looper>& looper) :
        mLooper(looper), mInteractive(true) {
    ...
    //NativeInputManager创建了EventHub
    sp<EventHub> eventHub = new EventHub();
    //继续创建Native层的InputManager
    mInputManager = new InputManager(eventHub, this, this);
}

这里的EventHub,可以看作是事件集线器,是利用Linux的inotify和epoll机制,对设备节点增删事件以及可读状态的监听,例如,各种触摸、按钮事件等等,主要面向的是/dev/input目录下的设备节点,比如说/dev/input/event0上的事件就是输入事件,通过EventHub的getEvents就可以监听并获取该事件。在InputManager创建的时候,同时也会再创建一个InputReader对象及InputReaderThread Loop线程,这个loop线程的主要作用就是像一台水泵一样,通过EventHub的getEvents获取Input事件。 
/frameworks/native/services/inputflinger/InputManager.cpp

 

InputManager::InputManager(
        const sp<EventHubInterface>& eventHub,
        const sp<InputReaderPolicyInterface>& readerPolicy,
        const sp<InputDispatcherPolicyInterface>& dispatcherPolicy) {
    //创建InputDispatcher,事件分发执行类    
    mDispatcher = new InputDispatcher(dispatcherPolicy);
    //创建InputReader,事件读取执行类
    mReader = new InputReader(eventHub, readerPolicy, mDispatcher);
    //将InputReader与InputDispatcher建立关联
    initialize();
}
void InputManager::initialize() {
    //创建供InputReader运行的线程InputReaderThread
    mReaderThread = new InputReaderThread(mReader);
    //创建供InputDispatcher运行的线程InputDispatcherThread
    mDispatcherThread = new InputDispatcherThread(mDispatcher);
}

 /frameworks/native/services/inputflinger/InputReader.cpp

 

void InputReader::loopOnce() {
    int32_t oldGeneration;
    int32_t timeoutMillis;
    bool inputDevicesChanged = false;
    Vector<InputDeviceInfo> inputDevices;
    ...
    //监听事件
    size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);
    ...
            //处理事件
            processEventsLocked(mEventBuffer, count);
    ...
     //通知派发(对上面mDispatcher的进一步封装)  
    mQueuedListener->flush();
}
bool InputReaderThread::threadLoop() {
    mReader->loopOnce();
    return true;
}

上面代码的流程,输入事件就可以被读取,经过processEventsLocked被初步封装成RawEvent,最后发通知,请求派发消息。

在新建InputManager的时候,不但创建了一个事件读取线程(InputReaderThread),而且还创建了一个事件派发线程()InputDispatcherThread,虽然也可以直接在读取线程中派发,但是这样肯定会增加耗时,不利于事件的及时读取,因此,事件读取完毕后,直接向派发线程发个通知,请派发线程去处理,这样读取线程就可以更加敏捷,防止事件丢失。 
InputReader的mQueuedListener其实就是InputDispatcher对象,因此mQueuedListener->flush()就是通知InputDispatcher事件读取完毕,可以派发事件了, InputDispatcherThread是一个典型Looper线程,基于native的Looper实现了Hanlder消息处理模型,如果有Input事件到来就被唤醒处理事件,处理完毕后继续睡眠等待。 
/frameworks/native/services/inputflinger/InputDispatcher.cpp

 

bool InputDispatcherThread::threadLoop() {
    mDispatcher->dispatchOnce();
    return true;
}
void InputDispatcher::dispatchOnce() {
    nsecs_t nextWakeupTime = LONG_LONG_MAX;
    { ...
        //被唤醒 ,处理Input消息
        if (!haveCommandsLocked()) {
            dispatchOnceInnerLocked(&nextWakeupTime);
        }
    ...
    } 

    nsecs_t currentTime = now();
    int timeoutMillis = toMillisecondTimeoutDelay(currentTime, nextWakeupTime);
    //睡眠等待input事件
    mLooper->pollOnce(timeoutMillis);
}

 上面代码是派发线程的模型,dispatchOnceInnerLocked是具体的派发处理逻辑,里面有很多的分支,以触摸事件为例:

void InputDispatcher::dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) {
     case EventEntry::TYPE_MOTION: {
        MotionEntry* typedEntry = static_cast<MotionEntry*>(mPendingEvent);
        ...
        done = dispatchMotionLocked(currentTime, typedEntry,
                &dropReason, nextWakeupTime);
        break;
    }
}
bool InputDispatcher::dispatchMotionLocked(
        nsecs_t currentTime, MotionEntry* entry, DropReason* dropReason, nsecs_t* nextWakeupTime) {
    ...
    Vector<InputTarget> inputTargets;

    bool conflictingPointerActions = false;
    int32_t injectionResult;
    if (isPointerEvent) {
        //关键点1: 找到目标Window
        injectionResult = findTouchedWindowTargetsLocked(currentTime,
                entry, inputTargets, nextWakeupTime, &conflictingPointerActions);
    } else {
        // Non touch event.  (eg. trackball)
        injectionResult = findFocusedWindowTargetsLocked(currentTime,
                entry, inputTargets, nextWakeupTime);
    }
    ...
    //关键点2: 消息派发到目标窗口中
    dispatchEventLocked(currentTime, entry, inputTargets);
    return true;
}

触摸事件会首先通过findTouchedWindowTargetsLocked()函数找到目标Window,进而通过dispatchEventLocked()将消息发送到目标窗口。

4、触摸事件找到对应目标窗口

根据触摸事件的位置及窗口的属性来确定将事件发送到哪个窗口 
/frameworks/native/services/inputflinger/InputDispatcher.cpp

 

int32_t InputDispatcher::findTouchedWindowTargetsLocked(nsecs_t currentTime,
        const MotionEntry* entry, Vector<InputTarget>& inputTargets, nsecs_t* nextWakeupTime,
        bool* outConflictingPointerActions) {
        ...
        sp<InputWindowHandle> newTouchedWindowHandle;
        bool isTouchModal = false;
        //遍历所有窗口
        size_t numWindows = mWindowHandles.size();
        for (size_t i = 0; i < numWindows; i++) {
            sp<InputWindowHandle> windowHandle = mWindowHandles.itemAt(i);
            const InputWindowInfo* windowInfo = windowHandle->getInfo();
            if (windowInfo->displayId != displayId) {
                continue; // wrong display
            }

            int32_t flags = windowInfo->layoutParamsFlags;
            if (windowInfo->visible) {
                if (! (flags & InputWindowInfo::FLAG_NOT_TOUCHABLE)) {
                    isTouchModal = (flags & (InputWindowInfo::FLAG_NOT_FOCUSABLE
                            | InputWindowInfo::FLAG_NOT_TOUCH_MODAL)) == 0;
                    //找到目标窗口       
                    if (isTouchModal || windowInfo->touchableRegionContainsPoint(x, y)) {
                        newTouchedWindowHandle = windowHandle;
                        break; // found touched window, exit window loop
                    }
                 }
            ...     
}

mWindowHandles代表着所有窗口,findTouchedWindowTargetsLocked的就是从mWindowHandles中找到目标窗口。mWindowHandles是怎么来的呢,窗口增删的时候怎样才能保持最新的?这里就引进来WindowManagerService交互问题了,接着分析:

void InputDispatcher::setInputWindows(const Vector<sp<InputWindowHandle> >& inputWindowHandles) {
...
mWindowHandles = inputWindowHandles;
...

WindowManagerService中的InputMonitor,会间接调用InputDispatcher::setInputWindows,这个主要是跟窗口增改删除等逻辑相关。

5、发送事件到目标窗口

找到了目标窗口,事件也封装完毕,剩下的就是通知目标窗口。目前所有的操作都是在SystemServer进程完成的,如何才能通知窗口位于APP端的用户进程呢?接下来Socket的通信方式出场了,它是处理input事件的。Socket是怎么来的呢?在当APP端向WMS请求添加窗口的时候,会伴随着Input通道的创建,窗口的添加一定会调用ViewRootImpl中的setView函数: 
/frameworks/native/services/inputflinger/ViewRootImpl.cpp

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
                ...
                requestLayout();
                if ((mWindowAttributes.inputFeatures
                        & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
                    //创建InputChannel容器    
                    mInputChannel = new InputChannel();
                }
                try {
                    mOrigWindowType = mWindowAttributes.type;
                    mAttachInfo.mRecomputeGlobalAttributes = true;
                    collectViewAttributes();
                    //添加窗口,并请求开辟Socket Input通信通道
                    res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(),
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mOutsets, mInputChannel);
                } ...
                //监听,开启Input信道
                if (mInputChannel != null) {
                    if (mInputQueueCallback != null) {
                        mInputQueue = new InputQueue();
                        mInputQueueCallback.onInputQueueCreated(mInputQueue);
                    }
                    mInputEventReceiver = new WindowInputEventReceiver(mInputChannel,
                            Looper.myLooper());
                }
                ...
}                

 接着看服务端WMS如何填充:

public int addWindow(Session session, IWindow client, int seq,
            WindowManager.LayoutParams attrs, int viewVisibility, int displayId,
            Rect outContentInsets, Rect outStableInsets, Rect outOutsets,
            InputChannel outInputChannel) {
     ...
     if (outInputChannel != null && (attrs.inputFeatures
                    & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
                String name = win.makeInputChannelName();
                //关键点1创建通信信道
                InputChannel[] inputChannels = InputChannel.openInputChannelPair(name);
                //本地用
                win.setInputChannel(inputChannels[0]);
                //APP端用
                inputChannels[1].transferTo(outInputChannel);
                //注册信道与窗口
                mInputManager.registerInputChannel(win.mInputChannel, win.mInputWindowHandle);
            }
     ...       
}

WMS首先创建socketpair作为全双工通道,并分别填充到Client与Server的InputChannel中去;再让InputManager将Input通信信道与当前的窗口ID绑定,这样就能知道哪个窗口用哪个信道通信了;最后通过Binder将outInputChannel回传到APP端,以下是SocketPair的创建代码: 
frameworks/native/libs/Input/tests/InputTransport.cpp

status_t InputChannel::openInputChannelPair(const String8& name,
        sp<InputChannel>& outServerChannel, sp<InputChannel>& outClientChannel) {
    int sockets[2];
    if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, sockets)) {
        status_t result = -errno;
        ...
        return result;
    }

    int bufferSize = SOCKET_BUFFER_SIZE;
    setsockopt(sockets[0], SOL_SOCKET, SO_SNDBUF, &bufferSize, sizeof(bufferSize));
    setsockopt(sockets[0], SOL_SOCKET, SO_RCVBUF, &bufferSize, sizeof(bufferSize));
    setsockopt(sockets[1], SOL_SOCKET, SO_SNDBUF, &bufferSize, sizeof(bufferSize));
    setsockopt(sockets[1], SOL_SOCKET, SO_RCVBUF, &bufferSize, sizeof(bufferSize));
    //填充到server inputchannel
    String8 serverChannelName = name;
    serverChannelName.append(" (server)");
    outServerChannel = new InputChannel(serverChannelName, sockets[0]);
    //填充到client inputchannel
    String8 clientChannelName = name;
    clientChannelName.append(" (client)");
    outClientChannel = new InputChannel(clientChannelName, sockets[1]);
    return OK;
}

 socketpair的创建与访问还是借助文件描述符(Fd),WMS需要借助Binder通信向APP端回传文件描述符,在内核层面实现两个进程的转换,窗口添加成功后,socketpair被创建,被传递到了APP端,但是信道并未完全建立,还需要一个主动的监听,当消息到来的时候是需要通知的。APP端的监听消息的手段是:将socket添加到Looper线程的epoll数组中去,当有消息到来时,Looper线程就会被唤醒,并获取事件内容,通信信道的打开是伴随WindowInputEventReceiver的创建来完成的。 
信息到来,Looper根据文件描述符(Fd)找到对应的监听器:NativeInputEventReceiver,并调用handleEvent处理对应事件: 
/frameworks/base/native/android/android_view_InputEventReceiver.cpp

 

int NativeInputEventReceiver::handleEvent(int receiveFd, int events, void* data) {
   ...
   if (events & ALOOPER_EVENT_INPUT) {
        JNIEnv* env = AndroidRuntime::getJNIEnv();
        status_t status = consumeEvents(env, false /*consumeBatches*/, -1, NULL);
        mMessageQueue->raiseAndClearException(env, "handleReceiveCallback");
        return status == OK || status == NO_MEMORY ? 1 : 0;
    }
...
}

 进一步读取事件,并封装成Java层对象,传递给Java层,进行相应的回调:

status_t NativeInputEventReceiver::consumeEvents(JNIEnv* env,
        bool consumeBatches, nsecs_t frameTime, bool* outConsumedBatch) {
     ...
     for (;;) {
        uint32_t seq;
        InputEvent* inputEvent;
        //获取事件
        status_t status = mInputConsumer.consume(&mInputEventFactory,
                consumeBatches, frameTime, &seq, &inputEvent);   
     ...
     //处理touch事件
     case AINPUT_EVENT_TYPE_MOTION: 
                ...
                MotionEvent* motionEvent = static_cast<MotionEvent*>(inputEvent);
                if ((motionEvent->getAction() & AMOTION_EVENT_ACTION_MOVE) && outConsumedBatch) {
                    *outConsumedBatch = true;
                }
                inputEventObj = android_view_MotionEvent_obtainAsCopy(env, motionEvent);
                break;
            }
              ...
              //回调处理函数
               if (inputEventObj) {
                ...
                   env->CallVoidMethod(receiverObj.get(),
                      gInputEventReceiverClassInfo.dispatchInputEvent, seq, inputEventObj);              
}

最后就是触摸事件被封装成了inputEvent,并通过InputEventReceiver的dispatchInputEvent(WindowInputEventReceiver)进行处理,这里就返回到我们常见的Java世界了。 

---------------------从此处开始java应用层的代码调用过程------------------------------------------

6、在目标窗口中处理事件 
/frameworks/base/core/java/android/view/ViewRootImpl.java

 

//android.view.InputEventReceiver.java

    // Called from native code.
    //native层的NativeInputEventReceiver处调用java层的InputEventReceiver
    @SuppressWarnings("unused")
    private void dispatchInputEvent(int seq, InputEvent event, int displayId) {
        mSeqMap.put(event.getSequenceNumber(), seq);
        //事件进行中
        onInputEvent(event, displayId);
    }

//ViewRootImpl#WindowInputEventReceiver

        @Override
        public void onInputEvent(InputEvent event, int displayId) {
            //将输入事件放入队列中处理
            enqueueInputEvent(event, this, 0, true);
        }

//ViewRootImpl

  void enqueueInputEvent(InputEvent event,
            InputEventReceiver receiver, int flags, boolean processImmediately) {
       ...
        //如果需要立刻处理输入事件
        if (processImmediately) {
            //处理输入事件
            doProcessInputEvents();
        }else {
            //将输入事件通过handle发送异步Message
            scheduleProcessInputEvents();
        }
        ...
  }


  void doProcessInputEvents() {
      ...
      //交付传递处理输入事件
      deliverInputEvent(q);
      ...
  }

  private void deliverInputEvent(QueuedInputEvent q) {
        ...
        InputStage stage;
        if (q.shouldSendToSynthesizer()) {
            stage = mSyntheticInputStage;
        } else {
            stage = q.shouldSkipIme() ? mFirstPostImeInputStage : mFirstInputStage;
        }

        if (stage != null) {
            //继续交付要处理的事件
            stage.deliver(q);
        } 
        ...
   }

//ViewRootImpl#InputStage

        public final void deliver(QueuedInputEvent q) {
            if ((q.mFlags & QueuedInputEvent.FLAG_FINISHED) != 0) {
                //将事件转发到下一阶段,最终又回到该方法
                forward(q);
            } else if (shouldDropInputEvent(q)) {
                //结束输入事件
                finish(q, false);
            } else {
                //处理输入事件
                apply(q, onProcess(q));
            }
        }

//ViewRootImpl#ViewPostImeInputStage 

        protected int onProcess(QueuedInputEvent q) {
            //如果是按键事件,例如返回键、菜单键
            if (q.mEvent instanceof KeyEvent) {
                return processKeyEvent(q);
            } else {
                // If delivering a new non-key event, make sure the window is
                // now allowed to start updating.
                handleDispatchWindowAnimationStopped();
                final int source = q.mEvent.getSource();
                //处理输入源是与显示器相关联的指向设备,例如触摸屏、鼠标
                if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) {
                    return processPointerEvent(q);
                }
                ...
            }
        }
        ...
        private int processPointerEvent(QueuedInputEvent q) {
            ...
            boolean handled = mView.dispatchPointerEvent(event);
            ...
        }

processPointerEvent()方法通过调用mView的dispatchPointerEvent()方法来做进一步的操作,变量mView是一个View子类类型的对象,它的父类View来实现这个方法,这个调用最终就会进入它的父类android.view.View的dispatchPointerEvent()方法中。

public final boolean dispatchPointerEvent(MotionEvent event) {
        if (event.isTouchEvent()) {
            return dispatchTouchEvent(event);
        } else {
            return dispatchGenericMotionEvent(event);
        }
}

View的dispatchPointerEvent()方法首先通过MotionEvent的isTouchEvent()方法来判断当前的事件是不是TouchEvent事件,若当前是TouchEvent事件,就用dispatchTouchEvent()方法来做分发事件的处理,这个分发过程分为两种情况: 


(1)若目前的View是普通的View,即继承View的情况。如果目前View重写了 dispatchTouchEvent()方法,则会进入目前View.dispatchTouchEvent()方法;如果没有重写,例如TextView这种,则会调用View.java的dispatchTouchEvent()默认处理


(2)若目前的View是容器类View,即继承ViewGroup,比如DecorView,由于它实现了dispatchTouchEvent()方法,所以View的dispatchPointerEvent()方法中调用dispatchTouchEvent()方法,就会进入DecorView的dispatchTouchEvent()方法。

 如果当前PhoneWindow跟Activity绑定:

 则Activity对应的ViewRootImpl的mView参数就是通过Activity的setContentView方法-->PhoneWindow的setContentView方法

-->PhoneWindow的installDecor方法创建的DecorView,在Activity的onResume方法之后,在 ActivityThread#handleResumeActivity方法中,会将该View通过WindowManager添加在Activity所挂载的Window上进行展现。

//ViewRootImpl.mView为DecorView.java

    public boolean dispatchTouchEvent(MotionEvent ev) {
        final Window.Callback cb = mWindow.getCallback();
        return cb != null && !mWindow.isDestroyed() && mFeatureId < 0
                ? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev);
    }
//Activity

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, ActivityConfigCallback activityConfigCallback) {

        
        ....
        mWindow = new PhoneWindow(this, window, activityConfigCallback);
        mWindow.setWindowControllerCallback(this);
        //将Activity自己作为callback传入
        mWindow.setCallback(this);
        mWindow.setOnWindowDismissedCallback(this);
        mWindow.getLayoutInflater().setPrivateFactory(this);
        ....

}

则DecorView的dispatchTouchEvent方法里面的mWindow.getCallback()获取的是activity对象,即最终调用activity的dispatchTouchEvent

//Activity
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            onUserInteraction();
        }
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        return onTouchEvent(ev);
    }

activity事件的后续分发请看   https://blog.csdn.net/a2923790861/article/details/106938229

 

 如果当前PhoneWindow跟PopupWindow绑定:

则PopupWindow对应的ViewRootImpl的mView参数就是在PopupWindow的preparePopup方法中创建的PopupWindow.PopupDecorView  ,通过PopupWindow#showAtLocation或者showAsDropDown  --> PopupWindow的invokePopup方法中,将该View通过WindowManager添加在PopupWindow所挂载的Window上进行展现。

//ViewRootImpl.mView为PopupWindow#PopupDecorView

        public boolean dispatchTouchEvent(MotionEvent ev) {
            if (mTouchInterceptor != null && mTouchInterceptor.onTouch(this, ev)) {
                return true;
            }
            //进行事件分发,如果通过setContentView方法传入的contentView不处理事件,则最终交由PopupDecorView的onTouchEvent方法处理事件

            return super.dispatchTouchEvent(ev);
        }

//ViewRootImpl.mView为PopupWindow#PopupDecorView

        @Override
        public boolean onTouchEvent(MotionEvent event) {
            final int x = (int) event.getX();
            final int y = (int) event.getY();

            //如果是按下事件,并且焦点在试图外,则调用PopupWindow#dismiss方法
            if ((event.getAction() == MotionEvent.ACTION_DOWN)
                    && ((x < 0) || (x >= getWidth()) || (y < 0) || (y >= getHeight()))) {
                dismiss();
                return true;
            } else if (event.getAction() == MotionEvent.ACTION_OUTSIDE) {
                //手指 不在控件区域 时触发,即触摸点在PopupWindow外,只有在mOutsideTouchable=true时才可以触发
                dismiss();
                return true;
            } else {
                return super.onTouchEvent(event);
            }
        }

>PopupWindow.java
    
    //每次showAtLocation、showAsDropDown时均会调用createPopupLayoutParams方法,进而调用computeFlags方法
    private int computeFlags(int curFlags) {
        curFlags &= ~(
                WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES |
                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |
                WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE |
                WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH |
                WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS |
                WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM |
                WindowManager.LayoutParams.FLAG_SPLIT_TOUCH);
        .....
        if (mOutsideTouchable) {//设置是否可以接收 MotionEvent.ACTION_OUTSIDE 事件
            curFlags |= WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
        }
        .....
        return curFlags;
    }

 如果当前PhoneWindow跟Dialog绑定:

则Dialog对应的ViewRootImpl的mView参数就是通过Dialog的构造函数创建的PhoneWindow---->Dialog的setContentView方法

--->PhoneWindow的setContentView方法-->PhoneWindow的installDecor方法创建的DecorView,在Dialog的show方法中,

通过WindowManager添加在Dialog所挂载的Window上进行展现。

//ViewRootImpl.mView为DecorView.java

    public boolean dispatchTouchEvent(MotionEvent ev) {
        final Window.Callback cb = mWindow.getCallback();
        return cb != null && !mWindow.isDestroyed() && mFeatureId < 0
                ? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev);
    }
//Dialog
    Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
        if (createContextThemeWrapper) {
            if (themeResId == ResourceId.ID_NULL) {
                final TypedValue outValue = new TypedValue();
                context.getTheme().resolveAttribute(R.attr.dialogTheme, outValue, true);
                themeResId = outValue.resourceId;
            }
            mContext = new ContextThemeWrapper(context, themeResId);
        } else {
            mContext = context;
        }

        mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);

        final Window w = new PhoneWindow(mContext);
        mWindow = w;
        //将Dialog自己作为callback传入
        w.setCallback(this);
        w.setOnWindowDismissedCallback(this);
        w.setOnWindowSwipeDismissedCallback(() -> {
            if (mCancelable) {
                cancel();
            }
        });
        w.setWindowManager(mWindowManager, null, null);
        w.setGravity(Gravity.CENTER);

        mListenersHandler = new ListenersHandler(this);
    }

则DecorView的dispatchTouchEvent方法里面的mWindow.getCallback()获取的是dialog对象,即最终调用dialog的dispatchTouchEvent方法

//Dialog.java
    public boolean dispatchTouchEvent(@NonNull MotionEvent ev) {
        ///mWindow的DecorView或者通过Dialog.setContentView方法传入的View如果处理了事件,返回true
        if (mWindow.superDispatchTouchEvent(ev)) {
            return true;
        }
        //mWindow的DecorView以及子控件都不处理事件,则交由Dialog#onTouchEvent处理
        return onTouchEvent(ev);
    }
>Dialog.java
    public boolean onTouchEvent(@NonNull MotionEvent event) {
        if (mCancelable && mShowing && mWindow.shouldCloseOnTouch(mContext, event)) {
            cancel();
            return true;
        }
        
        return false;
    }

 

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值