framework學習筆記15. Input 輸入事件(1)

原計劃 input 輸入事件的學習分為兩節(jié)內(nèi)容學習并記錄述召,經(jīng)學習發(fā)現(xiàn)并遠不止這些內(nèi)容,所以決定重新寫 input 輸入事件番外篇,如需參考,請閱讀 input 輸入事件番外篇甚负;造成的不便,深表抱歉。

1. InputManagerService的啟動:安卓系統(tǒng)服務的啟動都是在 SystemServer 這個進程中梭域,我們可以在main()方法調用的 new SystemServer().run() 中找打如下代碼:

inputManager = new InputManagerService(context);  // 構建InputManagerService對象斑举,見1.1
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);
mActivityManagerService.setWindowManager(wm);

inputManager.setWindowManagerCallbacks(wm.getInputMonitor());
inputManager.start();  // 執(zhí)行start()方法,見1.2

1.1 InputManagerService 的構造函數(shù):

    // InputManagerService.java中:構造函數(shù)
    public InputManagerService(Context context) {
        this.mContext = context;
        this.mHandler = new InputManagerHandler(DisplayThread.get().getLooper());

        mUseDevInputEventForAudioJack =
                context.getResources().getBoolean(R.bool.config_useDevInputEventForAudioJack);
        // native方法病涨,com_android_server_input_InputManagerService.cpp中
        mPtr = nativeInit(this, mContext, mHandler.getLooper().getQueue());

        LocalServices.addService(InputManagerInternal.class, new LocalService());
    }

我們在看看這個native方法:frameworks/base/services/core/jni目錄中

// native方法懂昂,com_android_server_input_InputManagerService.cpp
static jlong nativeInit(JNIEnv* env, jclass clazz,
        jobject serviceObj, jobject contextObj, jobject messageQueueObj) {
    // MessageQueue的指針
    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);
}

// native方法,com_android_server_input_InputManagerService.cpp
NativeInputManager::NativeInputManager(jobject contextObj,
        jobject serviceObj, const sp<Looper>& looper) :
        mLooper(looper), mInteractive(true) {
    // ...

    sp<EventHub> eventHub = new EventHub();
    mInputManager = new InputManager(eventHub, this, this);
}

上面的代碼中創(chuàng)建了一個 NativeInputManager 對象没宾,并且把這個對象返回了保存在 InputManagerService的 mPtr 中;然后再看看 new InputManager(eventHub, this, this):

// InputManager.cpp中:
InputManager::InputManager(
        const sp<EventHubInterface>& eventHub,
        const sp<InputReaderPolicyInterface>& readerPolicy,
        const sp<InputDispatcherPolicyInterface>& dispatcherPolicy) {
    mDispatcher = new InputDispatcher(dispatcherPolicy);
    // 傳入mDispatcher:讀到的信號沸柔,肯定是要通過 mDispatcher 回調的
    mReader = new InputReader(eventHub, readerPolicy, mDispatcher);
    initialize();
}

// InputManager.cpp中:
void InputManager::initialize() {
    mReaderThread = new InputReaderThread(mReader);
    mDispatcherThread = new InputDispatcherThread(mDispatcher);
}

到這里循衰,我們就獲取到和輸入事件相關的幾個關鍵類:EventHub、InputReader褐澎、InputDispatcher会钝;另外 InputReaderThread 和 InputDispatcherThread 就是兩條處理線程;

1.2 執(zhí)行start()方法:inputManager.start();

    public void start() {
        Slog.i(TAG, "Starting input manager");
        nativeStart(mPtr);  // native 方法:關鍵代碼

        // Add ourself to the Watchdog monitors.
        Watchdog.getInstance().addMonitor(this);

        registerPointerSpeedSettingObserver();
        registerShowTouchesSettingObserver();

        // 接收ACTION_USER_SWITCHED工三,這是關于多用戶切換的操作
        mContext.registerReceiver(new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                updatePointerSpeedFromSettings();
                updateShowTouchesFromSettings();
            }
        }, new IntentFilter(Intent.ACTION_USER_SWITCHED), null, mHandler);

        updatePointerSpeedFromSettings();
        updateShowTouchesFromSettings();
    }

com_android_server_input_InputManagerService.cpp 中的 nativeStart(mPtr) 方法:

// com_android_server_input_InputManagerService.cpp 中:
static void nativeStart(JNIEnv* env, jclass clazz, jlong ptr) {
    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);

    status_t result = im->getInputManager()->start(); // InputManager 的 start() 方法
    if (result) {
        jniThrowRuntimeException(env, "Input manager could not be started.");
    }
}


// InputManager.cpp中:
status_t InputManager::start() {
    status_t result = mDispatcherThread->run("InputDispatcher", PRIORITY_URGENT_DISPLAY);
    if (result) {
        ALOGE("Could not start InputDispatcher thread due to error %d.", result);
        return result;
    }

    result = mReaderThread->run("InputReader", PRIORITY_URGENT_DISPLAY);
    if (result) {
        ALOGE("Could not start InputReader thread due to error %d.", result);

        mDispatcherThread->requestExit();
        return result;
    }

    return OK;
}

小結:nativeStart(mPtr) 方法就是把創(chuàng)建好的 c++ 對象 InputManager 傳入迁酸,然后調用 InputManager 的start() 方法,而在 InputManager::start() 中也很簡單俭正,啟動上面的兩條處理線程(mReaderThread 和mDispatcherThread)奸鬓;

2. InputReader 與 InputReaderThread:InputReaderThread繼承自C++的Thread類,Thread類封裝了pthread線程工具掸读,提供了與Java層Thread類相似的API串远。C++的Thread類提供了一個名為threadLoop()的純虛函數(shù),當線程開始運行后儿惫,將會在內(nèi)建的線程循環(huán)中不斷地調用threadLoop()澡罚,直到此函數(shù)返回false,則退出線程循環(huán)肾请,從而結束線程留搔。
InputReaderThread僅僅重寫了threadLoop()函數(shù):

// InputReader.cpp 中:
bool InputReaderThread::threadLoop() {
    mReader->loopOnce();
    return true;
}

// InputReader.cpp 中:
void InputReader::loopOnce() {
    // ... 省略
    // 見 注釋
    size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);
    // ...
    if (count) {
        processEventsLocked(mEventBuffer, count);  // 我們只關注 input 事件的輸入,見 2.1
    }
    // ... 
}

注釋:這里涉及到EventHub铛铁,我們就在這里開始研究一下EventHub了:
(1)EventHub的構造函數(shù):

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

    /*(1)使用epoll_create()函數(shù)創(chuàng)建一個epoll對象**隔显。EPOLL_SIZE_HINT指定最大監(jiān)聽個數(shù)為8
       這個epoll對象將用來監(jiān)聽設備節(jié)點是否有數(shù)據(jù)可讀(有無事件)***/
    mEpollFd = epoll_create(EPOLL_SIZE_HINT);

    //(2)創(chuàng)建一個inotify對象**。這個inotify對象將被用來監(jiān)聽設備節(jié)點的增刪事件
    mINotifyFd = inotify_init();
    // 將存儲設備節(jié)點的路徑/dev/input作為監(jiān)聽對象添加到inotify對象中避归。當此文件夾下的設備節(jié)點
    int result = inotify_add_watch(mINotifyFd, DEVICE_PATH, IN_DELETE | IN_CREATE);

    /*(3)接下來將mINotifyFd作為epoll的一個監(jiān)控對象荣月。當inotify事件到來時,epoll_wait()將
       立刻返回梳毙,EventHub便可從mINotifyFd中讀取設備節(jié)點的增刪信息哺窄,并作相應處理 */
    struct epoll_event eventItem;
    memset(&eventItem, 0, sizeof(eventItem));
    eventItem.events = EPOLLIN;  // 監(jiān)聽mINotifyFd可讀
    // 注意這里并沒有使用fd字段,而使用了自定義的值EPOLL_ID_INOTIFY
    eventItem.data.u32 = EPOLL_ID_INOTIFY;
    // 將對mINotifyFd的監(jiān)聽注冊到epoll對象中
    result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mINotifyFd, &eventItem);

    /* 在構造函數(shù)剩余的代碼中,EventHub創(chuàng)建了一個名為wakeFds的匿名管道萌业,并將管道讀取端
       的描述符的可讀事件注冊到epoll對象中坷襟。因為InputReader在執(zhí)行getEvents()時會因無事件而
       導致其線程阻塞在epoll_wait()的調用里,然而有時希望能夠立刻喚醒InputReader線程使其處
       理一些請求生年。此時只需向wakeFds管道的寫入端寫入任意數(shù)據(jù)婴程,此時讀取端有數(shù)據(jù)可讀,使得
       epoll_wait()得以返回抱婉,從而達到喚醒InputReader線程的目的*/
    // ...
}

(2)getEvents():
InputReaderThread的線程循環(huán)為Reader子系統(tǒng)提供了運轉的動力档叔,EventHub的工作也是由它驅動的。
InputReader::loopOnce()函數(shù)調用EventHub::getEvents()函數(shù)獲取事件列表蒸绩,所以這個getEvents()是EventHub運行的動力所在衙四,幾乎包含了EventHub的所有工作事項。

// 那么先看看對事件的封裝:RawEvent
struct RawEvent {
    nsecs_t when;             /* 發(fā)生事件時的時間戳 */
    int32_t deviceId;        /* 產(chǎn)生事件的設備Id患亿,它是由EventHub自行分配的传蹈,InputReader
                                    以根據(jù)它從EventHub中獲取此設備的詳細信息 */
    int32_t type;             /* 事件的類型 */
    int32_t code;             /* 事件代碼 */
    int32_t value;            /* 事件值 */
};


// getEvent():
size_t EventHub::getEvents(int timeoutMillis,RawEvent* buffer, size_t bufferSize) {
    /* event指針指向了在buffer下一個可用于存儲事件的RawEvent結構體。每存儲一個事件步藕,
       event指針都回向后偏移一個元素 */
    RawEvent* event = buffer;
    /*capacity記錄了buffer中剩余的元素數(shù)量惦界。當capacity為0時,表示buffer已滿咙冗,此時需要停
       繼續(xù)處理新事件沾歪,并將已處理的事件返回給調用者 */
    size_t capacity = bufferSize;
    /* 接下來的循環(huán)是getEvents()函數(shù)的主體。在這個循環(huán)中乞娄,會先將可用事件放入到buffer中并返回瞬逊。
       如果沒有可用事件,則進入epoll_wait()等待事件的到來仪或,epoll_wait()返回后會重新循環(huán)將可用
       將新事件放入buffer */
    for (;;){
       nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
        /* **(1)首先進行與設備相關的工作确镊。**某些情況下,如EventHub創(chuàng)建后第一次執(zhí)行getEvents()函數(shù)
           時范删,需要掃描/dev/input文件夾下的所有設備節(jié)點并將這些設備打開蕾域。另外,當設備節(jié)點的發(fā)生增
           動作生時到旦,會將設備事件存入到buffer中 */
        // ...
        /* **(2)處理未被InputReader取走的輸入事件與設備事件旨巷。**epoll_wait()所取出的epoll_event
           存儲在mPendingEventItems中,mPendingEventCount指定了mPendingEventItems數(shù)組
           所存儲的事件個數(shù)添忘。而mPendingEventIndex指定尚未處理的epoll_event的索引 */
       while (mPendingEventIndex < mPendingEventCount) {
           const struct epoll_event & eventItem = mPendingEventItems[mPendingEventIndex++];
           /* 在這里分析每一個epoll_event采呐,如果是表示設備節(jié)點可讀,則讀取原始事件并放置到buffer
              中搁骑。如果是表示mINotifyFd可讀斧吐,則設置mPendingINotify為true又固,當InputReader
              將現(xiàn)有的輸入事件都取出后讀取mINotifyFd中的事件,并進行相應的設備加載與卸載操作煤率。
               另外仰冠,如果此epoll_event表示wakeFds的讀取端有數(shù)據(jù)可讀,則設置awake標志為true蝶糯,
              無論此次getEvents()調用有無取到事件洋只,都不會再次進行epoll_wait()進行事件等待 */
           // ...
        }
        //(3)如果mINotifyFd有數(shù)據(jù)可讀,說明設備節(jié)點發(fā)生了增刪操作
        if(mPendingINotify && mPendingEventIndex >= mPendingEventCount) {
           /* 讀取mINotifyFd中的事件昼捍,同時對輸入設備進行相應的加載與卸載操作识虚。這個操作必須當
              InputReader將現(xiàn)有輸入事件讀取并處理完畢后才能進行,因為現(xiàn)有的輸入事件可能來自需要
              被卸載的輸入設備妒茬,InputReader處理這些事件依賴于對應的設備信息 */
            // ...
            deviceChanged= true;
        }
        // 設備節(jié)點增刪操作發(fā)生時舷礼,則重新執(zhí)行循環(huán)體,以便將設備變化的事件放入buffer中
        if(deviceChanged) {
           continue;
        }
        // 如果此次getEvents()調用成功獲取了一些事件郊闯,或者要求喚醒InputReader,則退出循環(huán)并
        // 結束getEvents()的調用蛛株,使InputReader可以立刻對事件進行處理
        if(event != buffer || awoken) {
           break;
        }
        /*(4)如果此次getEvents()調用沒能獲取事件团赁,說明mPendingEventItems中沒有事件可用,
           于是執(zhí)行epoll_wait()函數(shù)等待新的事件到來谨履,將結果存儲到mPendingEventItems里欢摄,并重
           置mPendingEventIndex為0 */
       mPendingEventIndex = 0;
       // ...
        intpollResult = epoll_wait(mEpollFd, mPendingEventItems, EPOLL_MAX_EVENTS,timeoutMillis);
       // ...
        mPendingEventCount= size_t(pollResult);
        // 從epoll_wait()中得到新的事件后,重新循環(huán)笋粟,對新事件進行處理
    }
    // 返回本次getEvents()調用所讀取的事件數(shù)量
    return event - buffer;
}

2.1 上面getEvents()返回了mEventBuffer之后怀挠,初步的處理:

void InputReader::processEventsLocked(const RawEvent* rawEvents, size_t count) {
    for (const RawEvent* rawEvent = rawEvents; count;) {  // 遍歷獲取到的event數(shù)組  
        int32_t type = rawEvent->type;
        size_t batchSize = 1;
        // 如果是常規(guī)的event事件,F(xiàn)IRST_SYNTHETIC_EVENT = DEVICE_ADDED (0x10000000)
        if (type < EventHubInterface::FIRST_SYNTHETIC_EVENT) {  
            int32_t deviceId = rawEvent->deviceId;
            while (batchSize < count) {
                if (rawEvent[batchSize].type >= EventHubInterface::FIRST_SYNTHETIC_EVENT
                        || rawEvent[batchSize].deviceId != deviceId) {
                    break;
                }
                batchSize += 1;
            }
            /* 把這次獲取到的event數(shù)組中屬于同一批次的害捕,進一步處理绿淋,判定條件就是:常規(guī)event以
               及是屬于同一設備 */
            processEventsForDeviceLocked(deviceId, rawEvent, batchSize);  // 見 2.3
        } else {
            // 這里就是3種特殊的 event 類型,例如有時候打開設備的時候會有這個 ADD 事件
            switch (rawEvent->type) {
            case EventHubInterface::DEVICE_ADDED:
                addDeviceLocked(rawEvent->when, rawEvent->deviceId);  // 見 2.2
                break;
            case EventHubInterface::DEVICE_REMOVED:
                removeDeviceLocked(rawEvent->when, rawEvent->deviceId);
                break;
            case EventHubInterface::FINISHED_DEVICE_SCAN:
                handleConfigurationChangedLocked(rawEvent->when);
                break;
            default:
                ALOG_ASSERT(false); // can't happen
                break;
            }
        }
        // 如果再上面沒有處理完event數(shù)組中的成員尝盼,那么依次繼續(xù)  
        count -= batchSize;
        rawEvent += batchSize;
    }
}

2.2 添加設備:addDeviceLocked(rawEvent->when, rawEvent->deviceId)吞滞;
在EventHub中提到將事件封裝成 RawEvent結構體,其中的 type 就是事件的類型盾沫,通過上面的代碼可以知道一共有四種類型裁赠,接下來挑選 DEVICE_ADDED 事件看看。

void InputReader::addDeviceLocked(nsecs_t when, int32_t deviceId) {
    ssize_t deviceIndex = mDevices.indexOfKey(deviceId);
    if (deviceIndex >= 0) {
        return;
    }
    // 這里取之前在EventHub中解析出來的設備相關參數(shù) 
    InputDeviceIdentifier identifier = mEventHub->getDeviceIdentifier(deviceId);
    // 在open設備時 初始化赴精,代表類型
    // open設備調用的方法:EventHub::openDeviceLocked(const charchar *devicePath)
    uint32_t classes = mEventHub->getDeviceClasses(deviceId);
    int32_t controllerNumber = mEventHub->getDeviceControllerNumber(deviceId);
    // 這里又創(chuàng)建一個 InputDervice佩捞,會根據(jù)classes選擇對應的事件處理mapper與當前的設備綁定
    InputDevice* device = createDeviceLocked(deviceId, controllerNumber, identifier, classes);
    device->configure(when, &mConfig, 0);
    device->reset(when);

    mDevices.add(deviceId, device);  // 添加這個 InputDevice  
    bumpGenerationLocked();
}

2.3 常規(guī) event 的處理:

    // InputReader.cpp 中 processEventsForDeviceLocked() 方法:
    void InputReader::processEventsForDeviceLocked(int32_t deviceId,  
            const RawEvent* rawEvents, size_t count) {  
        ssize_t deviceIndex = mDevices.indexOfKey(deviceId);  
        if (deviceIndex < 0) {  
            return;  
        }  

         // 這里根據(jù)id 取出上面添加進去的inputdevice  
        InputDevice* device = mDevices.valueAt(deviceIndex);
        if (device->isIgnored()) {  
            return;  
        }  
      
        device->process(rawEvents, count); // 這里調用了一個process的函數(shù)  
    }  


    // InputReader.cpp 中 process() 方法:
    void InputDevice::process(const RawEvent* rawEvents, size_t count) {  
        // 這里有個map個數(shù),在create時 會根據(jù)classes類型去匹配處理map蕾哟,一般都是匹配一個
        size_t numMappers = mMappers.size(); 
        // 遍歷事件數(shù)組一忱,依次去處理  
        for (const RawEvent* rawEvent = rawEvents; count--; rawEvent++) {   
      
            if (mDropUntilNextSync) {  
                if (rawEvent->type == EV_SYN && rawEvent->code == SYN_REPORT) {  
                    mDropUntilNextSync = false;  
                } else {  
                    ALOGD("Dropped input event while waiting for next input sync.");  
                }  
            } else if (rawEvent->type == EV_SYN && rawEvent->code == SYN_DROPPED) {  
                ALOGI("Detected input event buffer overrun for device %s.", getName().string());  
                mDropUntilNextSync = true;  
                reset(rawEvent->when);  
            } else {  
                for (size_t i = 0; i < numMappers; i++) {  
                    InputMapper* mapper = mMappers[i];  
                    // 調用處理 mapper的process 函數(shù)莲蜘,開始分發(fā)流程,見 3.1
                    mapper->process(rawEvent); 
                }  
            }  
        }  
    }  

3. input事件分發(fā):mapper->process(rawEvent)
在 2.2 中提到 createDeviceLocked() 這個方法給device添加綁定Mapper的掀潮,如下代碼所示菇夸。

InputDevice* InputReader::createDeviceLocked(int32_t deviceId, int32_t controllerNumber,
        const InputDeviceIdentifier& identifier, uint32_t classes) {
    InputDevice* device = new InputDevice(&mContext, deviceId, bumpGenerationLocked(),
            controllerNumber, identifier, classes);

    // External devices.
    if (classes & INPUT_DEVICE_CLASS_EXTERNAL) {
        device->setExternal(true);
    }

    // ...
    // Touchscreens and touchpad devices.
    if (classes & INPUT_DEVICE_CLASS_TOUCH_MT) {
        device->addMapper(new MultiTouchInputMapper(device));
    } else if (classes & INPUT_DEVICE_CLASS_TOUCH) {
        device->addMapper(new SingleTouchInputMapper(device));
    }

    return device;
}

3.1 分發(fā)前的處理:
我們就以SingleTouchInputMapper為例

void SingleTouchInputMapper::process(const RawEvent* rawEvent) {
    TouchInputMapper::process(rawEvent);  // 調用父類的process
    mSingleTouchMotionAccumulator.process(rawEvent); 
}


void TouchInputMapper::process(const RawEvent* rawEvent) {
    mCursorButtonAccumulator.process(rawEvent);
    mCursorScrollAccumulator.process(rawEvent);
    mTouchButtonAccumulator.process(rawEvent);

    if (rawEvent->type == EV_SYN && rawEvent->code == SYN_REPORT) {
        sync(rawEvent->when);
    }
}

/**
以下語句均是將輸入事件信息轉存至類成員變量中:
    mCursorButtonAccumulator.process(rawEvent);
    mCursorScrollAccumulator.process(rawEvent);
    mTouchButtonAccumulator.process(rawEvent);
    mSingleTouchMotionAccumulator.process(rawEvent); 
**/

輸入事件分發(fā)的關鍵在 TouchInputMapper::sync() 方法中:這個同步函數(shù)比較長,下面是簡化后的代碼仪吧,我們之關注一下事件的分發(fā)就行了庄新;

void TouchInputMapper::sync(nsecs_t when) {
    // Sync button state.
    mCurrentButtonState = mTouchButtonAccumulator.getButtonState()
            | mCursorButtonAccumulator.getButtonState();

    // Sync scroll state.
    mCurrentRawVScroll = mCursorScrollAccumulator.getRelativeVWheel();
    mCurrentRawHScroll = mCursorScrollAccumulator.getRelativeHWheel();
    mCursorScrollAccumulator.finishSync();

    // Sync touch state.
    bool havePointerIds = true;
    mCurrentRawPointerData.clear();
    /*調用子類的syncTouch,這里是 SingleTouchMotionAccumulator的syncTouch()薯鼠,
      更新ABS 坐標值,我這里是把數(shù)據(jù)存入到mCurrentRawPointerData中供下面cook */
    syncTouch(when, &havePointerIds);

    // Reset state that we will compute below.
    mCurrentFingerIdBits.clear();
    mCurrentStylusIdBits.clear();
    mCurrentMouseIdBits.clear();
    mCurrentCookedPointerData.clear();  // 先清除一下

    if (mDeviceMode == DEVICE_MODE_DISABLED) {
        // Drop all input if the device is disabled.
        mCurrentRawPointerData.clear();
        mCurrentButtonState = 0;
    } else {
        if (mDeviceMode == DEVICE_MODE_POINTER) {
            PointerUsage pointerUsage = mPointerUsage;
            dispatchPointerUsage(when, policyFlags, pointerUsage);  
        } else {
            if (mDeviceMode == DEVICE_MODE_DIRECT
                    && mConfig.showTouches && mPointerController != NULL) {
                mPointerController->setPresentation(PointerControllerInterface::PRESENTATION_SPOT);                
                mPointerController->fade(PointerControllerInterface::TRANSITION_GRADUAL);
                mPointerController->setButtonState(mCurrentButtonState);
                mPointerController->setSpots(mCurrentCookedPointerData.pointerCoords,
                        mCurrentCookedPointerData.idToIndex,
                        mCurrentCookedPointerData.touchingIdBits);
            }
            // 分發(fā)事件择诈,這里的三個方法最終都回調用 dispatchMotion()
            //            
            dispatchHoverExit(when, policyFlags);
            dispatchTouches(when, policyFlags);
            dispatchHoverEnterAndMove(when, policyFlags);
        }

           // 之后的代碼是一些數(shù)據(jù)保存之類的操作
           // ...
    }
    // ...
    mCurrentRawVScroll = 0;
    mCurrentRawHScroll = 0;
}

dispatchMotion():

void TouchInputMapper::dispatchMotion(nsecs_t when, uint32_t policyFlags, uint32_t source,
        int32_t action, int32_t flags, int32_t metaState, int32_t buttonState, int32_t edgeFlags,
        const PointerProperties* properties, const PointerCoords* coords,
        const uint32_t* idToIndex, BitSet32 idBits,
        int32_t changedId, float xPrecision, float yPrecision, nsecs_t downTime) {
    // ...
    NotifyMotionArgs args(when, getDeviceId(), source, policyFlags,
            action, flags, metaState, buttonState, edgeFlags,
            mViewport.displayId, pointerCount, pointerProperties, pointerCoords,
            xPrecision, yPrecision, downTime);
    // 這里的 getListener() 就是我們之前提到的 傳入mDispatcher作為回調,只是做了進一步封裝出皇;
    getListener()->notifyMotion(&args);  
}

3.2 InputDispatcher 和 InputDispatcherThread:分發(fā)輸入事件
InputDispatcherThread 和之前的 InputReaderThread 一樣羞芍,先看看 threadLoop()方法;

// threadLoop() 方法:
bool InputDispatcherThread::threadLoop() {
    mDispatcher->dispatchOnce();
    return true;
}


// dispatchOnce() 方法:
void InputDispatcher::dispatchOnce() {
    nsecs_t nextWakeupTime = LONG_LONG_MAX;
    { 
        AutoMutex _l(mLock);
        mDispatcherIsAliveCondition.broadcast();

        // 第一次進來時mCommandQueue是空郊艘,能進入此分支荷科;
·       // 然后在 dispatchOnceInnerLocked() 方法中 return;
        // 最終在 mLooper->pollOnce(timeoutMillis) 休眠等待纱注; 
        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

    // Wait for callback or timeout or wake.  (make sure we round up, not down)
    nsecs_t currentTime = now();
    int timeoutMillis = toMillisecondTimeoutDelay(currentTime, nextWakeupTime);
    // looper進入休眠等待畏浆,wake() 方法喚醒(向fd中寫入數(shù)據(jù)就會喚醒);
    mLooper->pollOnce(timeoutMillis);  
}

上面的 dispatcherOnce() 方法在第一次進入時狞贱,會進入休眠狀態(tài)刻获,那么輸入事件的時候是如何調用的呢?這就是我們 3.1 中的 getListener()->notifyMotion(&args)瞎嬉,也就是Dispatcher的 notifyMotion() 方法:

void InputDispatcher::notifyMotion(const NotifyMotionArgs* args) {

    if (!validateMotionEvent(args->action, args->pointerCount, args->pointerProperties)) {
        return;   // 校驗MotionEvent的參數(shù)
    }
    // ...
    bool needWake;
    { // acquire lock
        mLock.lock();

        if (shouldSendMotionToInputFilterLocked(args)) {
            mLock.unlock();

            MotionEvent event;
            event.initialize(args->deviceId, args->source, args->action, args->flags,
                    args->edgeFlags, args->metaState, args->buttonState, 0, 0,
                    args->xPrecision, args->yPrecision,
                    args->downTime, args->eventTime,
                    args->pointerCount, args->pointerProperties, args->pointerCoords);

            policyFlags |= POLICY_FLAG_FILTERED;
            if (!mPolicy->filterInputEvent(&event, policyFlags)) {
                return; // event was consumed by the filter
            }

            mLock.lock();
        }

        // Just enqueue a new motion event.
        // 解析成一個新的MotionEntry:newEntry        
        MotionEntry* newEntry = new MotionEntry(args->eventTime,
                args->deviceId, args->source, policyFlags,
                args->action, args->flags, args->metaState, args->buttonState,
                args->edgeFlags, args->xPrecision, args->yPrecision, args->downTime,
                args->displayId,
                args->pointerCount, args->pointerProperties, args->pointerCoords, 0, 0);

        needWake = enqueueInboundEventLocked(newEntry);  //加入隊列
        mLock.unlock();
    } // release lock

    if (needWake) {
        mLooper->wake();  // 如果需要喚醒InputDispatcher線程, 則調用Looper的喚醒方法
    }
}

加入輸入事件隊列:enqueueInboundEventLocked(EventEntry* entry) 方法

bool InputDispatcher::enqueueInboundEventLocked(EventEntry* entry) {
    bool needWake = mInboundQueue.isEmpty();  // 如果隊列為空 , 則需要喚醒
    mInboundQueue.enqueueAtTail(entry);  // 插入到mInboundQueue隊列尾部
    // ...
    return needWake;
}

InputDispatcherThread::threadLoop() 會一直執(zhí)行mDispatcher->dispatchOnce() 方法蝎毡,喚醒 looper后,在dispatchOnce() 方法中繼續(xù)調用 dispatchOnceInnerLocked(&nextWakeupTime)來處理輸入事件氧枣,下面是這個方法的代碼:

void InputDispatcher::dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) {
    nsecs_t currentTime = now();
    // 判斷事件分發(fā)是否允許沐兵,也就是在未開機、IMS未成功啟動便监、關機等狀態(tài)下是不可用的痒筒,默認值是false
    if (!mDispatchEnabled) {  
        resetKeyRepeatLocked();  //重置重復按鍵次數(shù)
    }
    //判斷分發(fā)線程是否被凍結,是否可以配發(fā)茬贵,默認值是false
    if (mDispatchFrozen) {
        return;
    }
    //判斷此處是不是正在切換應用簿透,以便在home和endcall按鍵到來時,及時丟棄之前的事件解藻,而直接響應特殊鍵
    bool isAppSwitchDue = mAppSwitchDueTime <= currentTime;
    if (mAppSwitchDueTime < *nextWakeupTime) {
        *nextWakeupTime = mAppSwitchDueTime;
    }

    // Ready to start a new event.
    //mPendingEvent是即將要被配發(fā)的事件老充,派發(fā)完成置為null,此處是判斷是否正在配發(fā)事件
    if (! mPendingEvent) {
        if (mInboundQueue.isEmpty()) {  // 如果Event隊列為空的話
            if (isAppSwitchDue) {
                // The inbound queue is empty so the app switch key we were waiting
                // for will never arrive.  Stop waiting for it.
                resetPendingAppSwitchLocked(false);
                isAppSwitchDue = false;
            }

            // Synthesize a key repeat if appropriate.
            if (mKeyRepeatState.lastKeyEntry) {
                if (currentTime >= mKeyRepeatState.nextRepeatTime) {
                    mPendingEvent = synthesizeKeyRepeatLocked(currentTime);
                } else {
                    if (mKeyRepeatState.nextRepeatTime < *nextWakeupTime) {
                        *nextWakeupTime = mKeyRepeatState.nextRepeatTime;
                    }
                }
            }

            // Nothing to do if there is no pending event.
            if (!mPendingEvent) {   // 如果沒有要處理的事件 , 則返回
                return;
            }
        } else {
            // Inbound queue has at least one entry.
            mPendingEvent = mInboundQueue.dequeueAtHead();  // 有Event時螟左,取出第一個Event啡浊;
            traceInboundQueueLengthLocked();
        }

        // Poke user activity for this event.
        if (mPendingEvent->policyFlags & POLICY_FLAG_PASS_TO_USER) {
            pokeUserActivityLocked(mPendingEvent);
        }

       // 重置此次事件分發(fā)的ANR超時時間觅够,如果超過5秒,就會產(chǎn)生ANR
        resetANRTimeoutsLocked();
    }

    // Now we have an event to dispatch.
    // All events are eventually dequeued and processed this way, even if we intend to drop them.
    ALOG_ASSERT(mPendingEvent != NULL);
    bool done = false;
    DropReason dropReason = DROP_REASON_NOT_DROPPED;
    if (!(mPendingEvent->policyFlags & POLICY_FLAG_PASS_TO_USER)) {
        dropReason = DROP_REASON_POLICY;
    } else if (!mDispatchEnabled) {
        dropReason = DROP_REASON_DISABLED;
    }

    if (mNextUnblockedEvent == mPendingEvent) {
        mNextUnblockedEvent = NULL;
    }

    switch (mPendingEvent->type) {
     // 處理Configuration Change消息 , 即屏幕旋轉等等
    case EventEntry::TYPE_CONFIGURATION_CHANGED: {  
        ConfigurationChangedEntry* typedEntry =
                static_cast<ConfigurationChangedEntry*>(mPendingEvent);
        done = dispatchConfigurationChangedLocked(currentTime, typedEntry);
        dropReason = DROP_REASON_NOT_DROPPED; // configuration changes are never dropped
        break;
    }
    // 處理設備重置消息 
    case EventEntry::TYPE_DEVICE_RESET: { 
        DeviceResetEntry* typedEntry =
                static_cast<DeviceResetEntry*>(mPendingEvent);
        done = dispatchDeviceResetLocked(currentTime, typedEntry);
        dropReason = DROP_REASON_NOT_DROPPED; // device resets are never dropped
        break;
    }
    // 處理Key按鍵消息
    case EventEntry::TYPE_KEY: {
        KeyEntry* typedEntry = static_cast<KeyEntry*>(mPendingEvent);
        if (isAppSwitchDue) {
            if (isAppSwitchKeyEventLocked(typedEntry)) {
                resetPendingAppSwitchLocked(true);
                isAppSwitchDue = false;
            } else if (dropReason == DROP_REASON_NOT_DROPPED) {
                dropReason = DROP_REASON_APP_SWITCH;
            }
        }
        if (dropReason == DROP_REASON_NOT_DROPPED
                && isStaleEventLocked(currentTime, typedEntry)) {
            dropReason = DROP_REASON_STALE;
        }
        if (dropReason == DROP_REASON_NOT_DROPPED && mNextUnblockedEvent) {
            dropReason = DROP_REASON_BLOCKED;
        }
        done = dispatchKeyLocked(currentTime, typedEntry, &dropReason, nextWakeupTime);
        break;
    }
    // 判斷時觸屏事件時:
    case EventEntry::TYPE_MOTION: {  
        MotionEntry* typedEntry = static_cast<MotionEntry*>(mPendingEvent);
        if (dropReason == DROP_REASON_NOT_DROPPED && isAppSwitchDue) {
            dropReason = DROP_REASON_APP_SWITCH;
        }
        if (dropReason == DROP_REASON_NOT_DROPPED
                && isStaleEventLocked(currentTime, typedEntry)) {
            dropReason = DROP_REASON_STALE;
        }
        if (dropReason == DROP_REASON_NOT_DROPPED && mNextUnblockedEvent) {
            dropReason = DROP_REASON_BLOCKED;
        }
        done = dispatchMotionLocked(currentTime, typedEntry,
                &dropReason, nextWakeupTime);  // 分發(fā)事件
        break;
    }

    default:
        ALOG_ASSERT(false);
        break;
    }

    if (done) {
        if (dropReason != DROP_REASON_NOT_DROPPED) {
            dropInboundEventLocked(mPendingEvent, dropReason); // 從配發(fā)隊列里面丟棄事件
        }

        releasePendingEventLocked();
        *nextWakeupTime = LONG_LONG_MIN;  // force next poll to wake up immediately
    }
}

分發(fā)事件:

bool InputDispatcher::dispatchMotionLocked(
        nsecs_t currentTime, MotionEntry* entry, DropReason* dropReason, nsecs_t* nextWakeupTime) {
    bool isPointerEvent = entry->source & AINPUT_SOURCE_CLASS_POINTER;
    // 找到目標窗口
    Vector<InputTarget> inputTargets;

    bool conflictingPointerActions = false;
    int32_t injectionResult;
    if (isPointerEvent) {
        // 如果是手指事件的話 ,則找到Touch窗口:關鍵代碼1
        injectionResult = findTouchedWindowTargetsLocked(currentTime,
                entry, inputTargets, nextWakeupTime, &conflictingPointerActions);
    } else {
        // 如果不是手指觸摸事件 , 比如軌跡球事件的話 , 則找到Focus窗口
        injectionResult = findFocusedWindowTargetsLocked(currentTime,
                entry, inputTargets, nextWakeupTime);
    }
    // 如果找到窗口失敗, 返回
    if (injectionResult == INPUT_EVENT_INJECTION_PENDING) {
        return false;
    }
    // 開始向窗口分發(fā)事件:關鍵代碼2
    dispatchEventLocked(currentTime, entry, inputTargets);
    return true;
}

(關鍵代碼1)找到Touch窗口findFocusedWindowTargetsLocked():也是這個方法限制了不同app在不同窗口層級時巷嚣,上面的app不能把觸屏事件分發(fā)給下面的app喘先;

int32_t InputDispatcher::findTouchedWindowTargetsLocked(nsecs_t currentTime,
        const MotionEntry* entry, Vector<InputTarget>& inputTargets, nsecs_t* nextWakeupTime,
        bool* outConflictingPointerActions) {
    // ...
    if (newGesture || (isSplit && maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN)) {
       //從MotionEntry中獲取坐標點
        int32_t pointerIndex = getMotionEventActionPointerIndex(action);
        int32_t x = int32_t(entry->pointerCoords[pointerIndex].
                getAxisValue(AMOTION_EVENT_AXIS_X));
        int32_t y = int32_t(entry->pointerCoords[pointerIndex].
                getAxisValue(AMOTION_EVENT_AXIS_Y));        
        sp<InputWindowHandle> newTouchedWindowHandle;
        bool isTouchModal = false;
        size_t numWindows = mWindowHandles.size();//1
        // 遍歷窗口,找到觸摸過的窗口和窗口之外的外部目標
        for (size_t i = 0; i < numWindows; i++) {//2
            //獲取InputDispatcher中代表窗口的windowHandle 
            sp<InputWindowHandle> windowHandle = mWindowHandles.itemAt(i);
            //得到窗口信息windowInfo 
            const InputWindowInfo* windowInfo = windowHandle->getInfo();
            if (windowInfo->displayId != displayId) {
            //如果displayId不匹配廷粒,開始下一次循環(huán)
                continue; 
            }
            //獲取窗口的flag
            int32_t flags = windowInfo->layoutParamsFlags;
            //如果窗口時可見的
            if (windowInfo->visible) {
               //如果窗口的flag不為FLAG_NOT_TOUCHABLE(窗口是touchable)
                if (! (flags & InputWindowInfo::FLAG_NOT_TOUCHABLE)) {
                   // 如果窗口是focusable或者flag不為FLAG_NOT_FOCUSABLE窘拯,則說明該窗口是"可觸摸模式"
                    isTouchModal = (flags & (InputWindowInfo::FLAG_NOT_FOCUSABLE
                            | InputWindowInfo::FLAG_NOT_TOUCH_MODAL)) == 0;//3
                   //如果窗口是 可觸摸模式或者坐標點落在窗口之上(找到目標窗口)
                    if (isTouchModal || windowInfo->touchableRegionContainsPoint(x, y)) {
                        newTouchedWindowHandle = windowHandle;//4
                        break; // found touched window, exit window loop
                    }
                }
                if (maskedAction == AMOTION_EVENT_ACTION_DOWN
                        && (flags & InputWindowInfo::FLAG_WATCH_OUTSIDE_TOUCH)) {
                    //將符合條件的窗口放入TempTouchState中,以便后續(xù)處理坝茎。
                    mTempTouchState.addOrUpdateWindow(
                            windowHandle, InputTarget::FLAG_DISPATCH_AS_OUTSIDE, BitSet32(0));//5
                }
            }
        // ...
        }
    } else{
        // ...
    }
    // ...
    // 把臨時存放窗口的 TempTouchState 加入到全局的 inputTargets 中
    for (size_t i = 0; i < mTempTouchState.windows.size(); i++) {
        const TouchedWindow& touchedWindow = mTempTouchState.windows.itemAt(i);
        addWindowTargetLocked(touchedWindow.windowHandle, touchedWindow.targetFlags,
                touchedWindow.pointerIds, inputTargets);
    }
   // ...
}

(關鍵代碼2)開始向窗口分發(fā)事件 dispatchEventLocked(currentTime, entry, inputTargets):

void InputDispatcher::dispatchEventLocked(nsecs_t currentTime,
        EventEntry* eventEntry, const Vector<InputTarget>& inputTargets) {

    ALOG_ASSERT(eventEntry->dispatchInProgress); // should already have been set to true

    pokeUserActivityLocked(eventEntry);

    for (size_t i = 0; i < inputTargets.size(); i++) {  // 遍歷 inputTargets
        const InputTarget& inputTarget = inputTargets.itemAt(i);
        
        ssize_t connectionIndex = getConnectionIndexLocked(inputTarget.inputChannel);
        if (connectionIndex >= 0) {
            // 獲取跨進程通訊的連接涤姊;
            sp<Connection> connection = mConnectionsByFd.valueAt(connectionIndex);
            // 通過拿到的連接進行分發(fā);
            prepareDispatchCycleLocked(currentTime, connection, eventEntry, &inputTarget);
        } else {
            // ...
        }
    }
}

prepareDispatchCycleLocked(currentTime, connection, eventEntry, &inputTarget) ->
enqueueDispatchEntriesLocked(currentTime, connection, splitMotionEntry, inputTarget) ->
startDispatchCycleLocked(currentTime, connection)

void InputDispatcher::startDispatchCycleLocked(nsecs_t currentTime,
        const sp<Connection>& connection) {
#if DEBUG_DISPATCH_CYCLE
    ALOGD("channel '%s' ~ startDispatchCycle",
            connection->getInputChannelName());
#endif

    while (connection->status == Connection::STATUS_NORMAL&& !connection->outboundQueue.isEmpty()) {
        DispatchEntry* dispatchEntry = connection->outboundQueue.head;
        dispatchEntry->deliveryTime = currentTime;

        // Publish the event.
        status_t status;
        EventEntry* eventEntry = dispatchEntry->eventEntry;
        switch (eventEntry->type) {
        case EventEntry::TYPE_KEY: {
            // ... key 事件
            break;
        }

        case EventEntry::TYPE_MOTION: {
            MotionEntry* motionEntry = static_cast<MotionEntry*>(eventEntry);

            PointerCoords scaledCoords[MAX_POINTERS];
            const PointerCoords* usingCoords = motionEntry->pointerCoords;

            // Set the X and Y offset depending on the input source.
            float xOffset, yOffset, scaleFactor;
            if ((motionEntry->source & AINPUT_SOURCE_CLASS_POINTER)
                    && !(dispatchEntry->targetFlags & InputTarget::FLAG_ZERO_COORDS)) {
                scaleFactor = dispatchEntry->scaleFactor;
                xOffset = dispatchEntry->xOffset * scaleFactor;
                yOffset = dispatchEntry->yOffset * scaleFactor;
                if (scaleFactor != 1.0f) {
                    for (uint32_t i = 0; i < motionEntry->pointerCount; i++) {
                        scaledCoords[i] = motionEntry->pointerCoords[i];
                        scaledCoords[i].scale(scaleFactor);
                    }
                    usingCoords = scaledCoords;
                }
            } else {
                xOffset = 0.0f;
                yOffset = 0.0f;
                scaleFactor = 1.0f;

                // We don't want the dispatch target to know.
                if (dispatchEntry->targetFlags & InputTarget::FLAG_ZERO_COORDS) {
                    for (uint32_t i = 0; i < motionEntry->pointerCount; i++) {
                        scaledCoords[i].clear();
                    }
                    usingCoords = scaledCoords;
                }
            }

            // Publish the motion event. 
            // 通過連接分發(fā)給遠程端嗤放;
            status = connection->inputPublisher.publishMotionEvent(dispatchEntry->seq,
                    motionEntry->deviceId, motionEntry->source,
                    dispatchEntry->resolvedAction, dispatchEntry->resolvedFlags,
                    motionEntry->edgeFlags, motionEntry->metaState, motionEntry->buttonState,
                    xOffset, yOffset,
                    motionEntry->xPrecision, motionEntry->yPrecision,
                    motionEntry->downTime, motionEntry->eventTime,
                    motionEntry->pointerCount, motionEntry->pointerProperties,
                    usingCoords);
            break;
        }

        // Check the result.
        if (status) {
            // ...
            return;
        }

        // Re-enqueue the event on the wait queue.
        connection->outboundQueue.dequeue(dispatchEntry);
        traceOutboundQueueLengthLocked(connection);
        connection->waitQueue.enqueueAtTail(dispatchEntry);
        traceWaitQueueLengthLocked(connection);
    }
}
最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末思喊,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子次酌,更是在濱河造成了極大的恐慌恨课,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,692評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件岳服,死亡現(xiàn)場離奇詭異庄呈,居然都是意外死亡,警方通過查閱死者的電腦和手機派阱,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,482評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來斜纪,“玉大人贫母,你說我怎么就攤上這事『懈眨” “怎么了腺劣?”我有些...
    開封第一講書人閱讀 162,995評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長因块。 經(jīng)常有香客問我橘原,道長,這世上最難降的妖魔是什么涡上? 我笑而不...
    開封第一講書人閱讀 58,223評論 1 292
  • 正文 為了忘掉前任趾断,我火速辦了婚禮,結果婚禮上吩愧,老公的妹妹穿的比我還像新娘芋酌。我一直安慰自己,他們只是感情好雁佳,可當我...
    茶點故事閱讀 67,245評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著厅瞎,像睡著了一般驶社。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上炸站,一...
    開封第一講書人閱讀 51,208評論 1 299
  • 那天,我揣著相機與錄音疚顷,去河邊找鬼旱易。 笑死,一個胖子當著我的面吹牛荡含,可吹牛的內(nèi)容都是我干的咒唆。 我是一名探鬼主播,決...
    沈念sama閱讀 40,091評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼释液,長吁一口氣:“原來是場噩夢啊……” “哼全释!你這毒婦竟也來了?” 一聲冷哼從身側響起误债,我...
    開封第一講書人閱讀 38,929評論 0 274
  • 序言:老撾萬榮一對情侶失蹤浸船,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后寝蹈,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體李命,經(jīng)...
    沈念sama閱讀 45,346評論 1 311
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,570評論 2 333
  • 正文 我和宋清朗相戀三年箫老,在試婚紗的時候發(fā)現(xiàn)自己被綠了封字。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,739評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡耍鬓,死狀恐怖阔籽,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情牲蜀,我是刑警寧澤笆制,帶...
    沈念sama閱讀 35,437評論 5 344
  • 正文 年R本政府宣布,位于F島的核電站涣达,受9級特大地震影響在辆,放射性物質發(fā)生泄漏。R本人自食惡果不足惜度苔,卻給世界環(huán)境...
    茶點故事閱讀 41,037評論 3 326
  • 文/蒙蒙 一匆篓、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧寇窑,春花似錦奕删、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,677評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽伏钠。三九已至,卻和暖如春谨设,著一層夾襖步出監(jiān)牢的瞬間熟掂,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,833評論 1 269
  • 我被黑心中介騙來泰國打工扎拣, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留赴肚,地道東北人。 一個月前我還...
    沈念sama閱讀 47,760評論 2 369
  • 正文 我出身青樓二蓝,卻偏偏與公主長得像誉券,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子刊愚,可洞房花燭夜當晚...
    茶點故事閱讀 44,647評論 2 354

推薦閱讀更多精彩內(nèi)容

  • 序言 最近在看Android觸摸屏事件相關的源碼踊跟,為了對整個事件體系的了解,所以對事件相關鸥诽,從事件的產(chǎn)生商玫,寫入設備...
    Jensen95閱讀 718評論 0 3
  • 前言 事件分發(fā)機制是Android中的基礎而重要的知識,一般認為Activity#dispatchKeyEvent...
    彭旭銳閱讀 5,599評論 8 61
  • InputManagerService(IMS) Linux內(nèi)核牡借,接受輸入設備的中斷拳昌,并將原始事件的數(shù)據(jù)寫入設備節(jié)...
    傀儡世界閱讀 4,065評論 1 2
  • 久違的晴天,家長會钠龙。 家長大會開好到教室時炬藤,離放學已經(jīng)沒多少時間了。班主任說已經(jīng)安排了三個家長分享經(jīng)驗碴里。 放學鈴聲...
    飄雪兒5閱讀 7,522評論 16 22
  • 今天感恩節(jié)哎沈矿,感謝一直在我身邊的親朋好友。感恩相遇并闲!感恩不離不棄。 中午開了第一次的黨會谷羞,身份的轉變要...
    迷月閃星情閱讀 10,562評論 0 11