Android InputFlinger简单分析(主要分析Touch)

本文主要分析Android系统中InputFlinger服务在处理Touch事件的过程。从InputManagerService的启动,到InputDispatcher和InputReader的协同工作,特别是对MotionEvent的处理。通过开机加载Touch设备的日志和单点touch事件的日志,揭示了InputReader如何读取并通知InputDispatcher,以及事件在不同Mapper之间的传递,最后到达Java层的View系统。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Android InputFlinger简单分析(主要分析Touch)

首先,它有个服务,InputManagerService.

InputManagerService启动

startOtherServices@SystemServer

...
inputManager = new InputManagerService(context);
wm = WindowManagerService.main(context, inputManager,
     mFactoryTestMode != FactoryTest.FACTORY_TEST_LOW_LEVEL,
                    !mFirstBoot, mOnlyCore);
...
ServiceManager.addService(Context.INPUT_SERVICE, inputManager);
...
inputManager.setWindowManagerCallbacks(wm.getInputMonitor());
            inputManager.start();
...

看看构造函数:

public InputManagerService(Context context) {
    ...
    mPtr = nativeInit(this, mContext,mHandler.getLooper().getQueue());
    ...
}

这个mPrt之后作为了其他native函数的第一个参数。先看看nativeInit

(位置base/services/core/jni/com_android_server_input_InputManagerService.cpp)

static jlong nativeInit(JNIEnv* env, jclass clazz,
        jobject serviceObj, jobject contextObj, jobject messageQueueObj) {
    sp<MessageQueue> messageQueue = android_os_MessageQueue_getMessageQueue(env, messageQueueObj);
...
    NativeInputManager* im = new NativeInputManager(contextObj,         serviceObj,messageQueue->getLooper());
    im->incStrong(0);
    return reinterpret_cast<jlong>(im);
}

简单来理解就是获取java层的消息队列,然后构造了一个NativeInputManager对象,然后返回java层,赋值给mPtr。

前文,startOtherServices@SystemServer中还调用了

inputManager.start();

start()@InputManagerService

...
nativeStart(mPtr);
...

nativeStart:

static void nativeStart(JNIEnv* env, jclass clazz, jlong ptr) {
    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
status_t result = im->getInputManager()->start();
...
}

即用nativeInit中new的NativeInputManager对象调用start函数。

那NativeInputManager是什么呢?

inline sp<InputManager> getInputManager() const { return mInputManager; }

直接去看InputManager调用start函数就好了。

接下来,我们的工作目录就集中到

frameworks/native/services/inputflinger

从InputManager::start开始

...
  status_t result = mDispatcherThread->run("InputDispatcher", PRIORITY_URGENT_DISPLAY);
...
  result = mReaderThread->run("InputReader", PRIORITY_URGENT_DISPLAY);
...

mDispatcherThread和mReaderThread的run函数,都会去调用threadLoop函数(就当是常识了)。

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

从名字上来看,一个专门去读取input,一个专门去分发input.可是为什么只运行一次(loopOnce)呢?

我们这次主要关注的是touch事件,即MotionEvent.

InputDispatcher.h

struct MotionEntry : EventEntry {

Motion是Event的一种!

virtual void notifyConfigurationChanged(const NotifyConfigurationChangedArgs* args);
virtual void notifyKey(const NotifyKeyArgs* args);
virtual void notifyMotion(const NotifyMotionArgs* args);
virtual void notifySwitch(const NotifySwitchArgs* args);
virtual void notifyDeviceReset(const NotifyDeviceResetArgs* args);

这几个函数功能应该类似,只不过Event种类不同。

在InputDispatcher.cpp中竟然找不到调用。嗯!yes!搜索结果如下:

InputReader.cpp:2551:        getListener()->notifyMotion(&args);
...

getListener@InputReader:

class InputReader : public InputReaderInterface {
...
virtual InputListenerInterface* getListener();

InputDispatcher.h:

class InputDispatcherInterface : public virtual RefBase, public InputListenerInterface
...
class InputDispatcher : public InputDispatcherInterface

回头过去看一下InputManager的构造函数,就都明白了

InputManager::InputManager

InputManager::InputManager(
        const sp<EventHubInterface>& eventHub,
        const sp<InputReaderPolicyInterface>& readerPolicy,
        const sp<InputDispatcherPolicyInterface>& dispatcherPolicy) {
    mDispatcher = new InputDispatcher(dispatcherPolicy);
    //注意第三个参数
    mReader = new InputReader(eventHub, readerPolicy, mDispatcher);
    initialize();
}

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

InputListenerInterface* InputReader::ContextImpl::getListener() {
    return mReader->mQueuedListener.get();
}

总的来说就是,notifyMotion函数的调用来自InputReader.读到input之后notify!

InputReader分析

InputReader::loopOnce

//引出一个EventHub,getEvents也非常类似平时在adb shell中使用的命令
size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);
...
processEventsLocked(mEventBuffer, count);
...

InputReader::processEventsLocked

void InputReader::processEventsLocked(const RawEvent* rawEvents, size_t count) {
    for (const RawEvent* rawEvent = rawEvents; count;) {
      ...
        //type不是
        //DEVICE_ADDED,DEVICE_REMOVED,FINISHED_DEVICE_SCAN这三个
        if(type < EventHubInterface::FIRST_SYNTHETIC_EVENT) {
      ...
        processEventsForDeviceLocked(deviceId, rawEvent, batchSize);
        }
    }
}

我们来看看一个开机加载T

<think>嗯,用户提到Android手写板区域比例不匹配的问题。我需要先理解这个问题的具体表现。用户可能在连接数位板或手写设备时,发现手写笔的位置和屏幕上的光标位置不一致,比如在数位板的某个角落书写,但光标出现在屏幕中间,或者移动范围不匹配。这通常是因为数位板的输入区域映射不正确导致的。 首先,我应该考虑Android系统如何处理输入设备,特别是像数位板这样的外部设备。Android的Input子系统会根据设备的报告来设定输入区域,可能涉及到坐标映射和比例调整。如果驱动或固件有问题,可能导致系统无法正确识别数位板的实际工作区域,从而出现比例不匹配。 接下来,用户可能遇到的常见原因包括:驱动兼容性问题、设备配置错误、系统设置问题或者应用程序本身的限制。例如,数位板制造商可能没有为Android提供正确的驱动程序,导致系统无法正确解析输入数据。或者,用户可能在系统中没有正确配置手写板的区域设置,比如屏幕映射范围设置不当。 然后,我需要思考解决方法。可能的解决方案包括检查数位板制造商的Android应用是否有区域校准功能,更新设备驱动或固件,检查系统级别的输入配置,或者使用第三方应用进行校准。此外,开发者选项中的指针位置显示可能帮助诊断问题,查看原始输入数据是否正常。 还需要考虑用户的具体情况,比如是否使用了特定的应用程序,或者是否在多个设备上出现同样的问题。如果是特定应用的问题,可能需要调整应用的输入处理逻辑。而如果是系统级的问题,可能需要更深入的配置或root权限来调整输入映射。 另外,可能涉及到的技术点包括Android的Input子系统如何处理绝对坐标事件,输入设备的报告描述符是否正确,以及应用程序如何处理这些输入事件。例如,数位板通常使用绝对坐标(ABS_X, ABS_Y),而触屏设备也是如此,但两者的分辨率和比例可能不同,导致映射错误。 用户可能没有说明他们是否尝试过校准设备,或者是否在多个应用中都有这个问题。这时候需要建议用户进行分步排查,比如先检查系统设置,再测试不同应用,最后考虑硬件或驱动问题。同时,可能需要查看日志信息,类似之前提供的InputReader日志,确认设备是否正确识别,以及输入事件的数据是否合理。 总结下来,回答需要涵盖问题原因、排查步骤、解决方案,以及可能的底层原理,帮助用户全面理解并解决问题。同时,使用结构化的方式,如分点说明,会让信息更清晰易懂。此外,提醒用户注意驱动更新和应用兼容性也是重要的。</think>当Android设备连接数位板出现区域比例不匹配时,通常表现为手写笔的物理移动范围与屏幕光标移动范围不对应。以下是问题解析和解决方案: --- ### **问题原理** 数位板通过`ABS_X`/`ABS_Y`轴上报绝对坐标: $$x_{screen} = \frac{x_{input} \times screenWidth}{inputMaxX}$$ $$y_{screen} = \frac{y_{input} \times screenHeight}{inputMaxY}$$ 当`inputMaxX/Y`与屏幕宽高比例不一致时,会产生形变。 --- ### **常见原因** 1. **驱动配置错误** - 数位板未正确上报物理尺寸(通过`ABS_MT_POSITION_X/Y`的`info.resolution`) 2. **系统映射策略** - Android默认采用线性缩放(可能忽略设备物理尺寸) 3. **多显示器适配** - 外接显示器与主屏分辨率比例差异导致的拉伸 --- ### **排查步骤** 1. **获取输入设备参数** ```bash adb shell getevent -p /dev/input/eventX ``` 检查`ABS_MT_POSITION_X`的`max`值和`resolution`: ``` ... ABS_MT_POSITION_X : value 0, min 0, max 30000, fuzz 0, flat 0, resolution 400 ABS_MT_POSITION_Y : value 0, min 0, max 20000, fuzz 0, flat 0, resolution 400 ``` `resolution=400`表示每毫米40个坐标点 2. **查看实际坐标流** ```bash adb shell getevent -l /dev/input/eventX ``` 观察`ABS_MT_POSITION_X/Y`数值是否在`max`范围内波动 3. **验证屏幕映射** 启用开发者选项中的**指针位置**,观察`X/Y`值是否均匀覆盖屏幕 --- ### **解决方案** 1. **系统级修正** - 修改`frameworks/native/services/inputflinger/InputMapper.cpp`中的坐标转换逻辑: ```cpp float xScale = displayWidth / (inputMaxX - inputMinX); float yScale = displayHeight / (inputMaxY - inputMinY); // 改为按物理比例缩放 float scale = min(xScale, yScale); ``` 2. **应用层适配** ```java // 在onTouchEvent中手动校准 float calibratedX = event.getX() * (screenWidth / tabletMaxX); float calibratedY = event.getY() * (screenHeight / tabletMaxY); ``` 3. **厂商工具** - 使用Wacom/Huion等厂商的Android版驱动应用(如`Wacom Feel Multi-Touch`) - 通过**四点校准**功能重建坐标映射 --- ### **典型故障案例** **现象**:数位板16:9区域映射到4:3屏幕导致图像压缩 **修复**: ```xml <!-- 在设备配置XML中强制宽高比 --> <input> <device product="HUION_H641P"> <display-width>16</display-width> <display-height>9</display-height> </device> </input> ``` 建议优先通过厂商应用校准,若为自定义ROM可修改`/system/usr/idc/`目录下的配置文件。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值