Android 加载键盘布局文件过程

来源:互联网 发布:淄博seo推广公司 编辑:程序博客网 时间:2024/06/07 22:05

转自:  http://blog.sina.com.cn/s/blog_6695f9eb0101gf1w.html


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

一、系统启动过程中SystemServer添加WindowManagerService
Slog.i(TAG,"WindowManager");
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.javastart()函数实现
privateWindowManagerService(Contextcontext, PowerManagerService pm,
           ……..
           ……..

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

       PolicyThreadthr= 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)
publicInputManager(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 voidandroid_server_InputManager_nativeInit(JNIEnv* env, jclass clazz,
       jobject callbacks){
    if (gNativeInputManager== NULL) {
       gNativeInputManager=new NativeInputManager(callbacks);
    } else {
       LOGE("Input manager alreadyinitialized.");
       jniThrowRuntimeException(env,"Inputmanager already initialized.");
    }
}

NativeInputManager::NativeInputManager(jobjectcallbacksObj):
   mFilterTouchEvents(-1),mFilterJumpyTouchEvents(-1),mVirtualKeyQuietTime(-1),
   mMaxEventsPerSecond(-1){
   JNIEnv* env = jniEnv();

   mCallbacksObj = env->NewGlobalRef(callbacksObj);

   …….
   sp eventHub = newEventHub();
    mInputManager = newInputManager(eventHub, this,this);
}

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

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

五、InputManager.cpp中主要有三个函数:initialize()初始化函数,在构造函数中调用;start()开启线程函数;stop()取消线程函数,在虚构函数中调用。
1.初始化,InputManager.cpp构造函数调用initialize(),期间new一个InputReaderThread线程
InputManager::InputManager(
       const sp& eventHub,
       const sp& readerPolicy,
       const sp& dispatcherPolicy){
    mDispatcher = newInputDispatcher(dispatcherPolicy);
    mReader = newInputReader(eventHub,readerPolicy, mDispatcher);
   initialize();
}

voidInputManager::initialize() {
    mReaderThread = newInputReaderThread(mReader);
    mDispatcherThread = newInputDispatcherThread(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 startInputReader thread due to error %d.", result);

       mDispatcherThread->requestExit();
       returnresult;
    }

   returnOK;
}

六、InputReader.cpp中定义了InputReaderThread类,继承于Thread类
1.初始化,InputReaderThread构造函数,初始化一个Thread类
InputReaderThread::InputReaderThread(constsp& reader):
       Thread(true),mReader(reader){
}

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

#ifDEBUG_RAW_EVENTS
   LOGD("Input event: device=%dtype=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)

boolEventHub::openPlatformInput(void){
   
   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);
    }

   returntrue;
}

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

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

   // Load the configuration filefor the device.
   loadConfiguration(device);

   ……

   if((device->classes &INPUT_DEVICE_CLASS_KEYBOARD)!=0){
       // Load the keymapfor the device.
       status_tstatus= 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中。
voidEventHub::loadConfiguration(Device* device){
   device->configurationFile = getInputDeviceConfigurationFilePathByDeviceIdentifier(
           device->identifier,INPUT_DEVICE_CONFIGURATION_FILE_TYPE_CONFIGURATION);
    if (device->configurationFile.isEmpty()){
       LOGD("No input deviceconfiguration file found for device ‘%s’.",
               device->identifier.name.string());
    } else {
       status_tstatus= PropertyMap::load(device->configurationFile,
               &device->configuration);
       if(status){
           LOGE("Error loading inputdevice configuration file for device ‘%s’. "
                   "Using defaultconfiguration.",
                   device->identifier.name.string());
       }
    }
}

EventHub.cpp中loadKeyMap又调用了Keyboard.cpp的KeyMap::load()方法
status_tEventHub::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_tKeyMap::load(constInputDeviceIdentifier& deviceIdenfifier,
       const PropertyMap* deviceConfiguration){
   // Use the configured key layoutif available.
    if (deviceConfiguration){
       String8 keyLayoutName;
       if(deviceConfiguration->tryGetProperty(String8("keyboard.layout"),
               keyLayoutName)){
           status_tstatus= loadKeyLayout(deviceIdenfifier, keyLayoutName);
           if(status== NAME_NOT_FOUND){
               LOGE("Configuration forkeyboard 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_tstatus= loadKeyCharacterMap(deviceIdenfifier,keyCharacterMapName);
           if(status== NAME_NOT_FOUND){
               LOGE("Configuration forkeyboard device ‘%s’ requested keyboard character "
                       "map ‘%s’ but it was notfound.",
                       deviceIdenfifier.name.string(),keyLayoutName.string());
           }
       }

       if(isComplete()){
           returnOK;
       }
    }

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

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

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


附件:qwerty.idc配置文件内容
# Copyright (C)2010 The Android Open Source Project
#
# Licensed underthe Apache License, Version 2.0 (the "License");
# you may not usethis file except in compliance with the License.
# You may obtain acopy of the License at
#
    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless requiredby applicable law or agreed to in writing, software
# distributedunder the License is distributed on an "AS IS" BASIS,
# WITHOUTWARRANTIES OR CONDITIONS OF ANY KIND, either express orimplied.
# See the Licensefor the specific language governing permissions and
# limitationsunder the License.

#
# Emulatorkeyboard 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


0 0
原创粉丝点击