Android IMS原理解析

       针对平时工作中出现的问题,学习了一下Input处理机制,对在触摸屏幕或按键后事件传到应用进行处理整个过程有了一个大概的了解,将这段时间的所学所得在此记录一下。
       IMS原理涉及的知识点比较多,为了方便阅读,总共分为六篇文章:
       Android IMS原理解析之InputReader
       Android IMS原理解析之InputDispatcher
       Android IMS原理解析之InputChannel
       Android IMS原理解析之processEvent
       Android IMS原理解析之dispatchEvent

Android输入系统

简介

       输入事件的源头是位于dev/input/下的设备节点,即:当我们触摸屏幕或按键后会在该节点下生成数据,而输入系统的终点是由WMS管理的某个窗口。
       最初的输入事件为内核生成的原始事件,而最终交付给窗口的则是KeyEvent或MotionEvent对象。
       Android输入系统的主要工作是读取设备节点中的原始事件,将其加工封装,然后派发给一个指定的窗口以及窗口中的控件。这个过程由InputManagerService系统服务为核心的多个参与者共同完成。


输入系统参与者.png

核心成员

1.Linux内核

       接受输入设备的中断,并将原始事件的输入写入设备节点中;

2.设备节点

       作为内核和IMS的桥梁,将原始事件的数据暴露给用户空间,以便IMS可以从中读取事件;

3.InputManagerService

       Android系统服务,它分为java层和native层两部分;java层负责与WMS通信,native层则是InputReader和InputDispatcher两个输入系统关键组件的运行容器;

4.EventHub

       直接访问所有的设备节点。它通过一个名为getEvent()的函数将所有输入系统相关的待处理的底层事件返回给使用者。这些事件包括原始输入事件、设备节点的增删等;

5.InputReader

       IMS中的关键组件之一,它运行于一个独立的线程中,负责管理输入设备的列表与配置,以及进行输入事件的加工处理。它通过其线程循环不断地通过getEvents()函数从EventHub中将事件取出并进行处理。对于设备节点的增删事件,它会更新输入设备列表与配置。对于原始输入事件,InputReader对其进行翻译、组装、封装为包含更多信息、更具可读性的输入事件,然后交给InputDispatcher进行派发;

6.InputReaderPolicy

       它为InputReader的事件加工处理提供一些策略配置,例如键盘布局信息等;

7.InputDispatcher

       IMS中的另一个关键组件,它也运行于一个独立的线程中。InputDispatcher中保管了来自WMS的所有窗口的信息,其收到来自InputReader的输入事件后,会在其保管的窗口中寻找合适的窗口,并将事件派发给此窗口;

8.InputDispatcherPolicy

       它为InputDispatcher的派发过程提供策略控制。例如截取某些特定的输入事件用作特殊用途,或者阻止将某些事件派发给目标窗口。一个典型的例子就是HOME键被InputDispatcherPolicy截取到PhoneWindowManager中进行处理,并阻止窗口收到HOME键按下的事件;

9.WMS

       不是输入系统的一员,但它对InputDispatcher的正常工作起到重要作用。当新建窗口时,WMS为新窗口和IMS创建了事件传递所用的通道。另外,WMS还将所有窗口的信息,包括窗口的可点击区域,焦点窗口等信息,实时的更新到IMS的InputDispatcher中,使得InputDispatcher可以正确地将事件派发到指定的窗口;

10.ViewRootImpl

       对某些窗口,如壁纸窗口、SurfaceView的窗口来说,窗口就是输入事件派发的终点。而对其他的activity、对话框等使用了Android控件系统的窗口来说,输入事件的终点是控件View。ViewRootImpl将窗口所接收的输入事件沿着控件树将事件派发给感兴趣的控件;

总结

       内核将原始事件写入设备节点中,InputReader不断地通过EventHub将原始事件取出来并翻译加工成Android输入事件,然后交给InputDispatcher。InputDispatcher根据WMS提供的窗口信息将事件交给合适的窗口。窗口的ViewRootImpl对象再沿着控件树将事件派发给感兴趣的控件。控件对其收到的事件做出响应,更新自己的画面、执行特定的动作。所有这些参与者以IMS为核心,构建Android输入体系。

IMS启动及构成

       IMS分为java和native两部分,其启动过程是从java部分的初始化开始,进而完成native部分的初始化;
       同其他核心服务一样,IMS运行在system_server进程里面,在startOtherServices()里面启动:

private void startOtherServices() {
     .......
     .......
     traceBeginAndSlog("StartInputManagerService");
     inputManager = new InputManagerService(context);
     traceEnd();

     traceBeginAndSlog("StartInputManager");
     inputManager.setWindowManagerCallbacks(wm.getInputMonitor());
     inputManager.start();
     traceEnd();
     .......
     ......
}

       通过启动逻辑可以看到,在创建完IMS实例后,先执行了setWindowManagerCallbacks然后执行了start(),接下来进入InputManagerService源码中一起看一下内部实现逻辑:

private static native long nativeInit(InputManagerService service,
            Context context, MessageQueue messageQueue);
private static native void nativeStart(long ptr);

public InputManagerService(Context context) {
    this.mContext = context;
    this.mHandler = new InputManagerHandler(DisplayThread.get().getLooper());

    mPtr = nativeInit(this, mContext, mHandler.getLooper().getQueue());

    ......
}

public void start() {
    Slog.i(TAG, "Starting input manager");
    nativeStart(mPtr);

    .......
}

//该方法设置了callbacks,后续native层的callback最终会调用到这里
public void setWindowManagerCallbacks(WindowManagerCallbacks callbacks) {
    mWindowManagerCallbacks = callbacks;
}

       在构造方法内,执行了nativeInit(),在start()中执行了nativeStart(),以上两个方法都是native方法,具体的逻辑是在native层实现的,对应的类路径为:base/services/core/jni/com_android_server_input_InputManagerService.cpp,接下来先一起看一下nativeInit():

nativeInit()
static jlong nativeInit(JNIEnv* env, jclass /* clazz */,
        jobject serviceObj, jobject contextObj, jobject messageQueueObj) {
    sp<MessageQueue> messageQueue = android_os_MessageQueue_getMessageQueue(env, messageQueueObj);
    if (messageQueue == NULL) {
        jniThrowRuntimeException(env, "MessageQueue is not initialized.");
        return 0;
    }

    NativeInputManager* im = new NativeInputManager(contextObj, serviceObj,
            messageQueue->getLooper());
    im->incStrong(0);
    return reinterpret_cast<jlong>(im);
}

       在nativeInit方法内部,创建了NativeInputManager对象:

class NativeInputManager : public virtual RefBase,
    public virtual InputReaderPolicyInterface,
    public virtual InputDispatcherPolicyInterface,
    public virtual PointerControllerPolicyInterface {
    ......
    ......
}

NativeInputManager::NativeInputManager(jobject contextObj,jobject serviceObj, const sp<Looper>& looper) :
        mLooper(looper), mInteractive(true) {
    JNIEnv* env = jniEnv();

    mContextObj = env->NewGlobalRef(contextObj);
    mServiceObj = env->NewGlobalRef(serviceObj);
    .......
    .......

    sp<EventHub> eventHub = new EventHub();
    mInputManager = new InputManager(eventHub, this, this);
}

       可以看到,NativeInputManager继承了InputReaderPolicyInterface、InputDispatcherPolicyInterface等,后续会讲到,然后回到构造方法,在构造方法内部,先创建了EventHub,然后创建InputManager,并将自身和EventHub实例作为参数传入,再看一下InputManager,位于frameworks/native/services/inputflinger/InputManager.cpp:

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();
}

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

       在实例化InputManager时,会创建InputDispatcher,然后创建InputReader,并把eventHub和mDispatcher作为参数传入,参数dispatcherPolicy和readerPolicy的实现都是NativeInputManager及参数mDispatcher,在后续事件传递过程中会用到,接下来会讲;然后执行了initialize(),分别创建了InputReader运行的线程InputReaderThread和InputDispatcher运行的线程InputDispatcherThread;
       总结一下:在执行nativeInit()后,会创建NativeInputManager对象,然后在NativeInputManager内部创建了EventHub和InputManager对象,接着在InputManager内部创建了InputReader和InputDispatcher对象,并创建了InputReaderThread和InputDispatcherThread,用一张图概况一下:

IMS主要构成.png

       Java层IMS的主要工作是为InputReaderPolicy和InputDispatcherPolicy提供实现,以及与Android系统服务进行协作,最主要的就是WMS;
       NativeInputManager位于IMS的jni层,负责native层的组件与java层的IMS的相互通信,同时它为主要工作是为InputReader和InputDispatcher提供策略请求接口InputReaderPolicyInterface和InputDispatcherPolicyInterface,策略请求被它转发为Java层的IMS,由IMS最终确定;
       InputManager由NativeInputManager创建,是InputReader和InputDispatcher的运行容器,并创建了InputReaderThread和InputDispatcherThread分别承载InputReader和InputDispatcher的运行;

nativeStart()

       接着上面分析,在执行nativeInit()后,会执行nativeStart():

static void nativeStart(JNIEnv* env, jclass /* clazz */, jlong ptr) {
    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);

    status_t result = im->getInputManager()->start();
    if (result) {
        jniThrowRuntimeException(env, "Input manager could not be started.");
    }
}

       可以看到,实际上是调用InputManager的start()方法:

status_t InputManager::start() {
    status_t result = mDispatcherThread->run("InputDispatcher", PRIORITY_URGENT_DISPLAY);
    if (result) {
        ALOGE("Could not start InputDispatcher thread due to error %d.", result);
        return result;
    }

    result = mReaderThread->run("InputReader", PRIORITY_URGENT_DISPLAY);
    if (result) {
        ALOGE("Could not start InputReader thread due to error %d.", result);

        mDispatcherThread->requestExit();
        return result;
    }

    return OK;
}

       在start()内部,启动了InputReader运行的线程InputReaderThread和InputDispatcher运行的线程InputDispatcherThread;
       system_server在启动IMS时,执行的流程如图所示:


image.png
Thread运行

       当两个线程启动后,InputReader在其线程循环中不断地从EventHub中抽取原始输入事件,进行加工处理后将加工所得的事件放入InputDispatcher的派发队列中;InputDispatcher则在其线程循环中将派发队列中的事件取出,查找合适的窗口,将事件写入窗口的事件接收管道中;窗口事件接收线程的Looper从管道中将事件取出,交由事件处理函数进行事件响应。


处理流程.png
成员关系

       从上面的分析可以看到,在创建IMS时,涉及到许多类,先将关系整理如下,接下来通过功能详细分析:

IMS成员关系.png

       接下来从事件读取开始分析,对应文章为:Android IMS原理解析之InputReader

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,222评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,455评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,720评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,568评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,696评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,879评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,028评论 3 409
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,773评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,220评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,550评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,697评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,360评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,002评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,782评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,010评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,433评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,587评论 2 350

推荐阅读更多精彩内容