編輯:關於Android編程
請對照AOSP版本:6.0.1_r50。
這裡和老羅當年的版本有很大不同了,有了InputManagerService管理InputManager。
想要探索如何啟動的相關server,需要從SystemServer開始探尋。從SystemServer的進程開始運行開始,它就會創建一些系統server,這裡就會啟動other services。
其中,會創建Input Manager和Window Manager兩個服務。
frameworks/base/services/java/com/android/server/SystemServer.java :
Slog.i(TAG, "Input Manager"); inputManager = new InputManagerService(context); Slog.i(TAG, "Window Manager"); wm = WindowManagerService.main(context, inputManager, mFactoryTestMode != FactoryTest.FACTORY_TEST_LOW_LEVEL, !mFirstBoot, mOnlyCore); ServiceManager.addService(Context.WINDOW_SERVICE, wm); ServiceManager.addService(Context.INPUT_SERVICE, inputManager);
先來仔細端詳一下InputManagerService的構造函數。這裡會調用c++層的初始化函數。
frameworks/base/services/core/java/com/android/server/input/InputManagerService.java :
mPtr = nativeInit(this, mContext, mHandler.getLooper().getQueue());
注意這裡將一個本地的MessageQueue傳遞到c++層。
這一步首先將java層的MessageQueue變為了c++的MessageQueue。然後構造了一個NativeInputManager對象,最後將指向該對象的指針im返回給java層。
frameworks/base/services/core/jni/com_android_server_input_InputManagerService.cpp :
NativeInputManager* im = new NativeInputManager(contextObj, serviceObj, messageQueue->getLooper());
1.4
在構造NativeInputManager時,會創建一個InputManager對象mInputManager。
frameworks/base/services/core/jni/com_android_server_input_InputManagerService.cpp :
sp eventHub = new EventHub();
mInputManager = new InputManager(eventHub, this, this);
1.5
這一步會創建一個dispatcher負責分發輸入事件,一個reader負責獲取事件。
frameworks/native/services/inputflinger/InputManager.cpp :
mDispatcher = new InputDispatcher(dispatcherPolicy);
mReader = new InputReader(eventHub, readerPolicy, mDispatcher);
initialize();
1.6
這裡會創建兩個線程,在以後的步驟中會用來運行前面創建的dispathcer和reader。
frameworks/native/services/inputflinger/InputManager.cpp :
mReaderThread = new InputReaderThread(mReader);
mDispatcherThread = new InputDispatcherThread(mDispatcher);
2. 啟動InputManager
將視線再次回到SystemServer中,在創建完InputManagerService後,需要將這個Service啟動,同樣是在1.1的startOtherServices函數裡,調用了InputManagerService的成員函數start。
2.1
這裡首先調用了c++層的nativeStart,然後InputManagerService將自己交給Watchdog監視。然後注冊了PointerSpeedSetting和ShowTouchesSetting兩個Observer。
frameworks/base/services/core/java/com/android/server/input/InputManagerService.java :
nativeStart(mPtr);
// Add ourself to the Watchdog monitors.
Watchdog.getInstance().addMonitor(this);
registerPointerSpeedSettingObserver();
registerShowTouchesSettingObserver();
這兩個Observer暫時還沒搞清楚是干什麼的。
2.2
這一步將傳來的ptr參數轉化為一個NativeInputManager指針,同時開始啟動NativeInputManager中的InputManager。
frameworks/base/services/core/jni/com_android_server_input_InputManagerService.cpp :
NativeInputManager* im = reinterpret_cast(ptr);
status_t result = im->getInputManager()->start();
2.3
這裡會啟動在1.6中創建的兩個線程,分別用來分發和監聽Input事件。
frameworks/native/services/inputflinger/InputManager.cpp :
status_t result = mDispatcherThread->run("InputDispatcher", PRIORITY_URGENT_DISPLAY);
result = mReaderThread->run("InputReader", PRIORITY_URGENT_DISPLAY);)
3. 啟動InputDispatcher
在2.3中運行的線程以threadLoop為入口,開始進入循環。
3.1
這一步直接將任務交給InputDispatcher的dispatchOnce函數。
frameworks/native/services/inputflinger/InputDispatcher.cpp :
bool InputDispatcherThread::threadLoop() {
mDispatcher->dispatchOnce();
return true;
}
3.2
整個函數如下:
frameworks/native/services/inputflinger/InputDispatcher.cpp :
void InputDispatcher::dispatchOnce() {
nsecs_t nextWakeupTime = LONG_LONG_MAX;
{
// acquire lock
AutoMutex _l(mLock);
mDispatcherIsAliveCondition.broadcast();
// Run a dispatch loop if there are no pending commands.
// The dispatch loop might enqueue commands to run afterwards.
if (!haveCommandsLocked()) {
dispatchOnceInnerLocked(&nextWakeupTime);
}
// Run all pending commands if there are any.
// If any commands were run then force the next poll to wake up immediately.
if (runCommandsLockedInterruptible()) {
nextWakeupTime = LONG_LONG_MIN;
}
} // release lock
nsecs_t currentTime = now();
int timeoutMillis = toMillisecondTimeoutDelay(currentTime, nextWakeupTime);
mLooper->pollOnce(timeoutMillis);
}
在這一步驟中,首先判斷是否有Command還未被執行,如果有則不需要去獲取輸入事件,否則,會調用dispatchOnceInnerLocked函數去嘗試獲取事件,這裡會將nextWakeupTime傳遞過去,讓其設置合適的蘇醒時間,具體內容在以後講解。然後runCommandsLockedInterruptible函數會執行緩存的Command,如果有Command在這一步中被執行,則需要將蘇醒事件設置為
LONG_LONG_MIN,因為執行這些命令需要耗費事件,在這期間可能已經有輸入事件發生了,所有下次循環不需要等待。
最後,根據等待時間和當前時間,計算出需要睡眠的時間,通過pollOnce進入睡眠,等待喚醒,或者超時。
3.3
這裡和上一個章節中的pollOnce道理相同。
3.4
這裡會調用epoll_wait函數,使其在mEpollFd所描述的epoll上等待一段時間,這個epoll監聽著pip的讀寫事件。如果有人在pip中寫入,則會返回,否則等待指定時間後返回。
system/core/libutils/Looper.cpp
int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);
4. 啟動InputReader
在2.3中運行的線程以threadLoop為入口,開始進入循環。
<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxoMyBpZD0="41">4.1
這一步與3.1一樣,將任務丟給InputReader處理。
4.2
這一步會嘗試從mEventHub中獲取事件,如果獲取一些事件,則進行處理。
frameworks/native/services/inputflinger/InputReader.cpp :
//從EventHub中獲取event,這裡先詳細講解這一步
size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);
//省略..
if (count) {
//事件處理,將在後面博客中講解
processEventsLocked(mEventBuffer, count);
}
這裡我們先考慮如何從EventHub中獲取事件的。
4.3
首先這一個函數不是就獲得一個event這麼簡單,它是想獲得一組event,這裡和舊版本有所不同,可見工程師對系統做了優化。這一步內容比較到,讓我們通過注釋來講解。
frameworks/native/services/inputflinger/EventHub.cpp :
size_t EventHub::getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSize) {
ALOG_ASSERT(bufferSize >= 1);
AutoMutex _l(mLock);
struct input_event readBuffer[bufferSize];
//event 指向了存儲事件的位置
RawEvent* event = buffer;
size_t capacity = bufferSize;
bool awoken = false;
//開始循環獲取事件,目的是填充buffer
for (;;) {
nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
// Reopen input devices if needed.
//如果需要重新打開輸入設備,則首先要關閉所有的設備
if (mNeedToReopenDevices) {
mNeedToReopenDevices = false;
ALOGI("Reopening all input devices due to a configuration change.");
closeAllDevicesLocked();
mNeedToScanDevices = true;
break; // return to the caller before we actually rescan
}
// Report any devices that had last been added/removed.
//這裡會移除所有關閉的設備
while (mClosingDevices) {
Device* device = mClosingDevices;
ALOGV("Reporting device closed: id=%d, name=%s\n",
device->id, device->path.string());
mClosingDevices = device->next;
//創建了一個設備removed的event
event->when = now;
event->deviceId = device->id == mBuiltInKeyboardId ? BUILT_IN_KEYBOARD_ID : device->id;
event->type = DEVICE_REMOVED;
event += 1;
delete device;
mNeedToSendFinishedDeviceScan = true;
if (--capacity == 0) {
break;
}
}
//如果上面步驟關閉了設備,這裡需要重新掃描所有的設備
if (mNeedToScanDevices) {
mNeedToScanDevices = false;
//下面會詳細講解這裡如何獲取輸入設備的
scanDevicesLocked();
mNeedToSendFinishedDeviceScan = true;
}
//這裡會添加正在開啟的設備
while (mOpeningDevices != NULL) {
Device* device = mOpeningDevices;
ALOGV("Reporting device opened: id=%d, name=%s\n",
device->id, device->path.string());
mOpeningDevices = device->next;
//同樣這裡會創建設備添加的event
event->when = now;
event->deviceId = device->id == mBuiltInKeyboardId ? 0 : device->id;
event->type = DEVICE_ADDED;
event += 1;
mNeedToSendFinishedDeviceScan = true;
if (--capacity == 0) {
break;
}
}
if (mNeedToSendFinishedDeviceScan) {
mNeedToSendFinishedDeviceScan = false;
event->when = now;
event->type = FINISHED_DEVICE_SCAN;
event += 1;
if (--capacity == 0) {
break;
}
}
//以上步驟主要是負責重新獲取接入的設備,下面將會負責獲得設備中的event
// Grab the next input event.
bool deviceChanged = false;
//開始循環獲取pending的event
//當前處理的Event序號是否小於正在等待的事件數目,這裡會循環讀出所有等待的事件
while (mPendingEventIndex < mPendingEventCount) {
//獲取一個event項
const struct epoll_event& eventItem = mPendingEventItems[mPendingEventIndex++];
//如果這是個INotify事件
if (eventItem.data.u32 == EPOLL_ID_INOTIFY) {
if (eventItem.events & EPOLLIN) {
mPendingINotify = true;
} else {
ALOGW("Received unexpected epoll event 0x%08x for INotify.", eventItem.events);
}
continue;
}
//如果這是一個Id wake事件,則讀出mWakeReadPipeFd的數據,讓等待在這個文件描述符上的線程得到喚醒
if (eventItem.data.u32 == EPOLL_ID_WAKE) {
if (eventItem.events & EPOLLIN) {
ALOGV("awoken after wake()");
awoken = true;
char buffer[16];
ssize_t nRead;
do {
nRead = read(mWakeReadPipeFd, buffer, sizeof(buffer));
} while ((nRead == -1 && errno == EINTR) || nRead == sizeof(buffer));
} else {
ALOGW("Received unexpected epoll event 0x%08x for wake read pipe.",
eventItem.events);
}
continue;
}
//這裡開始處理其它非特殊的event
//獲取event項對應的設備的編號
ssize_t deviceIndex = mDevices.indexOfKey(eventItem.data.u32);
if (deviceIndex < 0) {
ALOGW("Received unexpected epoll event 0x%08x for unknown device id %d.",
eventItem.events, eventItem.data.u32);
continue;
}
//獲取設備,所有已知的設備都放在了mDevices中
Device* device = mDevices.valueAt(deviceIndex);
if (eventItem.events & EPOLLIN) {
//從這個設備中讀出數據流,並且存入readBuffer下
int32_t readSize = read(device->fd, readBuffer,
sizeof(struct input_event) * capacity);
if (readSize == 0 || (readSize < 0 && errno == ENODEV)) {
// Device was removed before INotify noticed.
//先處理一些異常情況,先省略
//...
} else {
//鍵盤事件的id需要特殊處理,一直設置為0
int32_t deviceId = device->id == mBuiltInKeyboardId ? 0 : device->id;
size_t count = size_t(readSize) / sizeof(struct input_event);
//開始循環,從設備中讀出每一個event
for (size_t i = 0; i < count; i++) {
struct input_event& iev = readBuffer[i];
//這裡做了許多處理事件異常時間的工作,先略過
//...
//將獲取的事件存入event
event->deviceId = deviceId;
event->type = iev.type;
event->code = iev.code;
event->value = iev.value;
//event指向下一個位置,容量也隨之減少一個
event += 1;
capacity -= 1;
}
if (capacity == 0) {
// The result buffer is full. Reset the pending event index
// so we will try to read the device again on the next iteration.
//buffer已經填滿,退出處理pending event的循環,將index回到上一個位置,因為該設備event還沒讀完,下次再接著讀
mPendingEventIndex -= 1;
break;
}
}
} else if (eventItem.events & EPOLLHUP) {
//處理一些其它情況,省略
//...
}
}
//在讀出所有event後,才能關閉設備,這裡省略了對此的處理過程
//...
//到這裡說明pending event已經處理完,或者buffer已經塞滿。buffer塞滿或者存了一些事件,則退出最外層填充buffer的循環
// Return now if we have collected any events or if we were explicitly awoken.
if (event != buffer || awoken) {
break;
}
//這裡處理了一些wake lock的事情,省略
//...
//到這一步說明buffer裡沒有填任何事件,同時也沒有pending event
//所以需要等待有人向pip裡寫入一些事件
int pollResult = epoll_wait(mEpollFd, mPendingEventItems, EPOLL_MAX_EVENTS, timeoutMillis);
//時間已到,還是沒有事件,那咱就結束吧
if (pollResult == 0) {
// Timed out.
mPendingEventCount = 0;
break;
}
//出錯了,讓我睡一會,下次再嘗試
if (pollResult < 0) {
// An error occurred.
mPendingEventCount = 0;
// Sleep after errors to avoid locking up the system.
// Hopefully the error is transient.
if (errno != EINTR) {
ALOGW("poll failed (errno=%d)\n", errno);
usleep(100000);
}
} else {
// Some events occurred.
mPendingEventCount = size_t(pollResult);
//獲取了一些event,那麼繼續循環,填充buffer!
}
}
// All done, return the number of events we read.
return event - buffer;
}
4.4
從這裡開始掃描設備。在研究這個函數前,先看一下
DEVICE_PATH的來頭:
frameworks/native/services/inputflinger/EventHub.cpp :
//這一段代碼位於EventHub的構造函數中,這裡使用了linux的inotify機制
//inotify機制可以監控文件的變化
//因此系統可以實時監控設備的添加和移除
mINotifyFd = inotify_init();
int result = inotify_add_watch(mINotifyFd, DEVICE_PATH, IN_DELETE | IN_CREATE);
frameworks/native/services/inputflinger/EventHub.cpp :
void EventHub::scanDevicesLocked() {
//掃描目錄:/dev/input
status_t res = scanDirLocked(DEVICE_PATH);
if(res < 0) {
ALOGE("scan dir failed for %s\n", DEVICE_PATH);
}
if (mDevices.indexOfKey(VIRTUAL_KEYBOARD_ID) < 0) {
//創建一個虛擬鍵盤,這個虛擬設備就是利用inotify機制監控系統中輸入設備變化的
createVirtualKeyboardLocked();
}
}
4.5
這裡開始掃描
/dev/input/目錄下的所有設備。
frameworks/native/services/inputflinger/EventHub.cpp :
status_t EventHub::scanDirLocked(const char *dirname)
{
char devname[PATH_MAX];
char *filename;
DIR *dir;
struct dirent *de;
dir = opendir(dirname);
if(dir == NULL)
return -1;
strcpy(devname, dirname);
filename = devname + strlen(devname);
//filename指向了devname目錄的尾端,方便在其後面添加設備文件
*filename++ = '/';
//讀取該目錄下的每一個設備文件
while((de = readdir(dir))) {
if(de->d_name[0] == '.' &&
(de->d_name[1] == '\0' ||
(de->d_name[1] == '.' && de->d_name[2] == '\0')))
continue;
strcpy(filename, de->d_name);
//打開設備,devname裡是設備的絕對路徑
openDeviceLocked(devname);
}
closedir(dir);
return 0;
}
4.6
frameworks/native/services/inputflinger/EventHub.cpp :
status_t EventHub::openDeviceLocked(const char *devicePath) {
char buffer[80];
//打開文件
int fd = open(devicePath, O_RDWR | O_CLOEXEC);
if(fd < 0) {
ALOGE("could not open %s, %s\n", devicePath, strerror(errno));
return -1;
}
InputDeviceIdentifier identifier;
// Get device name.
if(ioctl(fd, EVIOCGNAME(sizeof(buffer) - 1), &buffer) < 1) {
//fprintf(stderr, "could not get device name for %s, %s\n", devicePath, strerror(errno));
} else {
buffer[sizeof(buffer) - 1] = '\0';
identifier.name.setTo(buffer);
}
// Check to see if the device is on our excluded list
//刪除不排除的設備
//一下步驟從文件中獲取device的基本信息
//...
// Get device driver version.
// Get device identifier.
// Get device physical location.
// Get device unique id.
// Fill in the descriptor.
// Make file descriptor non-blocking for use with poll().
//創建device對象
// Allocate device. (The device object takes ownership of the fd at this point.)
int32_t deviceId = mNextDeviceId++;
Device* device = new Device(fd, deviceId, String8(devicePath), identifier);
//根據device的特征,設置device的class參數
//...
// Register with epoll.
struct epoll_event eventItem;
memset(&eventItem, 0, sizeof(eventItem));
eventItem.events = EPOLLIN;
if (mUsingEpollWakeup) {
eventItem.events |= EPOLLWAKEUP;
}
eventItem.data.u32 = deviceId;
//將該device的文件fd交給epoll監視,以及時獲得它的變化
if (epoll_ctl(mEpollFd, EPOLL_CTL_ADD, fd, &eventItem)) {
ALOGE("Could not add device fd to epoll instance. errno=%d", errno);
delete device;
return -1;
}
//處理時鐘問題..
//添加device
addDeviceLocked(device);
return 0;
4.7
這一步比較輕松,將創建好的device對象放入mDevices即可。
void EventHub::addDeviceLocked(Device* device) {
mDevices.add(device->id, device);
device->next = mOpeningDevices;
mOpeningDevices = device;
}
Android源代碼在編譯之前,要先對編譯環境進行初始化,其中最主要就是指定編譯的類型和目標設備的型號。Android的編譯類型主要有eng、userdebug和user
先看看電影票在線選座功能實現的效果圖: 界面比較粗糙,主要看原理。這個界面主要包括以下幾部分 1、座位 2、左邊的排數 3、左上方的縮略圖 4、縮略圖中的紅色區域 5、手
本篇開始分析按鍵消息事件分發(PS:本篇文章中源碼均是android 6.0,請知曉)先看下Agenda:ViewRootImpl中的dispatchInputEvent
step1:新建一個項目Compass,並將一張指南針圖片導入到res/drawable-hdpi目錄中 step2:設計應用的UI界面,main.xml 復