• Android Honeycomb加载键盘布局文件过程


    原文地址:http://zhougaofeng.ixiezi.com/2011/04/19/honeycomb-keylayout/

    Andriod启动过程中是如何正确加载.kl和.kcm的键盘布局文件?本文就从Honeycomb代码入手,详细介绍开机启动时键盘布局文件的加载过程。
    Honeycom 相较与之前的版本,加入了一个.idc后缀的配置文件,使在不修改系统代码的前提下,我们就可以使用自定义的键盘布局文件,系统中与键盘布局相关的目录为 /system/usr/keychars,/system/usr/keylayout,/system/usr/idc

    一、系统启动过程中SystemServer添加WindowManagerService
    Slog.i(TAG, "Window Manager");
    wm = WindowManagerService.main(context, power,
            factoryTest != SystemServer.FACTORY_TEST_LOW_LEVEL);
    ServiceManager.addService(Context.WINDOW_SERVICE, wm);

    ((ActivityManagerService)ServiceManager.getService("activity"))
            .setWindowManager(wm);

    二、WindowManagerService.java的构造函数,在加载键盘布局方面做了两件事情:1.初始化,构造一个InputManager实例;2.启动,由InputManager.java start()函数实现
    private WindowManagerService(Context context, PowerManagerService pm,
                ……..
                ……..

            mInputManager = new InputManager(context, this); //构造InputManager实例

            PolicyThread thr = new PolicyThread(mPolicy, this, context, pm);
            thr.start();

            synchronized (thr) {
                while (!thr.mRunning) {
                    try {
                        thr.wait();
                    } catch (InterruptedException e) {
                    }
                }
            }

            mInputManager.start(); //调用InputManager.java start()函数

            // Add ourself to the Watchdog monitors.
            Watchdog.getInstance().addMonitor(this);
    }

    三、InputManager.java是本地c代码的包装类,对com_android_server_InputManager.cpp接口函数进行包装,以提供其他java文件调取。
    1.初始化,InputManager.java构造函数中的init()最后调用nativeInit(mCallbacks)
    public InputManager(Context context, WindowManagerService windowManagerService) {
        this.mContext = context;
        this.mWindowManagerService = windowManagerService;
       
        this.mCallbacks = new Callbacks();
       
        init(); //调用init()函数
    }

    private void init() {
        Slog.i(TAG, "Initializing input manager");
        nativeInit(mCallbacks); //java接口,由本地函数实现
    }

    2. 启动,InputManager.java的start()最后调用nativeStart()
    public void start() {
        Slog.i(TAG, "Starting input manager");
        nativeStart(); //java接口,由本地函数实现
    }

    四、com_android_server_InputManager.cpp实现InutManager.java的nativeInit(mCallbacks和nativeStart(), 当然还实现了其他功能的接口函数,这里不再介绍,对于android如何实现java和c之间的转换,我想对于了解jni的来说不难理解。不懂的可以看此 文章学习:http://hi.baidu.com/kellyvivian/blog/item /09cfb541179d2f3387947397.html
    1.初始化,android_server_InputManager_nativeInit在被执行的时候会new一个NativeInputManager(callbacks)实例,NativeInputManager(callbacks)接着又会new一个InputManager(eventHub, this, this)实例
    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(jobject callbacksObj) :
        mFilterTouchEvents(-1), mFilterJumpyTouchEvents(-1), mVirtualKeyQuietTime(-1),
        mMaxEventsPerSecond(-1) {
        JNIEnv* env = jniEnv();

        mCallbacksObj = env->NewGlobalRef(callbacksObj);

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

    2.启动,android_server_InputManager_nativeStart中gNativeInputManager->getInputManager()->start()最终调用的是InputManager.cpp的start()函数
    static void android_server_InputManager_nativeStart(JNIEnv* env, jclass clazz) {
        if (checkInputManagerUnitialized(env)) {
            return;
        }

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

    五、InputManager.cpp中主要有三个函数:initialize()初始化函数,在构造函数中调用;start()开启线程函数;stop()取消线程函数,在虚构函数中调用。
    1.初始化,InputManager.cpp构造函数调用initialize(),期间new一个InputReaderThread线程
    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);
    }

    2.启动,mReaderThread->run("InputReader", PRIORITY_URGENT_DISPLAY)开启初始化时new的InputReaderThread线程
    status_t InputManager::start() {
        ……..

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

            mDispatcherThread->requestExit();
            return result;
        }

        return OK;
    }

    六、InputReader.cpp中定义了InputReaderThread类,继承于Thread类
    1.初始化,InputReaderThread构造函数,初始化一个Thread类
    InputReaderThread::InputReaderThread(const sp<InputReaderInterface>& reader) :
            Thread(/*canCallJava*/ true), mReader(reader) {
    }

    2.启动,run启动线程,Thread run()方法又调用InputReaderThread 的虚函数threadLoop(),接着调用InputReader的loopOnce()方法,最后调用EventHub.cpp的getEvent(& rawEvent)方法
    bool InputReaderThread::threadLoop() {
        mReader->loopOnce();
        return true;
    }
    void InputReader::loopOnce() {
        RawEvent rawEvent;
        mEventHub->getEvent(& rawEvent);

    #if DEBUG_RAW_EVENTS
        LOGD("Input event: device=%d type=0x%x scancode=%d keycode=%d value=%d",
                rawEvent.deviceId, rawEvent.type, rawEvent.scanCode, rawEvent.keyCode,
                rawEvent.value);
    #endif

        process(& rawEvent);
    }

    七、EventHub.cpp是android输入系统的硬件抽象层,维护输入设备的运行,包括Keyboard、 TouchScreen、TraceBall等。
    EventHub.cpp中依次执行getEvent()–>openPlatformInput()–>scanDir(DEVICE_PATH)–> openDevice(devname)

    bool EventHub::openPlatformInput(void) {
        /*
         * Open platform-specific input device(s).
         */
        int res, fd;
        ………
        // Reserve fd index 0 for inotify.
        struct pollfd pollfd;
        pollfd.fd = fd;
        pollfd.events = POLLIN;
        pollfd.revents = 0;
        mFds.push(pollfd);
        mDevices.push(NULL);

        res = scanDir(DEVICE_PATH); //DEVICE_PATH = "/dev/input"
        if(res < 0) {
            LOGE("scan dir failed for %s\n", DEVICE_PATH);
        }

        return true;
    }

    int EventHub::scanDir(const char *dirname)
    {
        ……
            openDevice(devname);
        }
        closedir(dir);
        return 0;
    }

    openDevice方法会打开/dev/input目录下的所有设备文件,读取name、version、id等设备信息,然后执行loadConfiguration()方法,如果键盘设备就会执行loadKeyMap()这个方法
    int EventHub::openDevice(const char *devicePath) {
        ……

        // Load the configuration file for the device.
        loadConfiguration(device);

        ……

        if ((device->classes & INPUT_DEVICE_CLASS_KEYBOARD) != 0) {
            // Load the keymap for the device.
            status_t status = loadKeyMap(device);

            ……
            }

            ……
    }

    Honeycomb与之前版本不同之处是加入loadConfiguration()方 法,它获取与当前设备驱动Vendor、Product、Version匹配的配置文件名,或者是Vendor、Product匹配的配置文件名,具体可 查看Input.cpp中getInputDeviceConfigurationFilePathByDeviceIdentifie和 getInputDeviceConfigurationFilePathByName方法。
    如: kernel/ drivers/input/keyboard/atkbd.c键盘驱动中定义了 input_dev->id.vendor = 0×0001; input_dev->id.product = 0×0001; input_dev->id.version = 0xab41,那么与之对应的配置名为Vendor_0001_Product_0001_Version_ad41.idc,返回这个文件的全路径并赋 值给device->configurationFile。如果/system/user/idc下存在此文件,接下来调用 PropertyMap.cpp的load()方法解析该配置文件并将解析后的信息保存到device->configuration中。
    void EventHub::loadConfiguration(Device* device) {
        device->configurationFile = getInputDeviceConfigurationFilePathByDeviceIdentifier(
                device->identifier, INPUT_DEVICE_CONFIGURATION_FILE_TYPE_CONFIGURATION);
        if (device->configurationFile.isEmpty()) {
            LOGD("No input device configuration file found for device ‘%s’.",
                    device->identifier.name.string());
        } else {
            status_t status = PropertyMap::load(device->configurationFile,
                    &device->configuration);
            if (status) {
                LOGE("Error loading input device configuration file for device ‘%s’.  "
                        "Using default configuration.",
                        device->identifier.name.string());
            }
        }
    }

    EventHub.cpp中loadKeyMap又调用了Keyboard.cpp的KeyMap::load()方法
    status_t EventHub::loadKeyMap(Device* device) {
        return device->keyMap.load(device->identifier, device->configuration);
    }

    八、在Keyboard.cpp的load方法中,首先判断deviceConfiguration参数是否为空,deviceConfiguration的赋值就是上面loadConfiguration()方法所做的工作。
    如 果有.idc的配置文件,那么获取key为keyboard.layout的value给keyLayoutName和key为 keyboard.characterMap的value给keyCharacterMapName,最后调用loadKeyLayout和 loadKeyCharacterMap方法加载此键盘布局文件;如果没有对应的.idc配置文件,则deviceConfiguration为空,就会 接着执行probeKeyMap(deviceIdenfifier, String8("Generic"))方法
    status_t KeyMap::load(const InputDeviceIdentifier& deviceIdenfifier,
            const PropertyMap* deviceConfiguration) {
        // Use the configured key layout if available.
        if (deviceConfiguration) {
            String8 keyLayoutName;
            if (deviceConfiguration->tryGetProperty(String8("keyboard.layout"),
                    keyLayoutName)) {
                status_t status = loadKeyLayout(deviceIdenfifier, keyLayoutName);
                if (status == NAME_NOT_FOUND) {
                    LOGE("Configuration for keyboard device ‘%s’ requested keyboard layout ‘%s’ but "
                            "it was not found.",
                            deviceIdenfifier.name.string(), keyLayoutName.string());
                }
            }

            String8 keyCharacterMapName;
            if (deviceConfiguration->tryGetProperty(String8("keyboard.characterMap"),
                    keyCharacterMapName)) {
                status_t status = loadKeyCharacterMap(deviceIdenfifier, keyCharacterMapName);
                if (status == NAME_NOT_FOUND) {
                    LOGE("Configuration for keyboard device ‘%s’ requested keyboard character "
                            "map ‘%s’ but it was not found.",
                            deviceIdenfifier.name.string(), keyLayoutName.string());
                }
            }

            if (isComplete()) {
                return OK;
            }
        }

        ……
        if (probeKeyMap(deviceIdenfifier, String8("Generic"))) {
            return OK;
        }
        ……
    }

    probeKeyMap方法判断名为Gerneric的布局文件是否存在,若存在就会调用loadKeyLayout和loadKeyCharacterMap方法加载此键盘布局文件
    bool KeyMap::probeKeyMap(const InputDeviceIdentifier& deviceIdentifier,
            const String8& keyMapName) {
        if (!haveKeyLayout()) {
            loadKeyLayout(deviceIdentifier, keyMapName);
        }
        if (!haveKeyCharacterMap()) {
            loadKeyCharacterMap(deviceIdentifier, keyMapName);
        }
        return isComplete();
    }

    至此,Android Honeycomb已经正确加载了键盘布局文件,那么我们如何定制和使用自己的键盘布局文件呢?


    附件:qwerty.idc配置文件内容
    # Copyright (C) 2010 The Android Open Source Project
    #
    # Licensed under the Apache License, Version 2.0 (the "License");
    # you may not use this file except in compliance with the License.
    # You may obtain a copy of the License at
    #
    #      http://www.apache.org/licenses/LICENSE-2.0
    #
    # Unless required by applicable law or agreed to in writing, software
    # distributed under the License is distributed on an "AS IS" BASIS,
    # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    # See the License for the specific language governing permissions and
    # limitations under the License.

    #
    # Emulator keyboard configuration file #1.
    #

    touch.deviceType = touchScreen
    touch.orientationAware = 1

    keyboard.layout = qwerty
    keyboard.characterMap = qwerty
    keyboard.orientationAware = 1
    keyboard.builtIn = 1

    cursor.mode = navigation
    cursor.orientationAware = 1

     
  • 相关阅读:
    js 小数取整的函数
    VS2015 Apache Cordova
    C# 标签(条码)
    异步提交form的时候利用jQuery validate实现表单验证
    调试javascript
    Chrome 控制台console的用法
    MvcPager分页控件以适用Bootstrap
    更好理解接口
    为什么上班一天都是坐着,但仍会疲惫不堪?(转)
    如何解决空虚感?(转)
  • 原文地址:https://www.cnblogs.com/jack2010/p/2516822.html
Copyright © 2020-2023  润新知