`
daojin
  • 浏览: 690221 次
  • 性别: Icon_minigender_1
  • 来自: 西安
社区版块
存档分类
最新评论

Android输入输出机制之来龙去脉之前生后世

阅读更多

密码太多记不了,怎么办?

http://a.app.qq.com/o/simple.jsp?pkgname=com.wa505.kf.epassword

补充一下更详细的解说图,在输入输出系统中存在很多类,这些类很难分清进程边界。

从图中可以看出,整个输入输出采用的是双队列缓冲。

 

其中的进程间通信采用的是 共享内存+管道+信号量,整个通信过程没有涉及Binder。因为使用了android中的匿名共享内存,而共享内存不存在数据复制的问题,因此速度非常快。

 

先讲一下基本一般的输入处理方式的知识。一般的输入输出采用生产者,消费者模式,并构造队列进行处理,如下图

  

这种输入模型在android的系统中很多地方采用,先从最底层说起:

 为了由于触屏事件频率很高,android设计者讲一个循环线程,拆分为两级循环,并做了个队列来进行缓冲。


InputDispatcherThread和InputReaderThread。InputDispatcherThread在自己的循环中对InputReaderThread请求同步,InputReaderThread收到同步信号后,把事件放入InputDispatcher的队列中。

具体代码如下:

InputReader.cpp中有很多InputMapper,有SwitchInputMapper,KeyBoardInputMapper,TrackballInputMapper,SingleTouchInputMapper,

MultiTouchInputMapper。当线程从EventHub读取到Event后,调用这些InputMapper的pocess方法:

 

文件InputReader.cpp中:

bool InputReaderThread::threadLoop() {
    mReader->loopOnce();
    return true;
}


void InputReader::loopOnce() {
    RawEvent rawEvent;
    mEventHub->getEvent(& rawEvent);

#if DEBUG_RAW_EVENTS
    LOGD("Input event: device=0x%x type=0x%x scancode=%d keycode=%d value=%d",
            rawEvent.deviceId, rawEvent.type, rawEvent.scanCode, rawEvent.keyCode,
            rawEvent.value);
#endif

    process(& rawEvent);
}

 process如下

  void InputReader::process(const RawEvent* rawEvent) {
    switch (rawEvent->type) {
    default:
        consumeEvent(rawEvent);
        break;
    }
}

void InputReader::process(const RawEvent* rawEvent) {
    switch (rawEvent->type) {
        consumeEvent(rawEvent);
        break;
    }
}

 

 

consumeEvent(rawEvent);

方法是关键,下面继续跟进;

 

void InputReader::consumeEvent(const RawEvent* rawEvent) {
    int32_t deviceId = rawEvent->deviceId;

    { 
        device->process(rawEvent);
    } // release device registry reader lock
}

   device->process(rawEvent)行, 跟进去:

void InputDevice::process(const RawEvent* rawEvent) {
    size_t numMappers = mMappers.size();
    for (size_t i = 0; i < numMappers; i++) {
        InputMapper* mapper = mMappers[i];
        mapper->process(rawEvent);
    }
}

 下面进入了IputMapper,InputMapper是个纯虚类,process是个纯虚方法,随便找个例子跟进去:

 

void SingleTouchInputMapper::process(const RawEvent* rawEvent) {
    switch (rawEvent->type) {
    case EV_KEY:
        switch (rawEvent->scanCode) {
        case BTN_TOUCH:
            mAccumulator.fields |= Accumulator::FIELD_BTN_TOUCH;
            mAccumulator.btnTouch = rawEvent->value != 0;
            // Don't sync immediately.  Wait until the next SYN_REPORT since we might
            // not have received valid position information yet.  This logic assumes that
            // BTN_TOUCH is always followed by SYN_REPORT as part of a complete packet.
            break;
        }
        break;
    case EV_SYN:
        switch (rawEvent->scanCode) {
        case SYN_REPORT:
            sync(rawEvent->when);
            break;
        }
        break;
    }
}

 最关键的是

 sync(rawEvent->when);

  展开如下:

 

void SingleTouchInputMapper::sync(nsecs_t when) {

    syncTouch(when, true);

 
}

 

void TouchInputMapper::syncTouch(nsecs_t when, bool havePointerIds) {
   
    if (touchResult == DISPATCH_TOUCH) {
        detectGestures(when);
        dispatchTouches(when, policyFlags);
    }
}

 

 这两行,一个是虚拟键盘,一个是触摸屏。

      TouchResult touchResult = consumeOffScreenTouches(when, policyFlags);

dispatchTouches

    只说触摸屏,虚拟键类似,触摸屏调用的是  

 

void TouchInputMapper::dispatchTouches(nsecs_t when, uint32_t policyFlags) {
        // Dispatch pointer down events using the new pointer locations.
        while (!downIdBits.isEmpty()) {
            dispatchTouch(when, policyFlags, &mCurrentTouch,
                    activeIdBits, downId, pointerCount, motionEventAction);
        }
    }
}

 

dispatchTouch(when, policyFlags, &mCurrentTouch,
                    activeIdBits, downId, pointerCount, motionEventAction);

这个方法展开如下:

 

void TouchInputMapper::dispatchTouch(nsecs_t when, uint32_t policyFlags,
        TouchData* touch, BitSet32 idBits, uint32_t changedId, uint32_t pointerCount,
        int32_t motionEventAction) {
    int32_t pointerIds[MAX_POINTERS];
    PointerCoords pointerCoords[MAX_POINTERS];
    int32_t motionEventEdgeFlags = 0;
    float xPrecision, yPrecision;

    { 
    getDispatcher()->notifyMotion(when, getDeviceId(), getSources(), policyFlags,
            motionEventAction, 0, getContext()->getGlobalMetaState(), motionEventEdgeFlags,
            pointerCount, pointerIds, pointerCoords,
            xPrecision, yPrecision, mDownTime);
}

 

  这样就到了InputDiaptcher的notifyMotion方法,这个方法很长,都再处理MOVE事件,将无用的删除后,留下如下关键代码:

 

 void InputDispatcher::notifyMotion(nsecs_t eventTime, int32_t deviceId, int32_t source,
        uint32_t policyFlags, int32_t action, int32_t flags, int32_t metaState, int32_t edgeFlags,
        uint32_t pointerCount, const int32_t* pointerIds, const PointerCoords* pointerCoords,
        float xPrecision, float yPrecision, nsecs_t downTime) {      

 // Just enqueue a new motion event.
        MotionEntry* newEntry = mAllocator.obtainMotionEntry(eventTime,
                deviceId, source, policyFlags, action, flags, metaState, edgeFlags,
                xPrecision, yPrecision, downTime,
                pointerCount, pointerIds, pointerCoords);

        needWake = enqueueInboundEventLocked(newEntry);
}

 最后一句:

 needWake = enqueueInboundEventLocked(newEntry);

 

bool InputDispatcher::enqueueInboundEventLocked(EventEntry* entry) {
    bool needWake = mInboundQueue.isEmpty();
    mInboundQueue.enqueueAtTail(entry);

    switch (entry->type) {
    case EventEntry::TYPE_KEY: {
        KeyEntry* keyEntry = static_cast<KeyEntry*>(entry);
        if (isAppSwitchKeyEventLocked(keyEntry)) {
            if (keyEntry->action == AKEY_EVENT_ACTION_DOWN) {
                mAppSwitchSawKeyDown = true;
            } else if (keyEntry->action == AKEY_EVENT_ACTION_UP) {
                if (mAppSwitchSawKeyDown) {
#if DEBUG_APP_SWITCH
                    LOGD("App switch is pending!");
#endif
                    mAppSwitchDueTime = keyEntry->eventTime + APP_SWITCH_TIMEOUT;
                    mAppSwitchSawKeyDown = false;
                    needWake = true;
                }
            }
        }
        break;
    }
    }

    return needWake;
}

 

mInboundQueue正是上面所说的队列。到此为止,从InputReader插入到队列就完成了。

 那么InputDispatcher又是如何从队列中取出来的呢?累了。

  InputDiapather的

dispatchOnce

方法如下:

void InputDispatcher::dispatchOnce() {
    nsecs_t keyRepeatTimeout = mPolicy->getKeyRepeatTimeout();
    nsecs_t keyRepeatDelay = mPolicy->getKeyRepeatDelay();

    nsecs_t nextWakeupTime = LONG_LONG_MAX;
    { // acquire lock
        AutoMutex _l(mLock);
        dispatchOnceInnerLocked(keyRepeatTimeout, keyRepeatDelay, & nextWakeupTime);

        if (runCommandsLockedInterruptible()) {
            nextWakeupTime = LONG_LONG_MIN;  // force next poll to wake up immediately
        }
    } // release lock

    // Wait for callback or timeout or wake.  (make sure we round up, not down)
    nsecs_t currentTime = now();
    int32_t timeoutMillis;
    if (nextWakeupTime > currentTime) {
        uint64_t timeout = uint64_t(nextWakeupTime - currentTime);
        timeout = (timeout + 999999LL) / 1000000LL;
        timeoutMillis = timeout > INT_MAX ? -1 : int32_t(timeout);
    } else {
        timeoutMillis = 0;
    }

    mLooper->pollOnce(timeoutMillis);
}

 最关键的是

  dispatchOnceInnerLocked(keyRepeatTimeout, keyRepeatDelay, & nextWakeupTime);

    mLooper->pollOnce(timeoutMillis);//这个是个回调。
    

代码又长又臭

 

 

void InputDispatcher::dispatchOnceInnerLocked(nsecs_t keyRepeatTimeout,
        nsecs_t keyRepeatDelay, nsecs_t* nextWakeupTime) {
    case EventEntry::TYPE_MOTION: {
        MotionEntry* typedEntry = static_cast<MotionEntry*>(mPendingEvent);
        if (dropReason == DROP_REASON_NOT_DROPPED && isAppSwitchDue) {
            dropReason = DROP_REASON_APP_SWITCH;
        }
        done = dispatchMotionLocked(currentTime, typedEntry,
                &dropReason, nextWakeupTime);
        break;
    }

}

 

dispatchMotionLocked

方法调用prepareDispatchCycleLocked,调用startDispatchCycleLocked,最终调用

       // Publish the key event.
        status = connection->inputPublisher.publishKeyEvent(keyEntry->deviceId, keyEntry->source,
                action, flags, keyEntry->keyCode, keyEntry->scanCode,
                keyEntry->metaState, keyEntry->repeatCount, keyEntry->downTime,
                keyEntry->eventTime);

或者 // Publish the motion event and the first motion sample.
        status = connection->inputPublisher.publishMotionEvent(motionEntry->deviceId,
                motionEntry->source, action, flags, motionEntry->edgeFlags, motionEntry->metaState,
                xOffset, yOffset,
                motionEntry->xPrecision, motionEntry->yPrecision,
                motionEntry->downTime, firstMotionSample->eventTime,
                motionEntry->pointerCount, motionEntry->pointerIds,
                firstMotionSample->pointerCoords);

 

    然后// Send the dispatch signal.
    status = connection->inputPublisher.sendDispatchSignal();
    if (status) {
        LOGE("channel '%s' ~ Could not send dispatch signal, status=%d",
                connection->getInputChannelName(), status);
        abortBrokenDispatchCycleLocked(currentTime, connection);
        return;
    }

至此,InputDisapatcher也结束了。

 

既然发布出去,必然有订阅者:在InputTransport.cpp中

status_t InputConsumer::consume(InputEventFactoryInterface* factory, InputEvent** outEvent) {
#if DEBUG_TRANSPORT_ACTIONS
    LOGD("channel '%s' consumer ~ consume",
            mChannel->getName().string());
#endif

    *outEvent = NULL;

    int ashmemFd = mChannel->getAshmemFd();
    int result = ashmem_pin_region(ashmemFd, 0, 0);
    if (result != ASHMEM_NOT_PURGED) {
        if (result == ASHMEM_WAS_PURGED) {
            LOGE("channel '%s' consumer ~ Error %d pinning ashmem fd %d because it was purged "
                    "which probably indicates that the publisher and consumer are out of sync.",
                    mChannel->getName().string(), result, ashmemFd);
            return INVALID_OPERATION;
        }

        LOGE("channel '%s' consumer ~ Error %d pinning ashmem fd %d.",
                mChannel->getName().string(), result, ashmemFd);
        return UNKNOWN_ERROR;
    }

    if (mSharedMessage->consumed) {
        LOGE("channel '%s' consumer ~ The current message has already been consumed.",
                mChannel->getName().string());
        return INVALID_OPERATION;
    }

    // Acquire but *never release* the semaphore.  Contention on the semaphore is used to signal
    // to the publisher that the message has been consumed (or is in the process of being
    // consumed).  Eventually the publisher will reinitialize the semaphore for the next message.
    result = sem_wait(& mSharedMessage->semaphore);
    if (result < 0) {
        LOGE("channel '%s' consumer ~ Error %d in sem_wait.",
                mChannel->getName().string(), errno);
        return UNKNOWN_ERROR;
    }

    mSharedMessage->consumed = true;

    switch (mSharedMessage->type) {
    case AINPUT_EVENT_TYPE_KEY: {
        KeyEvent* keyEvent = factory->createKeyEvent();
        if (! keyEvent) return NO_MEMORY;

        populateKeyEvent(keyEvent);

        *outEvent = keyEvent;
        break;
    }

    case AINPUT_EVENT_TYPE_MOTION: {
        MotionEvent* motionEvent = factory->createMotionEvent();
        if (! motionEvent) return NO_MEMORY;

        populateMotionEvent(motionEvent);

        *outEvent = motionEvent;
        break;
    }

    default:
        LOGE("channel '%s' consumer ~ Received message of unknown type %d",
                mChannel->getName().string(), mSharedMessage->type);
        return UNKNOWN_ERROR;
    }

    return OK;
}

 也许我们最关心的是如何订阅的,不得不取看一下JNI的代码,文件android_view_InputQueue.cpp

 聚焦到这里

 

status_t NativeInputQueue::registerInputChannel(JNIEnv* env, jobject inputChannelObj,
        jobject inputHandlerObj, jobject messageQueueObj) {
    sp<InputChannel> inputChannel = android_view_InputChannel_getInputChannel(env,
            inputChannelObj);
    if (inputChannel == NULL) {
        LOGW("Input channel is not initialized.");
        return BAD_VALUE;
    }

#if DEBUG_REGISTRATION
    LOGD("channel '%s' - Registered", inputChannel->getName().string());
#endif

    sp<Looper> looper = android_os_MessageQueue_getLooper(env, messageQueueObj);

    { // acquire lock
        AutoMutex _l(mLock);

        if (getConnectionIndex(inputChannel) >= 0) {
            LOGW("Attempted to register already registered input channel '%s'",
                    inputChannel->getName().string());
            return BAD_VALUE;
        }

        uint16_t connectionId = mNextConnectionId++;
        sp<Connection> connection = new Connection(connectionId, inputChannel, looper);
        status_t result = connection->inputConsumer.initialize();
        if (result) {
            LOGW("Failed to initialize input consumer for input channel '%s', status=%d",
                    inputChannel->getName().string(), result);
            return result;
        }

        connection->inputHandlerObjGlobal = env->NewGlobalRef(inputHandlerObj);

        int32_t receiveFd = inputChannel->getReceivePipeFd();
        mConnectionsByReceiveFd.add(receiveFd, connection);

        looper->addFd(receiveFd, 0, ALOOPER_EVENT_INPUT, handleReceiveCallback, this);
    } // release lock

    android_view_InputChannel_setDisposeCallback(env, inputChannelObj,
            handleInputChannelDisposed, this);
    return OK;
}

 

 

  也许更想知道的是消息队列在什么地方,进入InputQueue.java来看

 

    public static void registerInputChannel(InputChannel inputChannel, InputHandler inputHandler,
            MessageQueue messageQueue) {
        if (inputChannel == null) {
            throw new IllegalArgumentException("inputChannel must not be null");
        }
        if (inputHandler == null) {
            throw new IllegalArgumentException("inputHandler must not be null");
        }
        if (messageQueue == null) {
            throw new IllegalArgumentException("messageQueue must not be null");
        }
        
        synchronized (sLock) {
            if (DEBUG) {
                Slog.d(TAG, "Registering input channel '" + inputChannel + "'");
            }
            
            nativeRegisterInputChannel(inputChannel, inputHandler, messageQueue);
        }
    }
    

 

在ViewRoot.java中有这么几行

 

                    InputQueue.registerInputChannel(mInputChannel, mInputHandler,
                            Looper.myQueue());

 完毕。

这才牵涉到管道的问题,哪个Java中的Channel对应的正是linux系统的管道。有了管道,才能通过 跨进程方式回调回来,为什么是这个入口,上面进行了解释。具体参照INputQUEUE这个java类的JNI方法

int NativeInputQueue::handleReceiveCallback(int receiveFd, int events, void* data)

这个方法被InputQueue的RegisterInputChannel注册给了系统.系统通过回调,回调的是这个ALOOPER_EVENT_INPUT事件。

 

 

looper就是android中的【用户进程的循环】

注册的代码为 :

  looper->addFd(receiveFd, 0, ALOOPER_EVENT_INPUT, handleReceiveCallback, this);

回调的java函数为

    
回调的java代码的方法入口为:InputQueue.java中的。

@SuppressWarnings("unused")
    private static void dispatchMotionEvent(InputHandler inputHandler,
            MotionEvent event, long finishedToken) {
        Runnable finishedCallback = FinishedCallback.obtain(finishedToken);
        inputHandler.handleMotion(event, finishedCallback);
    }

 这样就回调到了ViewRoot中

  • 大小: 3.8 KB
  • 大小: 4.9 KB
  • 大小: 33 KB
1
0
分享到:
评论

相关推荐

    MFC源码剖析之——MFC来龙去脉

    ### MFC源码剖析之——MFC来龙去脉 #### 一、MFC与传统C/SDK程序对比 在Windows编程中,开发者通常有两种选择:一种是使用Microsoft Foundation Classes (MFC),另一种则是直接使用Windows API(通常称为C/SDK编程...

    Android性能优化来龙去脉总结

    这是因为Android的内存管理机制——Generational Heap Memory模型,当对象在年轻代(Young Generation)存活一段时间后,会逐渐晋升到老年代(Old Generation)和永久代(Permanent Generation)。一旦发生内存泄露...

    参考资料-斜坡补偿到的来龙去脉与实例.zip

    在“参考资料-斜坡补偿到的来龙去脉与实例.pdf”中,很可能会详细阐述斜坡补偿的发展历程,包括其早期的应用、理论基础,以及随着时间的推移和技术的进步,如何演变成现代控制系统中不可或缺的一部分。此外,该文档...

    Google Android开发入门实战 pdf 和 实例代码.part3

    本章介绍Android的来龙去脉,让读者以最短的时间直观地了解到Android的基础及发展的历史和趋势,并能清晰地知到自己可以在Android上做什么事情。 第2章 工欲善其事 必先利其器—搭建Android开发环境。本章介绍了在...

    Google Android开发入门实战 pdf 和 实例代码.part2

    本章介绍Android的来龙去脉,让读者以最短的时间直观地了解到Android的基础及发展的历史和趋势,并能清晰地知到自己可以在Android上做什么事情。 第2章 工欲善其事 必先利其器—搭建Android开发环境。本章介绍了在...

    Google Android开发入门实战 pdf 和 实例代码.part1

    本章介绍Android的来龙去脉,让读者以最短的时间直观地了解到Android的基础及发展的历史和趋势,并能清晰地知到自己可以在Android上做什么事情。 第2章 工欲善其事 必先利其器—搭建Android开发环境。本章介绍了在...

    MFC源码剖析之--MFC来龙去脉

    ### MFC源码剖析之——MFC来龙去脉 #### MFC的起源与核心概念 MFC(Microsoft Foundation Classes)是由微软开发的一套用于Windows平台的C++类库,它封装了Windows API,简化了GUI应用程序的开发过程。通过使用MFC...

    android-使用环信SDK开发即时通信功能(附源码下载)

    我在集成环信SDK到我们自己开发的app之前,研究了一下环信demo的代码,看了两三天的样子,基本搞清楚来龙去脉,但是只是清楚来龙去脉,要说到里面的细节可能得深一步研究,但是这就够了,已经可以把demo里面的功能...

    MFC来龙去脉

    ### MFC来龙去脉:深入理解MFC框架与Windows编程 #### MFC的历史与背景 MFC(Microsoft Foundation Classes)是微软为简化Windows应用程序开发而设计的一套类库,它基于C++语言,提供了对Windows API的封装,使得...

    X86指令编码的来龙去脉

    ### X86指令编码的来龙去脉:深入解析与理解 #### 1、序言:初探X86指令编码 X86指令集架构是个人计算机领域中最广泛使用的指令集之一,其指令编码机制复杂而精细,是实现硬件与软件间桥梁的关键。在学习X86指令...

    构件化编程的来龙去脉.ppt

    构件化编程的来龙去脉.ppt,构件编程详细解释

    Android例子源码多种图片渲染处理源码.rar

    Android例子源码多种图片渲染处理源码是一款图片处理器的项目源码,类似美颜相机的各种处理照片一样,本项目也有多种处理照片的形式,比美颜相机有过之而无不及,而且它更增加了变形,扭曲等等让照片更加丰富多彩。...

    中华文明外来论的来龙去脉.docx

    中华文明外来论的来龙去脉.docx

    聊聊企业容灾的来龙去脉.docx

    聊聊企业容灾的来龙去脉.docx

    产品经理深入浅出第1课-产品经理的诞生的来龙去脉

    产品经理作为互联网行业中一个重要的角色,其诞生与发展历程紧密地与互联网技术的进步和市场的需求变化相结合。接下来,我们将从多个维度深入解析产品经理的历史背景、职责、与项目经理的区别以及个人职业发展等方面...

    初中语文文摘历史“投机倒把”的来龙去脉

    初中语文文摘历史“投机倒把”的来龙去脉

Global site tag (gtag.js) - Google Analytics