Andriod启动过程中是如何正确加载.kl和.kcm的键盘布局文件?本文就从Honeycomb代码入手,详细介绍开机启动时键盘布局文件的加载过程。
一、系统启动过程中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); }
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) { }
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(); }
# 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
转自:http://zhougaofeng.ixiezi.com/2011/04/19/honeycomb-keylayout/