以前項目中涉及到對於Android事件的轉發處理相關的需求,所以對於這部分的系統源碼進行了分析,從寫入設備文件到咱們的應用層如何獲取到事件,傳遞事件,消耗事件。整個傳遞機制源碼進行了分析,如下爲對於相關代碼的梳理過程當中的一些代碼剖析記錄。linux
針對事件的分析,這裏以觸摸屏事件爲例,這也是咱們最經常使用的一個事件處理,這裏首先拋出咱們應用層相關使用代碼的例子,而後在來看事件管理的服務的啓動到如何讀取事件,事件的整個分發流程機制,如何一步步的走到咱們的應用層中,對於事件的源碼分析,本次分爲兩個部分,一個是內核層,一個是應用層。android
咱們平時的開發中,對於應用層的處理,主要在於爲View設置觸摸監聽器,而後從其中的回調函數中獲得咱們所須要的相關的觸摸的數據。咱們能夠對View進行設置相應的觸摸監聽器。緩存
@Override
public boolean onTouch(View v, MotionEvent event) {
float x = event.getX();
float y = event.getY();
return true;
}
複製代碼
從這裏,咱們能夠獲取到觸摸事件的相關信息。那麼問題來了,這個事件是從哪裏傳遞來的呢?也就是說這個函數'onTouch'是被誰調用的呢?事件是如何傳遞到該View的?帶着這些問題,咱們自低向上,從設備文件中事件信息的寫入到事件在上層應用的分發作一個系統的分析。bash
對於Android Framework層的一些service,都是在SystemServer進程中建立的。和大多數的Service同樣,InputManager它也是在SystemServer中建立。InputManager負責對於輸入事件的相應處理,在SystemServer的startOtherService中,進行了一些Service的建立。(對於SystemServer啓動相關在後續也會進行介紹們這裏先着於InputManagerService相關。)ide
InputManager inputManager = new InputManagerService(context);
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);
inputManager.setWindowManagerCallbacks(wm.getInputMonitor());
inputManager.start();
複製代碼
首先建立了一個InputManagerService的實例,而後將該服務加入到ServiceManger中,同時爲其設置了窗口的輸入監聽器,而後調用該服務的start方法。這裏咱們從其構造函數開始,而後再看一下它start方法的實現。函數
public InputManagerService(Context context) {
this.mContext = context;
this.mHandler = new InputManagerHandler(DisplayThread.get().getLooper());
mPtr = nativeInit(this, mContext, mHandler.getLooper().getQueue());
....
LocalServices.addService(InputManagerInternal.class, new LocalService());
}
複製代碼
首先建立了一個InputManagerHandler,同時傳入了DisplayThread的looper,這裏的InputManagerHandler爲InputManager的一個內部類,其中進行了一些消息的處理,調用native方法,nativeInit。其native實如今framework/services/core/jni下的com_android_server_input_InputManagerService.cppoop
其nativeInit的實現以下源碼分析
static jlong nativeInit(JNIEnv* env, jclass /* clazz */,
jobject serviceObj, jobject contextObj, jobject messageQueueObj) {
sp<MessageQueue> messageQueue = android_os_MessageQueue_getMessageQueue(env, messageQueueObj);
if (messageQueue == NULL) {
jniThrowRuntimeException(env, "MessageQueue is not initialized.");
return 0;
}
NativeInputManager* im = new NativeInputManager(contextObj, serviceObj,
messageQueue->getLooper());
im->incStrong(0);
return reinterpret_cast<jlong>(im);
}
複製代碼
獲取傳遞的消息隊列,而後根據其建立本地的NativeInputManager。性能
對於NativeInputManger實例的建立。其構造函數以下所示。學習
NativeInputManager::NativeInputManager(jobject contextObj,
jobject serviceObj, const sp<Looper>& looper) :
mLooper(looper), mInteractive(true) {
JNIEnv* env = jniEnv();
...
mInteractive = true;
sp<EventHub> eventHub = new EventHub();
mInputManager = new InputManager(eventHub, this, this);
}
複製代碼
建立了一個EventHub,同時利用EventHub來建立了一個InputManger實例。InputManger在framework/services/inputflinger下,
InputManger的構造函數代碼以下:
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();
}
複製代碼
經過構造函數,咱們能夠看到,這裏建立了一個InputDispatcher根據傳入的分發策略,而後建立了InputReader,傳遞了讀的策略和Dispatcher仍是有EventHub。接下來調用了initialize方法。
void InputManager::initialize()
{
mReaderThread = new InputReaderThread(mReader);
mDispatcherThread = new InputDispatcherThread(mDispatcher);
}
複製代碼
將上面建立的讀和分發事件的核心類傳入咱們所建立的線程之中。到此,在咱們的SystemServer中,對於InputManager的建立已經完成,接下來調用了InputManager的start方法,顧名思義,是對於這個InputManger服務的開始。代碼中的相關實現。
核心調用
nativeStart(mPtr);
複製代碼
static void nativeStart(JNIEnv* env, jclass /* clazz */, jlong ptr) {
NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
status_t result = im->getInputManager()->start();
}
複製代碼
nativeStart函數中調用了InputManager的start方法。該方法執行以下
status_t InputManager::start() {
status_t result = mDispatcherThread->run("InputDispatcher", PRIORITY_URGENT_DISPLAY);
result = mReaderThread->run("InputReader", PRIORITY_URGENT_DISPLAY);
}
複製代碼
至此,咱們能夠看到start方法所作的事情是啓動了在以前建立InputManger的時候,建立的DispatcherThread和ReaderThread.
上述建立啓動流程圖。
InputManager是系統事件處理的核心,它使用了兩個線程,一個是InputReaderThread,讀和處理未加工的輸入事件而後發送事件到由DispatcherThread管理的隊列中。InputDispatcherThread等待着在隊列上新來的事件,而後將它們分發給應用程序。而對於上層的類只是對這的一個包裝。因此要對輸入服務有個細緻的瞭解,對於InputManager類的剖析相當重要。
至此,咱們能夠看到InputManager開啓兩個線程,同時建立了Dispatcher核心類和InputReader核心類。
在NativeInputManager的構造函數中,建立了EventHub,同時將其做爲參數傳遞給InputManager,在InputManager構造函數中,建立InputRead的時候,傳遞了EventHub,EventHub的做用是未來源不一樣的各類信息,轉化成爲一種類型的信息,而後將這些信息提交到上層,給上層作處理。也就是說在輸入設備中,各類類型的輸入信息,經過EventHub進行一個處理以後,將信息轉化爲同一種類型的信息傳遞到上層。EventHub集合來自系統的全部輸出事件,包括模擬的一些事件,此外,其自身創造一些加數據,來代表設備的添加或者刪除。事件會被合成,當設備被添加或者刪除的時候。
設備文件也稱爲設備特定文件。設備文件用來爲操做系統和用戶提供它們表明的設備接口。全部的 Linux 設備文件均位於 /dev
目錄下,是根 (/
) 文件系統的一個組成部分,由於這些設備文件在操做系統啓動過程當中必須可使用。 關於這些設備文件,要記住的一件重要的事情,就是它們大多不是設備驅動程序。更準確地描述來講,它們是設備驅動程序的門戶。數據從應用程序或操做系統傳遞到設備文件,而後設備文件將它傳遞給設備驅動程序,驅動程序再將它發給物理設備。反向的數據通道也能夠用,從物理設備經過設備驅動程序,再到設備文件,最後到達應用程序或其餘設備。
設備文件分爲字符設備文件和塊設備文件
不管是哪一種設備,在 /dev 目錄下都有一個對應的文件(節點),而且每一個設備文件都必須有主/次設備號,主設備號相同的設備是同類設備,使用同一個驅動程序(雖然目前的內核容許多個驅動共享一個主設備號,但絕大多數設備依然遵循一個驅動對應一個主設備號的原則)。
在對源碼分析以前,這裏先講一下epoll,當咱們的應用程序要對多個輸入流進行監控的時候,處理多個輸入流來的數據,咱們能夠採起的一個方式是,對於這每個流進行遍歷,檢測到有數據,讀出。
while true {
for i in stream[]; {
if i has data
read until unavailable
}
}
複製代碼
若是有數據,則讀取直到其中沒有數據爲止。該種方式也稱爲忙輪詢,可是當輸入流一直沒有數據的時候,就是在空消耗CPU,所以產生了select,poll,epoll。select和poll類似,其實現爲,當沒有數據的時候阻塞,一旦有了數據,經過輪詢的方式讀取數據。
while true {
select(streams[])
for i in streams[] {
if i has data
read until unavailable
}
}
複製代碼
可是當咱們的流比較多的時候,對於輪詢檢測每個輸入流,也是比較消耗的,所以,epoll產生了,當沒有數據的時候,豈會阻塞,可是當有數據的時候,epoll可以把哪個流發生了怎樣的事件發送給咱們,這樣咱們對返回的流進行操做就是有意義的了。
先從EventHub的構造函數看起。
EventHub::EventHub(void) :
mBuiltInKeyboardId(NO_BUILT_IN_KEYBOARD), mNextDeviceId(1), mControllerNumbers(),
mOpeningDevices(0), mClosingDevices(0),
mNeedToSendFinishedDeviceScan(false),
mNeedToReopenDevices(false), mNeedToScanDevices(true),
mPendingEventCount(0), mPendingEventIndex(0), mPendingINotify(false) {
acquire_wake_lock(PARTIAL_WAKE_LOCK, WAKE_LOCK_ID);
//建立一個epoll句柄
mEpollFd = epoll_create(EPOLL_SIZE_HINT);
mINotifyFd = inotify_init();
//監視dev/input目錄的變化刪除和建立變化
int result = inotify_add_watch(mINotifyFd, DEVICE_PATH, IN_DELETE | IN_CREATE);
struct epoll_event eventItem;
memset(&eventItem, 0, sizeof(eventItem));
eventItem.events = EPOLLIN;
eventItem.data.u32 = EPOLL_ID_INOTIFY;
//把inotify的句柄加入到epoll監測
result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mINotifyFd, &eventItem);
//建立匿名管道
int wakeFds[2];
result = pipe(wakeFds);
mWakeReadPipeFd = wakeFds[0];
mWakeWritePipeFd = wakeFds[1];
//將管道的讀寫端設置爲非阻塞
result = fcntl(mWakeReadPipeFd, F_SETFL, O_NONBLOCK);
result = fcntl(mWakeWritePipeFd, F_SETFL, O_NONBLOCK);
eventItem.data.u32 = EPOLL_ID_WAKE;
//將管道的讀端加入到epoll監測
result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeReadPipeFd, &eventItem);
int major, minor;
getLinuxRelease(&major, &minor);
// EPOLLWAKEUP was introduced in kerel 3.5
mUsingEpollWakeup = major > 3 || (major == 3 && minor >= 5);
}
複製代碼
構造函數首先建立了epoll句柄,而後建立了inotify句柄,而後建立了一個匿名管道,並將匿名管道設置爲非阻塞,inotify是Linux下的一個監控目錄和文件變化的機制,這裏監控了/dev/input目錄,當這個目錄發生變化,就代表有輸入設備加入或者移除。至此,EventHub只是進行了一些監控操做的處理。而對於EventHub相關事件處理部分的調用則是在建立ReaderThread的時候。
ReaderThread是繼承自Android的Thread實現。下面是一個建立Android中Native 線程的方式。
namespace android {
class MyThread: public Thread {
public:
MyThread();
//virtual ~MyThread();
//若是返回true,循環調用此函數,返回false下一次不會再調用此函數
virtual bool threadLoop();
};
}
複製代碼
mINotifyFd = inotify_init();
複製代碼
從 Linux 2.6.13 內核開始,Linux 就推出了 inotify,容許監控程序打開一個獨立文件描述符,並針對事件集監控一個或者多個文件,例如打開、關閉、移動/重命名、刪除、建立或者改變屬性。inotify_init 是用於建立一個 inotify 實例的系統調用,並返回一個指向該實例的文件描述符。
int result = inotify_add_watch(mINotifyFd, DEVICE_PATH, IN_DELETE | IN_CREATE);
複製代碼
增長對文件或者目錄的監控,並指定須要監控哪些事件。標誌用於控制是否將事件添加到已有的監控中,是否只有路徑表明一個目錄才進行監控,是否要追蹤符號連接,是否進行一次性監控,當首次事件出現後就中止監控。
size_t EventHub::getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSize) {
RawEvent* event = buffer;
size_t capacity = bufferSize;
for(;;) {
....
while (mPendingEventIndex < mPendingEventCount) {
const struct epoll_event& eventItem = mPendingEventItems[mPendingEventIndex++];
.....
ssize_t deviceIndex = mDevices.indexOfKey(eventItem.data.u32);
if (eventItem.events & EPOLLIN) {
int32_t readSize = read(device->fd, readBuffer,
sizeof(struct input_event) * capacity);
if (readSize == 0 || (readSize < 0 && errno == ENODEV)) {
// 設備被移除,關閉設備
deviceChanged = true;
closeDeviceLocked(device);
} else if (readSize < 0) {
//沒法得到事件
if (errno != EAGAIN && errno != EINTR) {
ALOGW("could not get event (errno=%d)", errno);
}
} else if ((readSize % sizeof(struct input_event)) != 0) {
//得到事件的大小非事件類型整數倍
ALOGE("could not get event (wrong size: %d)", readSize);
} else {
int32_t deviceId = device->id == mBuiltInKeyboardId ? 0 : device->id;
//計算讀入了多少事件
size_t count = size_t(readSize) / sizeof(struct input_event);
for (size_t i = 0; i < count; i++) {
struct input_event& iev = readBuffer[i];
if (iev.type == EV_MSC) {
if (iev.code == MSC_ANDROID_TIME_SEC) {
device->timestampOverrideSec = iev.value;
continue;
} else if (iev.code == MSC_ANDROID_TIME_USEC) {
device->timestampOverrideUsec = iev.value;
continue;
}
}
//事件時間相關計算,時間的錯誤可能會致使ANR和一些bug。這裏採起一系列的防範
.........
event->deviceId = deviceId;
event->type = iev.type;
event->code = iev.code;
event->value = iev.value;
event += 1;
capacity -= 1;
}
if (capacity == 0) {
//每到咱們計算完一個事件,capacity就會減1,若是爲0。則表示 結果緩衝區已經滿了,
//須要重置開始讀取時間的索引值,來讀取下一個事件迭代
mPendingEventIndex -= 1;
break;
}
}
//代表讀到事件了,跳出循環
if (event != buffer || awoken) {
break;
}
mPendingEventIndex = 0;
int pollResult = epoll_wait(mEpollFd, mPendingEventItems, EPOLL_MAX_EVENTS, timeoutMillis);
if (pollResult == 0) {
mPendingEventCount = 0;
break;
}
//判斷是否有事件發生
if (pollResult < 0) {
mPendingEventCount = 0;
} else {
//產生的事件的數目
mPendingEventCount = size_t(pollResult);
}
}
//產生的事件數目
return event - buffer;
}
複製代碼