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

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_EVENTSLOGD("Input event: device=0x%x type=0x%x scancode=%d keycode=%d value=%d",rawEvent.deviceId, rawEvent.type, rawEvent.scanCode, rawEvent.keyCode,rawEvent.value);
#endifprocess(& 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_SWITCHLOGD("App switch is pending!");
#endifmAppSwitchDueTime = 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 lockAutoMutex _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_ACTIONSLOGD("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_REGISTRATIONLOGD("channel '%s' - Registered", inputChannel->getName().string());
#endifsp<Looper> looper = android_os_MessageQueue_getLooper(env, messageQueueObj);{ // acquire lockAutoMutex _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 lockandroid_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中

Android输入输出机制之来龙去脉之前生后世相关推荐

  1. [置顶] Android输入输出机制之来龙去脉之前生后世

    记不住密码怎么办? http://a.app.qq.com/o/simple.jsp?pkgname=com.wa505.kf.epassword 先讲一下基本一般的输入处理方式的知识.一般的输入输出 ...

  2. Android输入输出机制之来龙去脉

    openInputChannelPair( 阅读本文的前提条件是知道匿名管道和匿名共享内存是怎么一回事,否则阅读相应的文章. Anonymous pipes 和Anonymous Shared Mem ...

  3. aidl使用_借助 AIDL 理解 Android Binder 机制——Binder 来龙去脉

    AIDL 是 Android Interface Definition Language(Android 接口定义语言)的缩写,它是 Android 进程间通信的接口语言.由于 Android 系统的 ...

  4. SOA BPEL ESB的前生后世:----作者:吕建伟

    SOA BPEL ESB的前生后世 我不是卖中间件的,所以我也不必鼓吹SOA概念和大道理. 我也不是准备写一本SOA书的,所以我也不必写博客心得分享时咬文嚼字. 这篇文章涉及到SOA.SCA.SDO. ...

  5. android系统核心机制 基础(01)智能指针wp sp

    该系列文章总纲链接:android 系统核心机制基础 系列文章目录 本章关键点总结 & 说明: 以上是本模块的导图,整体概括了智能指针的几个要点,引用计数,弱转强,flag标志意义以及Ligh ...

  6. Android消息机制Handler用法

    这篇文章介绍了Android消息机制Handler用法总结,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧 1.简述 Handler消息机制主要包括: Messa ...

  7. 【腾讯Bugly干货分享】经典随机Crash之二:Android消息机制

    为什么80%的码农都做不了架构师?>>>    本文作者:鲁可--腾讯SNG专项测试组 测试工程师 背景 承上经典随机Crash之一:线程安全 问题的模型 好几次灰度top1.top ...

  8. Android刷新机制-View绘制原理

    Android刷新机制-View绘制原理 Android刷新机制-SurfaceFlinger原理 Android刷新机制-Choreographer原理 一.概述 本文将从startActivity ...

  9. android消息池,回转寿司你一定吃过!——Android消息机制(构造)

    消息机制的故事寿司陈放在寿司碟上,寿司碟按先后顺序被排成队列送上传送带.传送带被启动后,寿司挨个呈现到你面前,你有三种享用寿司的方法. 将Android概念带入后,就变成了Android消息机制的故事 ...

最新文章

  1. 微软宣布将于08年发布Centro服务器软件
  2. 科学记数法数字转换/保留数值小数点位数(数字格式化)
  3. 数人云CEO王璞:容器正成为软件交付的标准
  4. java例程练习(批量修改文件后缀名)
  5. 在贪心学院学习,是一种怎样的体验?
  6. mysql stragg_如何在MySQL中將子查詢行的結果顯示為一列?
  7. Spring @Lazy批注用例
  8. java 常用流_Java流类图结构: 流的概念和作用流是一组有顺序的,有起点和终点的字节集合,是对数据传输的总称或抽象。即数据在两设备间的传输称为流,流的本质是数据传输,根据数...
  9. cocoa pods的安装与我遇到的问题
  10. 大容量存储方案_基因行业容器存储解决方案
  11. 什么是Spring EL表达式
  12. python format()函数的用法
  13. 按键精灵定位坐标循环_按键精灵的控制命令居然恐怖到了这种程度
  14. tensorflow实现手写数字识别
  15. 修改密码 -测试用例设计
  16. 蒋志平从零到亿的创业路
  17. Eclipse---Refreshing /.org.eclipse.jdt.core.external.folders/.link0
  18. 平面坐标系旋转和平移
  19. 日语基础复习 Day 4
  20. 2018年全国多校算法寒假训练营练习比赛(第五场)解题报告

热门文章

  1. mysql自增长2个增加_mysql – 添加第二个自动增量字段并允许重复
  2. C++知识点39——运算符的重载概念与分数类实现(上)
  3. web接口响应时间标准_从零搭建Web应用(二)
  4. ov5640帧率配置_一文看懂赛博朋克2077中低配置的设置方法和能畅玩的电脑有哪些?...
  5. 弹性碰撞后速度方向_MEMS加速度计辐射效应
  6. C语言编程中的“堆”和“栈”七大不同之处
  7. Django搭建个人博客:渲染Markdown文章目录
  8. unity3D 4.6及以上版本. UI穿透问题,以及模拟器不穿透真机穿透问题解决方案
  9. Linux下,如何给PHP安装pdo_mysql扩展
  10. 使用jsonpath解析json内容