记不住密码怎么办?

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

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

这种输入模型在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
  • 查看图片附件

[置顶] 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. [置顶] Android输入输出系统之TouchEvent流程

    记不住密码怎么办? http://a.app.qq.com/o/simple.jsp?pkgname=com.wa505.kf.epassword 一个是InputReader,一个是InputDis ...

  4. android listview标题置顶,Android仿QQ左滑删除置顶ListView操作

    最近闲来无事,于是研究了一下qq的左滑删除效果,尝试着实现了一下,先上效果图: 大致思路原理: - 通过设置margin实现菜单的显示与隐藏 - 监听onTouchEvent,处理滑动事件 上代码 i ...

  5. [置顶] Android自定义控件 芝麻信用分雷达图

    [置顶] Android自定义控件 芝麻信用分雷达图 标签: android自定义雷达芝麻信用 2016-10-23 20:11  3548人阅读  评论(24)  收藏  举报   分类: 自定义控 ...

  6. android列表实现置顶,Android利用RecyclerView实现全选、置顶和拖拽功能示例

    Android利用RecyclerView实现全选.置顶和拖拽功能示例 发布时间:2020-08-23 16:26:42 来源:脚本之家 阅读:159 作者:爱开发 前言 今天给大家分享是如何在Rec ...

  7. android imageview 锯齿,[置顶] android 自定义圆角ImageView以及锯齿的处理

    看到很多人开发过程中要使用圆角图片时,解决方法有: 1.重新绘制一张图片 2.通过布局来配置 3.通过重写View来实现 其中1,2在这里就不讲了,重点讲讲方法三的实现. 实现一:通过截取画布一个圆形 ...

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

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

  9. [置顶]Android 面试题汇总

    MicrosoftInternetExplorer402DocumentNotSpecified7.8 磅Normal0 面试题基础储备 1.Activity相关 a.Activity的特点 1.可见 ...

最新文章

  1. pstree进程管理
  2. spring14:注解@Autowired,实现引用类型的赋值
  3. 河南省第十届大学生程序设计竞赛 A,B,C,D,F,G,H 题解
  4. js map 只输出key_什么时候适合使用Map而不是Object
  5. iOS之深入解析Xcode 13正式版发布的40个新特性
  6. 核弹级漏洞 Apache Log4j2 漏洞详情和修复建议
  7. php中如何判断目录是否存在文件_PHP判断指定目录下是否存在文件
  8. L3MON-远程Android管理套件环境搭建
  9. java实训致谢_Java教学实习报告(最终版).doc
  10. 个人推荐讲的非常好的数据结构免费[速成 速成 速成]视频了
  11. 今日头条2018.8.12笔试题总结
  12. 实用的shell脚本合集
  13. CentOS7 安装 CMake 解决 cmake command not found 问题
  14. C++中的几个输入函数:cin,cin.get(),getline(),cin.getline() 的区别
  15. 择校秘籍|保研去 北大软微 还是 上交计算机?
  16. centos 网卡设置
  17. php curl访问HTTPS页面502
  18. properties中文乱码快速处理
  19. MyCCL特征码定位原理学习
  20. 清华大学计算机系录取分数线河南,【清华大学分数线2017】2015-2016清华大学各省各专业录取分数线(7)...

热门文章

  1. aws主机php环境搭建,亚马逊AWS上安装Nginx(Linux,CentOS环境)
  2. mysql慢查询日志平时开启吗_MySQL开启慢查询日志功能的方法
  3. 编写程序python、实现将矩阵顺时针旋转90°_Java实现矩阵顺时针旋转90度的示例...
  4. 记录一下python绘制地图
  5. okhttp 工具类_HR常用的人才测评工具 ~ 团测系统
  6. python爬虫 发送定时气象预报
  7. 自动化运维之CentOS7下PXE+Kickstart+DHCP+TFTP+HTTP无人值守安装系统
  8. J0ker的CISSP之路:复习Access Control(10)
  9. html5中的新标签
  10. LaTeX 简介与安装