🔥 Hi,我是小余。本文已收录到 GitHub · Androider-Planet 中。这儿有 Android 进阶成长常识体系,重视大众号 [小余的自习室] ,在成功的路上不迷路!
浅谈
曾经在开发的很长一段时间内,笔者对点击事情的认知只存在于自定义View中的onTouchEvent
等办法的处理。 后来渐渐的接触到Android的事情分发机制
,但也只是在Activity->ViewGroup->View层面的分发逻辑。
诚然在咱们开发中也仅需求搞懂这个层面就够咱们平常所用了。
但笔者脑海里一向有个声音在问我:这些事情是怎样来的,本源在哪里? 秉着追根溯源的精神,踏上了渐渐源码路。。。
特别说明:文章中部分常识或许图片摘自其他网络文章,如有侵权,能够联络博主进行删去。
Android输入体系
说到屏幕点击事情,大部分同学最早想到的便是自定义中点击事情的处理进程,屏幕滑动,Down事情,Up事情等。
是的,笔者也是如此。
自定义View的事情处理其实在整个Android输入体系中只能算是最上层的。 输入体系假如按结构分层能够分为:
-
输入体系部分
包括输入子体系以及InputManagerService,用于事情的捕获以及分发给上一级
-
WindowManagerService处理部分
输入体系部分将事情分发给对应的Window,而Window正是由WMS来管理的
-
View处理部分
这儿便是前面咱们说的Activity->ViewGroup->View层面的逻辑
这儿再供给一张愈加具体的Android输入体系模型:
这张图涵盖了Android整个输入体系事情传递的大致模型,非常具有参阅含义,本系列文章便是依据这张图打开的 图片来自
输入体系说白了便是捕获事情,并将事情分发给WMS进行处理。关键字:事情捕获,事情分发 那体系是如何进行事情捕获和分发的呢?
输入体系在结构上又能够分为:
- 输入子体系
- InputManagerService(简称IMS)
输入子体系
Android中的输入设备有很多种,如:键盘,屏幕,鼠标等,开发中最常见的便是屏幕和按键(如Home键等归于键盘)了。
这些设备关于核心处理器来说便是一个“即插即用”外设,和咱们电脑刺进鼠标或手机后,在“设备管理器”里面会新增一个输入设备节点一样(前提是现已安装好对应的驱动)
Android处理器在接入这些“外设”后,比方滑动屏幕,设备驱动层就会接受到原始事情终究将事情传递到用户空间的设备节点(dev/input/)中。
Android供给了一些api能够让开发者在设备节点(dev/input/)中读取内核写入的事情。
IMS
IMS的作用:读取设备节点(dev/input/)中的输入事情,并对数据进行二次乃至三次加工后分发给适宜的Window进行处理。
而咱们今日要介绍的便是关于底层输入体系IMS的处理部分,关于WMS处理以及View处理部分,后续会出一些文章讲解。
文章将分两个阶段来对输入体系介绍
- 1.输入事情获取
- 2.输入事情分发
事情获取流程
咱们以IMS为进口。在设备开机进程中,发动SystemServer部分初始了很多体系服务,这儿面就包括了IMS的创立和发动进程。 注:文章运用的源码版别为8.0:
1.IMS的创立与发动
在SystemServer的run办法中调用了startOtherServices。
private void startOtherServices() {
...
inputManager = new InputManagerService(context);//1
...
wm = WindowManagerService.main(context, inputManager,
mFactoryTestMode != FactoryTest.FACTORY_TEST_LOW_LEVEL,
!mFirstBoot, mOnlyCore, new PhoneWindowManager());//2
...
inputManager.start();//3
}
注释1:调用了IMS的结构办法 注释2:将IMS作为参数传递给WMS 注释3:调用了IMS的发动办法start
咱们来具体剖析下注释1和注释3: 注释1:
public InputManagerService(Context context) {
...
mPtr = nativeInit(this, mContext, mHandler.getLooper().getQueue());
...
}
重点看nativeInit办法,这是一个native办法
//frameworks\base\services\core\jni\com_android_server_input_InputManagerService.cpp
static jlong nativeInit(JNIEnv* env, jclass /* clazz */,
jobject serviceObj, jobject contextObj, jobject messageQueueObj) {
...
NativeInputManager* im = new NativeInputManager(contextObj, serviceObj,
messageQueue->getLooper());
im->incStrong(0);
return reinterpret_cast<jlong>(im);
}
NativeInputManager::NativeInputManager(jobject contextObj,
jobject serviceObj, const sp<Looper>& looper) :
mLooper(looper), mInteractive(true) {
JNIEnv* env = jniEnv();
mContextObj = env->NewGlobalRef(contextObj);//1
mServiceObj = env->NewGlobalRef(serviceObj);//2
...
sp<EventHub> eventHub = new EventHub();//3
mInputManager = new InputManager(eventHub, this, this);//4
}
nativeInit办法中创立了一个NativeInputManager目标,并将该目标指针回来给了java framework层。 这便是为了打通java和native层,下次需求运用native层的NativeInputManager目标的时分,直接传递这个指针就能够访问了。
持续看NativeInputManager结构办法:
- 注释1.将java层的传下来的Context上下文保存在mContextObj中
- 注释2:将java层传递下来的InputManagerService目标保存在mServiceObj中。
假如你对源码比较了解,能够知道大部分native层和java层的交互都是经过这个形式
调用模型图
如下:
NativeInputManager结构办法注释3处:创立一个EventHub,EventHub经过Linux内核的INotify和epoll机制监听设备节点,运用它的getEvent函数就能够读取设备节点的原始事情以及设备节点增删事情。 这儿说明下原始事情和设备增删事情:
- 原始事情:比方键盘节点的输入事情或许屏幕的触屏DOWN或许UP事情等,都归于原始事情。
- 设备增删事情:值键盘或许屏幕等节点的增删,EventHub在获取这类事情后,会在native层创立对应的节点处理Mapper目标。
NativeInputManager结构办法注释4处:创立一个InputManager目标并将注释3处的EventHub目标作为参数传入。 进入InputManager结构办法:
InputManager::InputManager(
const sp<EventHubInterface>& eventHub,
const sp<InputReaderPolicyInterface>& readerPolicy,
const sp<InputDispatcherPolicyInterface>& dispatcherPolicy) {
mDispatcher = new InputDispatcher(dispatcherPolicy);//1
mReader = new InputReader(eventHub, readerPolicy, mDispatcher);//2
initialize();//3
}
void InputManager::initialize() {
mReaderThread = new InputReaderThread(mReader);
mDispatcherThread = new InputDispatcherThread(mDispatcher);
}
InputManager结构办法中:
- 1.创立InputDispatcher类目标mDispatcher,InputDispatcher类首要用来对原始事情进行分发,传递给WMS
- 2.创立InputReader类目标mReader,并传入1中mDispatcher目标以及eventHub目标, 为什么要传入这2个目标呢?因为InputReader机制便是:eventHub目标用来读取事情数据,mDispatcher目标用来将读取到事情数据分发。
- 3.initialize办法中创立了InputReaderThread目标和InputDispatcherThread目标 因为事情读取机制是一个耗时进程,不能在主线程中进行,所以运用InputReaderThread线程来读取事情,用InputDispatcherThread线程来分发事情
关于IMS的结构办法就讲了这么多,先来小结下:
- 1.IMS结构办法中:创立了一个NativeInputManager的native目标,并将java层的Context上下文保存在native层的mContextObj,将java层的IMS目标保存在native层的mServiceObj中 创立InputManager目标并传入一个新建的EventHub目标,用于读取设备节点事情。
- 2.InputManager结构办法中:创立了一个InputDispatcher和InputReader目标,以及用于读取事情的InputReaderThread线程和分发事情的InputDispatcherThread线程
下面咱们持续看IMS的发动办法,在startOtherServices办法的调用inputManager.start
private void startOtherServices() {
...
inputManager = new InputManagerService(context);//1
...
wm = WindowManagerService.main(context, inputManager,
mFactoryTestMode != FactoryTest.FACTORY_TEST_LOW_LEVEL,
!mFirstBoot, mOnlyCore, new PhoneWindowManager());//2
...
inputManager.start();//3
}
start办法中首要调用了nativeStart办法,参数为初始化时,native回来的NativeInputManager目标地址
static void nativeStart(JNIEnv* env, jclass /* clazz */, jlong ptr) {
NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
status_t result = im->getInputManager()->start();
if (result) {
jniThrowRuntimeException(env, "Input manager could not be started.");
}
}
nativeStart办法调用了NativeInputManager的InputManager的start办法
status_t InputManager::start() {
status_t result = mDispatcherThread->run("InputDispatcher", PRIORITY_URGENT_DISPLAY);
...
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;
}
start办法首要作用便是发动初始化时创立的两个线程:mDispatcherThread和mReaderThread 这儿留意先后顺序先发动事情分发线程,再发动事情读取线程。这是为了在事情读取后能够当即对事情进行分发。
IMS发动时序图如下:
2.线程的创立与发动
在剖析两个线程发动进程之前,咱们先来讲解下Thread的run办法
system\core\libutils\Threads.h
status_t Thread::run(const char* name, int32_t priority, size_t stack)
{
...
res = createThreadEtc(_threadLoop,
this, name, priority, stack, &mThread);
...
}
Thread的run办法中调用了createThreadEtc,这个办法第一个参数_
threadLoop是一个办法指针,第二个参数是自己,终究会调用到_threadLoop办法并传入this指针、
int Thread::_threadLoop(void* user)
Thread* const self = static_cast<Thread*>(user);
...
do {
bool result;
result = self->threadLoop();
...
if (result == false || self->mExitPending) {
...
break;
}
...
} while(strong != 0);
}
_threadLoop办法内部会调用self->threadLoop(),这个self便是当前Thread的this指针,除了result回来false或许调用了requestExit才会退出。 不然会一向循环调用self->threadLoop()函数,threadLoop在Thread中是一个纯虚函数,在其子类中实现。
所以后边只要剖析子线程的threadLoop办法即可。
好,下面咱们先来剖析InputDispatcherThread:
bool InputDispatcherThread::threadLoop() {
mDispatcher->dispatchOnce();
return true;
}
这儿的mDispatcher是InputDispatcher类目标。
void InputDispatcher::dispatchOnce() {
nsecs_t nextWakeupTime = LONG_LONG_MAX;
{ // acquire lock
..
if (!haveCommandsLocked()) {//1
dispatchOnceInnerLocked(&nextWakeupTime);//2
}
if (runCommandsLockedInterruptible()) {//3
nextWakeupTime = LONG_LONG_MIN;//4
}
} // release lock
...
mLooper->pollOnce(timeoutMillis);//5
}
注释1:判别有没有指令需求履行,假如没有指令,则调用注释2的dispatchOnceInnerLocked 假如有指令,则在注释3处履行完一切指令后,将nextWakeupTime置为LONG_LONG_MIN,这样就能够让下一次线程能够被当即唤醒。 注释5处调用mLooper->pollOnce,让线程进入休眠。第一次发动的时分是直接进入休眠,等待事情的到来。
InputReader/InputReaderThread
下面再来剖析事情读取线程InputReaderThread:
bool InputReaderThread::threadLoop() {
mReader->loopOnce();
return true;
}
这儿mReader是InputReader类目标:
void InputReader::loopOnce() {
...
size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);//1
{ // acquire lock
...
if (count) {
processEventsLocked(mEventBuffer, count);//2
}
...
} // release lock
...
mQueuedListener->flush();//3
}
3.事情的获取与赋值
注释1处调用EventHub的getEvents办法读取设备节点中的输入事情,
留意这个办法内部在没有输入事情的时分也是一个休眠的进程,并不是死循环耗时操作。
在注释2处假如count不为0,说明有事情,调用processEventsLocked办法。
4.分件的处理与加工
进入processEventsLocked看看:
void InputReader::processEventsLocked(const RawEvent* rawEvents, size_t count) {
for (const RawEvent* rawEvent = rawEvents; count;) {
int32_t type = rawEvent->type;
//1 原始事情类型
if (type < EventHubInterface::FIRST_SYNTHETIC_EVENT) {
...
processEventsForDeviceLocked(deviceId, rawEvent, batchSize);
} else {//2.设备节点事情
switch (rawEvent->type) {
case EventHubInterface::DEVICE_ADDED:
addDeviceLocked(rawEvent->when, rawEvent->deviceId);
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;
}
}
count -= batchSize;
}
}
processEventsLocked办法首要实现了:依据事情的type类型进行不同处理。
- 1.原始事情:调用processEventsForDeviceLocked处理
- 2.设备节点事情:调用节点的增加和删去等操作。
咱们先来看原始事情:processEventsForDeviceLocked
void InputReader::processEventsForDeviceLocked(int32_t deviceId,
const RawEvent* rawEvents, size_t count) {
ssize_t deviceIndex = mDevices.indexOfKey(deviceId);
...
InputDevice* device = mDevices.valueAt(deviceIndex);
...
device->process(rawEvents, count);
}
依据deviceId去获取设备在mDevices中的索引,依据索引获取InputDevice目标 调用InputDevice的process办法持续处理。
void InputDevice::process(const RawEvent* rawEvents, size_t count) {
...
size_t numMappers = mMappers.size();
for (const RawEvent* rawEvent = rawEvents; count--; rawEvent++) {
...
...
for (size_t i = 0; i < numMappers; i++) {
InputMapper* mapper = mMappers[i];
mapper->process(rawEvent);
}
...
...
}
}
process办法终究是运用不同的InputMapper进行处理, 那这个InputMapper在哪里设置成员呢。
咱们回到前面processEventsLocked办法:假如是节点处理事情,如增加则调用addDeviceLocked办法, addDeviceLocked办法中又调用了createDeviceLocked办法,并将回来的InputDevice放入到mDevices列表中。
void InputReader::addDeviceLocked(nsecs_t when, int32_t deviceId) {
...
InputDevice* device = createDeviceLocked(deviceId, controllerNumber, identifier, classes);
...
mDevices.add(deviceId, device);
...
}
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);
...
// Switch-like devices.
if (classes & INPUT_DEVICE_CLASS_SWITCH) {
device->addMapper(new SwitchInputMapper(device));
}
// Scroll wheel-like devices.
if (classes & INPUT_DEVICE_CLASS_ROTARY_ENCODER) {
device->addMapper(new RotaryEncoderInputMapper(device));
}
...
// Keyboard-like devices.
...
if (keyboardSource != 0) {
device->addMapper(new KeyboardInputMapper(device, keyboardSource, keyboardType));
}
...
// 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;
}
能够看到createDeviceLocked依据不同输入类型给Device设备增加了不同的InputMapper。
回到前面InputDevice::process办法中: 这儿用KeyboardInputMapper来做比如。 这个办法调用了KeyboardInputMapper的process办法
void KeyboardInputMapper::process(const RawEvent* rawEvent) {
switch (rawEvent->type) {
case EV_KEY: {
..
if (isKeyboardOrGamepadKey(scanCode)) {
processKey(rawEvent->when, rawEvent->value != 0, scanCode, usageCode);
}
break;
}
}
}
void KeyboardInputMapper::processKey(nsecs_t when, bool down, int32_t scanCode,
int32_t usageCode) {
...
NotifyKeyArgs args(when, getDeviceId(), mSource, policyFlags,
down ? AKEY_EVENT_ACTION_DOWN : AKEY_EVENT_ACTION_UP,
AKEY_EVENT_FLAG_FROM_SYSTEM, keyCode, scanCode, keyMetaState, downTime);
getListener()->notifyKey(&args);
}
终究在processKey办法中将原始事情封装为一个新的NotifyKeyArgs目标,并调用 getListener()->notifyKey办法作为参数传入,这儿getListener是在InputReader结构办法中初始化
InputReader::InputReader(const sp<EventHubInterface>& eventHub,
const sp<InputReaderPolicyInterface>& policy,
const sp<InputListenerInterface>& listener) :
mContext(this), mEventHub(eventHub), mPolicy(policy),
mGlobalMetaState(0), mGeneration(1),
mDisableVirtualKeysTimeout(LLONG_MIN), mNextTimeout(LLONG_MAX),
mConfigurationChangesToRefresh(0) {
mQueuedListener = new QueuedInputListener(listener);
...
}
便是这个mQueuedListener,而这个listener是外部传入的InputDispatcher目标。
那进入QueuedInputListener的notifyKey看看:
void QueuedInputListener::notifyKey(const NotifyKeyArgs* args) {
mArgsQueue.push(new NotifyKeyArgs(*args));
}
这儿只是将NotifyKeyArgs事情目标存储到mArgsQueue行列中,并没有真实对事情进行处理,那什么时分处理呢? 调查细心的同学应该看到在InputReader的loopOnce中调用了mQueuedListener->flush()
void InputReader::loopOnce() {
...
mQueuedListener->flush();//3
}
void QueuedInputListener::flush() {
size_t count = mArgsQueue.size();
for (size_t i = 0; i < count; i++) {
NotifyArgs* args = mArgsQueue[i];
args->notify(mInnerListener);
delete args;
}
mArgsQueue.clear();
}
这个flush办法便是循环调用列表中的事情,并顺次履行NotifyArgs的notify办法传入的参数mInnerListener为初始化时的InputDispatcher目标
5.事情的分发与告诉
持续进入NotifyArgs的notify办法:
struct NotifyArgs {
virtual void notify(const sp<InputListenerInterface>& listener) const = 0;
}
notify办法是一个纯虚函数,由其子类实现。这儿子类便是NotifyKeyArgs目标。
void NotifyKeyArgs::notify(const sp<InputListenerInterface>& listener) const {
listener->notifyKey(this);
}
这儿listener是InputDispatcher的目标。
InputDispatcher/InputDispatcherThread
void InputDispatcher::notifyKey(const NotifyKeyArgs* args) {
...
bool needWake;
{ // acquire lock
...
KeyEntry* newEntry = new KeyEntry(args->eventTime,
args->deviceId, args->source, policyFlags,
args->action, flags, keyCode, args->scanCode,
metaState, repeatCount, args->downTime);//1
needWake = enqueueInboundEventLocked(newEntry); //2
} // release lock
if (needWake) {
mLooper->wake();//3
}
}
- 注释1处:将NotifyKeyArgs事情从头封装为一个KeyEntry目标。
- 注释2处:enqueueInboundEventLocked将KeyEntry目标压入mInboundQueue中。
- 注释3处:唤醒InputDispatcherThread线程进行处理。
到这儿InputReader的输入事情读取流程现已悉数走完。
事情读取时序图如下:
6.事情获取流程小结
这儿对输入事情的获取流程做个小结:
- 1.SystemServer创立并发动InputManagerService
- 2.InputManagerService在native层创立一个NativeInputManager目标
- 3.NativeInputManager内部创立一个InputManager目标
- 4.InputManager发动InputReaderThread和InputDispatcherThread
- 5.在InputReaderThread线程中调用EventHub的getEvents获取设备节点中的输入事情。
- 6.并将输入事情封装为NotifyKeyArgs目标放入行列中。
- 7.之后再调用flush,顺次将事情传递给InputDispatcher。
- 8.InputDispatcher在收到事情后,会从头封装为一个KeyEntry目标,并压力压入mInboundQueue列表中。
- 9.最后唤醒InputDispatcherThread线程。
总结
篇幅问题,为了防止大家迷失在源码中,此篇就讲到这儿,关于事情的分发进程将在下一篇中进行讲解。
假如笔者文章对你有协助,希望您能够帮助点个赞,重视下,这是对我最大的鼓舞。
笔者大众号:小余的自习室
参阅
Android输入体系(一)输入事情传递流程和InputManagerService的诞生
Android Input体系(一) 事情的获取流程
本文正在参加「金石方案 . 分割6万现金大奖」