Android中按键消息分发机制 上

来源:互联网 发布:淘宝小二商家客服电话 编辑:程序博客网 时间:2024/06/10 12:39

Android中的用户消息主要包括两种,按键消息和触摸消息,在这里我先分析一下按键消息的分发过程,如果分析得比较好,就继续分析一下触摸消息的分发过程,在这里我会将按键消息如何获取以及按键消息如何分发到对应窗口的过程进行分析,中间很多设计技术性的问题我就不深入,因为详细分析会使文章逻辑很混乱,不利于大家明白按键消息的分发过程,当大家从比较宏观的角度把握了按键消息的分发机制之后,自己可以通过阅读源码来了解其中的技术,我这次试用的源码是Android2.3,如果大家试用的是其他版本,难免会有些出入。


在开始之前,我先简要介绍一下Android2.2中的分发机制:

在Java层,WMS有两个类KeyQ和InputDispatchThread(名字可能写错,但是不影响我们分析),KeyQ实例化以后,会启动一个线程,不断从设备驱动中读取用户消息,然后放入到一个队列中,InputDispatchThread也会启动一个线程,不断冲队列中取出消息,并通过ViewRoot中的W对象发送给对应的窗口,最终这个消息是放在了这个应用的主线程的MessageQueue中。


在Android2.3(之后)版本中,对于这一块有很大的变动,它将按键消息的获取移到了Nativate层,并且使用了pipe机制代替了binder机制实现进程间的通信。那我们就开始学习2.3中的按键消息分发机制吧


首先我要明确的告诉你,2.3中也有两个线程,一个负责从驱动获取消息,一个负责将消息派发到相应的窗口,这两个线程是在InputManager中创建的,InputManager是在WMS启动的时候创建的,我们看看InputManager的构造函数:


public InputManager(Context context, WindowManagerService windowManagerService) {        this.mContext = context;        this.mWindowManagerService = windowManagerService;                this.mCallbacks = new Callbacks();                init();}

我们在这里记住这里有个mCallbacks变量,后面会使用到的,然后进入到init函数看看


private void init() {        Slog.i(TAG, "Initializing input manager");        nativeInit(mCallbacks);}


这里一看很简单,主要是调用nativeInit方法

在这里你需要有jni的相关知识,不懂的话 自己可以去补补,在这里我就贴出代码:这个方法在com_android_server_InputManager.cpp中

static void android_server_InputManager_nativeInit(JNIEnv* env, jclass clazz,        jobject callbacks) {    if (gNativeInputManager == NULL) {        gNativeInputManager = new NativeInputManager(callbacks);    } else {        LOGE("Input manager already initialized.");        jniThrowRuntimeException(env, "Input manager already initialized.");    }}

这里主要创建了一个本地的NativeInputManager,我们到NativeInputManager的构造函数看看


NativeInputManager::NativeInputManager(jobject callbacksObj) :        mCallbacksObj = env->NewGlobalRef(callbacksObj);    sp<EventHub> eventHub = new EventHub();    mInputManager = new InputManager(eventHub, this, this);}

在这里主要创建了一个本地的InputManager,这个是和java层的InputManager是对应的。


InputManager::InputManager(        const sp<EventHubInterface>& eventHub,        const sp<InputReaderPolicyInterface>& readerPolicy,        const sp<InputDispatcherPolicyInterface>& dispatcherPolicy) {    mDispatcher = new InputDispatcher(dispatcherPolicy);    mReader = new InputReader(eventHub, readerPolicy, mDispatcher);    initialize();}

这里创建了一个mDispacher和mReader对象,他们类似2.2中的KeyQ和InputDispatcherThread。

我们到initialize中看看

void InputManager::initialize() {    mReaderThread = new InputReaderThread(mReader);    mDispatcherThread = new InputDispatcherThread(mDispatcher);}

没错吧,对应了两个线程mReaderThread和mDispatcherThread。好了,我们没有必要再继续深入了,分析道了这里我们就已经知道有两个线程,一个负责获取按键消息,一个负责分发消息。他们之间是通过pipe通信的。mReaderThread将消息写入管道,mDispathcerThread从管道里面读取消息。

 

下面我们来分析两外一个问题:mDispatchThread是怎么将消息派发到对应窗口的,他是怎么知道分发给哪个窗口的?

在解决这个问题之间最好阅读我的另外一篇文章Android中窗口创建过程,在WindowManagerSerice中有一个变量:ArrayList<WindowState> mWindows,里面存储的是目前系统中所有的窗口信息,InputMonitor中有一个函数updateInutWindowLw,会从mWindows中读出所有的窗口信息,并转换成InputWindow对象。然后去调用InuptManager对象的setInutWindows函数。setInputWindows代码如下:

public void setInputWindows(InputWindow[] windows) {        nativeSetInputWindows(windows);}

就是调用了一个本地方法,我们进入本地方法瞧瞧:在com_andorid_server_InputManager.cpp中

static void android_server_InputManager_nativeSetInputWindows(JNIEnv* env, jclass clazz,        jobjectArray windowObjArray) {    if (checkInputManagerUnitialized(env)) {        return;    }    gNativeInputManager->setInputWindows(env, windowObjArray);}

还记得gNativeInputManager是什么东东吗,如果不记得请回看。这里就是调用NativeInputManager中的setInputWindows,继续跟进:

void NativeInputManager::setInputWindows(JNIEnv* env, jobjectArray windowObjArray) {    Vector<InputWindow> windows;    jsize length = env->GetArrayLength(windowObjArray);    for (jsize i = 0; i < length; i++) {        jobject inputTargetObj = env->GetObjectArrayElement(windowObjArray, i);        if (! inputTargetObj) {            break; // found null element indicating end of used portion of the array        }        windows.push();        InputWindow& window = windows.editTop();        bool valid = populateWindow(env, inputTargetObj, window);        if (! valid) {            windows.pop();        }        env->DeleteLocalRef(inputTargetObj);    }    mInputManager->getDispatcher()->setInputWindows(windows);}

我们只关注最后一句,其实就是调用mDispatcher的setInputWindows,所以清楚了吗?mDispatcher里面包含了目前所有窗口的信息,那么要找到当前互动窗口会很难吗,至于怎么找到的我想这里没有必要跟进了,如果你比较感兴趣可以进去看看。

 

我们再看下一个问题:分发线程和客户端窗口使用pipe进行通信

这个问题我想回避掉,因为它比较难以理解,我们只需要知道其过程,这里我使用文字语言描述一下即可。

每个客户端都会和分发线程有一对管道,分发线程主要往管道写入消息,而客户端从管道里面读取消息,客户端在哪里读我们在后面介绍。

 

在文章的开始我已经和大家说了,Android中用户消息主要分为按键消息和触摸消息,他们两者之间在分发的过程中稍有不同,按键消息在发往客户端时先要调用WMS中的某些函数,如果WMS中并没有处理这个消息,那么才发往客户端的。记得文章开始我让大家记住的mCallbacks吗,当分发线程收到一个按键消息时,会调用mCallbacks中的

interceptKeyBeforDispatching()函数,这个函数有调用了WMS对象中的InputMonitor中的同名函数,最终调用WMS中的函数,具体代码我就不展示了。如果WMS并没有处理这个消息,那么这个消息将会发往客户端。好吧,先写到这里,写多了可能会让大家晕头转向,先好好理解,然后看我的下一篇:Android中按键消息分发机制2




原创粉丝点击