1. 程式人生 > >Android 載入鍵盤佈局檔案過程

Android 載入鍵盤佈局檔案過程

二、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已經正確載入了鍵盤佈局檔案,那麼我們如何定製和使用自己的鍵盤佈局檔案呢?