您的位置:首页 > 移动开发 > Android开发

Android 加载键盘布局文件过程

2014-06-25 16:15 337 查看
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实例

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),

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 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中。

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_t
status=
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 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 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_t
status=
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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: