Android 14输入系统架构分析:图解源码从驱动层到应用层的完整传递链路

一、资料快车

1、深入了解Android输入系统:https://blog.csdn.net/innost/article/details/47660387

2、书籍 - Android系统源代码情景分析

二、Perface

1、参考:

2、系统程序分析方法

1)加入log,并跟着log一步步分析 -logcat;

2)利用ChatGPT提供基础概念解析 & 代码解析 & 设计原理;

3、目标

1)提供查阅代码的线索、思路;

2)能够根据日志进行快读的代码分析;

3)区分代码层次,为定制系统提供思路;

4)站在前人的肩膀上进一步探究;

5)熟悉架构后,我们应能 如何添加自定义按键、拦截keyevent并添加自己的处理逻辑;

6)Android源代码兼容多种设备,比如一开始Android针对手机设计(因此很多代码都有手机的身影),后面适应不同的设备,不同设备的处理流程不同,注意分辨;

4、action

务必根据本文提供的线索 去看源代码。

带着疑问去了解

1、kl/kcm作用是什么?在哪里会被读取?

2、定义CVTE快捷键,应该怎么做?

3、如何跟中控和TVAPI联合起来?

三、代码目录

所在层次名称代码路径
Application应用(调用系统库,实现用户逻辑)实现android中对应的类及方法,进行inputevent处理
frameworkandroid (此部分链接到应用)/android/frameworks/base/services/java/com/android/server/SystemServer.java
/android/frameworks/base/services/core/java/com/android/server/policy/PhoneWindowManager.java
/android/frameworks/base/core/java/*
/android/frameworks/base/core/java/com/android/internal/policy/DecorView.java
/android/frameworks/base/core/java/android/app/Activity.java
/android/frameworks/base/core/java/android/view/ViewRootImpl.java
/android/frameworks/base/core/java/android/view/View.java
/android/frameworks/base/core/java/android/view/KeyEvent.java
/android/frameworks/base/core/java/android/view/InputChannel.java
service (系统服务,服务端集中管理)Framework java基础服务
/android/frameworks/base/services/java/com/android/server/SystemServer.java
Framework android核心服务
/android/frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
JNI(过渡到native)一、libservices.core - 静态库模块
/android/frameworks/base/services/core/jni/
/android/frameworks/base/services/core/jni/com_android_server_input_InputManagerService.cpp
二、libandroid_runtime - 静态库模块
/android/frameworks/base/core/jni/

/android/frameworks/base/core/jni/android_view_InputEventReceiver.cpp
NDK一、libinput - 静态库模块
/android/frameworks/native/libs/input/
/android/frameworks/native/libs/input/InputTransport.cpp
二、libinputflinger/libinputflinger_base - 动态库模块
/android/frameworks/native/services/inputflinger/

/android/frameworks/native/services/inputflinger/InputManager.cpp
三、lib
/android/system/core/libutils/*
/android/system/core/libutils/Looper.cpp
cvte/android/frameworks/base/core/java/com/cvte/key/*
kernelinput子系统

四、系统框架流程

1、粗略框图 - 来自深入了解Android

在这里插入图片描述

2、代码层级
在这里插入图片描述

3、framework部分
在这里插入图片描述

4、下面针对各个部分进行拆解分析

五、基础概念

1、APP界面构成关系

1)Application 、Activity、Windows 、Decro(ration) 、View

在这里插入图片描述

一个Application有多个Activity,Activity中的界面部分为Windows(PhoneWindow),是我们所看到的当前整个界面,windows界面由Decro(样式) 来描述,Decro由各种view组件(TextView、button)组成。

2)从代码角度看Decro与View的树状关系
在这里插入图片描述

3)小结:

android里:
1个application, 有1个或多个activity
1个activity, 有1个PhoneWindow
1个window, 有1个decor
1个decor, 有多个viewgroup/layout
viewgroup/layout中, 有多个view

2、Activity组件与ViewRoot对象的关系

在这里插入图片描述

Android设计思想 - 抽象分工

一个应用程序窗口是由一个Activity来描述(描述控件组成、布局等等)- 策略者;

每一个Activity组件都有一个关联的ViewRoot对象(除了绘制具体的窗口,还负责分发键盘事件) - 具体实施者;

Activity和ViewRoot通过DecorView和PhoneWindow对象来关联起来;

需要跟踪代码才能掌握理解以上关系

Android-Activity与Window与View之间的关系:https://github.com/jeanboydev/Android-ReadTheFuckingSourceCode/blob/master/article/android/framework/Android-Activity%E4%B8%8EWindow%E4%B8%8EView%E4%B9%8B%E9%97%B4%E7%9A%84%E5%85%B3%E7%B3%BB.md

2、输入事件

输入事件有多种类型(触摸、拖动、鼠标、按键等等)

3、key相关概念

1)*.kl : keylayout,linux的scancode对应的Android按键映射;

2)*.kcm:key code map,Android按键对应的字符映射;

3)*.idc : input device configutation 输入设备配置文件;

以上文件在板卡都可以找到

4、socketpair 与 binder

socketpair 与 binder 为Android的跨进程通信技术模块,两者结合可以实现双向的跨进程通信。

5、inotify和epoll

inotify模块:用于监测某目录下的文件变化

epoll模块:用于监测文件内容变化

六、Linux kernel

1、input输入子系统

2、IR驱动

https://blog.csdn.net/STCNXPARM/article/details/134235394

七、Andorid系统部分

1、WMS

2、InputManagerService(java)

java层的InputManagerService对native层的InputManager进一步封装,很多实现都在native层。

1)Reader(CPP)

1、Reader角色
Reader主要工作
1)从设备里读取输入事件;
2)根据kcm/kl文件,进行按键映射处理;
3)将RawEvent传递给dispatcher处理;
这块代码比较固定,也很少去适配,了解下即可android\frameworks\native\services\inputflinger\*1、InputReader对象启动线程
android\frameworks\native\services\inputflinger\reader\InputReader.cpp
status_t InputReader::start() {if (mThread) {return ALREADY_EXISTS;}mThread = std::make_unique<InputThread>("InputReader", [this]() { loopOnce(); }, [this]() { mEventHub->wake(); });return OK;
}2、InputThread对,提供一个线程模板
android\frameworks\native\services\inputflinger\InputThread.cpp
InputThread::InputThread(std::string name, std::function<void()> loop, std::function<void()> wake): mName(name), mThreadWake(wake) {mThread = sp<InputThreadImpl>::make(loop);mThread->run(mName.c_str(), ANDROID_PRIORITY_URGENT_DISPLAY);
}3、读取输入事件
//主要使用inotify监测/dev/input,使用epoll监测有无数据
void InputReader::loopOnce() { //作为函数参数传给InputThread
std::vector<RawEvent> events = mEventHub->getEvents(timeoutMillis); //读取event设备节点
...
mQueuedListener.flush(); //flush队列,通知dispatcher线程
...
}
2、InputReader和EventHub的分工
1)InputReader(上层角色)

在这里插入图片描述

1)如果设备刚接入,则先addDeviceLocked() 添加设备

2)如果设备已存在且有输入事件产生,则processEventLocked()

3)处理事件对象,这里以KeyboardInputMapper为例

2)EventHub(底层角色)

EventHub读取设备节点,获取输入事件RawEvent
在这里插入图片描述

1)读取输入设备驱动信息(底层原理是使用inotify/epoll机制进行监测),加载idc/kcm/kl文件(驱动设备提供路径);

2)最终转换成RawEvent,交给InputReader继续处理;

EventHub是linux device交互的功能类

1、openDeviceLocked
android\frameworks\native\services\inputflinger\reader\EventHub.cpp
void EventHub::openDeviceLocked(const std::string& devicePath) {...//打开设备int fd = open(devicePath.c_str(), O_RDWR | O_CLOEXEC | O_NONBLOCK);if (fd < 0) {ALOGE("could not open %s, %s\n", devicePath.c_str(), strerror(errno));return;}...//ioctl得到信号// Get device name.if (ioctl(fd, EVIOCGNAME(sizeof(buffer) - 1), &buffer) < 1) {ALOGE("Could not get device name for %s: %s", devicePath.c_str(), strerror(errno));} else {buffer[sizeof(buffer) - 1] = '\0';identifier.name = buffer;}...//加载idc//kl/kcmif (device->classes.any(InputDeviceClass::KEYBOARD | InputDeviceClass::JOYSTICK |InputDeviceClass::SENSOR)) {// Load the keymap for the device.keyMapStatus = device->loadKeyMapLocked();}
}
3)InputReader和EventHub的分工介绍(分层思想)

在这里插入图片描述

3、复杂的底层数据结构介绍

1)RawEvent/input_event/Device/InputDevice

1、android\frameworks\native\services\inputflinger\reader\include\EventHub.h
struct RawEvent { //// Time when the event happenednsecs_t when;// Time when the event was read by EventHub. Only populated for input events.// For other events (device added/removed/etc), this value is undefined and should not be read.nsecs_t readTime;int32_t deviceId;int32_t type;int32_t code;int32_t value;
};2、include/linux/input.h
struct input_event{struct timeval time;__u16 type;__u16 code;__s32 value;
};3、Device //注意不是linux device,不要混淆
android\frameworks\native\services\inputflinger\reader\include\EventHub.h
struct Device {int fd; // may be -1 if device is closedconst int32_t id;const std::string path;const InputDeviceIdentifier identifier; //输入设备的基本信息...std::string configurationFile; //对应idc配置文件std::unique_ptr<PropertyMap> configuration;std::unique_ptr<VirtualKeyMap> virtualKeyMap;KeyMap keyMap; //对应两种配置文件,kcm->kl,先找kcm映射,找不到再找kl映射
}
关于Device信息,Android上可以敲dumpsys input查看所有的input设备信息定义
F:\1.code\MTK9256_AN14\android\frameworks\native\services\inputflinger\reader\include\EventHub.h
std::unordered_map<int32_t, std::unique_ptr<Device>> mDevices;4、
F:\1.code\MTK9256_AN14\android\frameworks\native\services\inputflinger\reader\include\InputDevice.hclass InputDevice { //绑定EventHub的Device,目的是封装Device的复杂操作,给上层提供更方便的操作
private:InputReaderContext* mContext;int32_t mId;int32_t mGeneration;int32_t mControllerNumber;InputDeviceIdentifier mIdentifier;std::unordered_map<int32_t, DevicePair> mDevices;
}
//定义
F:\1.code\MTK9256_AN14\android\frameworks\native\services\inputflinger\reader\include\InputReader.h
std::unordered_map<int32_t /*eventHubId*/, std::shared_ptr<InputDevice>> mDevices GUARDED_BY(mLock);5、
/* Types of input device configuration files. */
enum class InputDeviceConfigurationFileType : int32_t {CONFIGURATION = 0,     /* .idc file */KEY_LAYOUT = 1,        /* .kl file */KEY_CHARACTER_MAP = 2, /* .kcm file */
};

2)KeyMap-KeyLayoutMap-KeyCharacterMap

1、
F:\1.code\MTK9256_AN14\android\frameworks\native\include\input\Keyboard.h
class KeyMap {
public:std::string keyLayoutFile;std::shared_ptr<KeyLayoutMap> keyLayoutMap;std::string keyCharacterMapFile;std::shared_ptr<KeyCharacterMap> keyCharacterMap;
}2、
class KeyLayoutMap {
private:struct Key {int32_t keyCode;uint32_t flags;};
}
kl文件格式举例
key 305   BUTTON_B   // key <linux key> <Android key>3、
class KeyCharacterMap {
private:struct Behavior {/* The meta key modifiers for this behavior. */int32_t metaState = 0;/* The character to insert. */char16_t character = 0;/* The fallback keycode if the key is not handled. */int32_t fallbackKeyCode = 0;/* The replacement keycode if the key has to be replaced outright. */int32_t replacementKeyCode = 0;};struct Key {/* The single character label printed on the key, or 0 if none. */char16_t label = 0;/* The number or symbol character generated by the key, or 0 if none. */char16_t number = 0;/* The list of key behaviors sorted from most specific to least specific* meta key binding. */std::list<Behavior> behaviors;};class Parser {} //加载kcm文件时用此class来解析
}kcm文件格式举例:
1、
key B {label:                              'B'  # 印在按键上的文字                       base:                               'b'  # 如果没有其他按键(shift, ctrl等)同时按下,此按键对应的字符是'b'                     shift, capslock:                    'B'  # 组合按键,如shift+base='B', capslock+base='B'
}
2、
key SPACE {label:                              ' 'base:                               ' 'alt, meta:                          fallback SEARCH   #组合按键ctrl:                               fallback LANGUAGE_SWITCH   #组件按键
}解析组合按键构造三个Behavior
Behavior.metaState=alt
Behavior.fallbackKeyCode=AKEYCODE_SEARCHBehavior.metaState=meta
Behavior.fallbackKeyCode=AKEYCODE_SEARCHBehavior.metaState=ctrl
Behavior.fallbackKeyCode=AKEYCODE_LANGUAGE_SWITCH放到链表std::list<Behavior> behaviors

3)涉及的数据结构总览
在这里插入图片描述

2)Dispatcher(CPP)

1、Dispatcher的工作

在这里插入图片描述

要点解析

1)输入事件之按键分类:
Global key(一键启动 - 快捷键)
system key(静音、音量、home键)
user key(普通输入按键 )2)inboundQueue/outboundQueue(双端队列 - 用于储存输入事件);
出队:mInboundQueue.pop_front();
入队:mInboundQueue.push_front();3)mCommandQueue(用于储存Command):  
出队:mCommandQueue.pop_front();
入队:mCommandQueue.push_back(command);
2、代码时序图

输入事件有多种类型(触摸、拖动、鼠标等等),这里以输入按键为例(从底层->上层)
在这里插入图片描述

3、关键点分析
1)reader与dispatcher的交互实现

1、总体框图

在这里插入图片描述

InputFlinger进程

android\frameworks\native\services\inputflinger\host\main.cpp

2、InputManager的构造函数中建立联系,通过mQueuedListener,Reader获得Dispatcher的指针;

android\frameworks\native\services\inputflinger\InputManager.cpp
/*** The event flow is via the "InputListener" interface, as follows:* InputReader -> UnwantedInteractionBlocker -> InputProcessor -> InputDispatcher*/
InputManager::InputManager(const sp<InputReaderPolicyInterface>& readerPolicy,InputDispatcherPolicyInterface& dispatcherPolicy) {mDispatcher = createInputDispatcher(dispatcherPolicy);mProcessor = std::make_unique<InputProcessor>(*mDispatcher);mBlocker = std::make_unique<UnwantedInteractionBlocker>(*mProcessor);mReader = createInputReader(readerPolicy, *mBlocker);
}

3、处理流 InputReader -> UnwantedInteractionBlocker -> InputProcessor -> InputDispatcher

类图

在这里插入图片描述

4、InputListenerInterface

android\frameworks\native\services\inputflinger\InputListener.cpp1、根据NotifyArgs的类型执行对应的函数-泛型思想
void InputListenerInterface::notify(const NotifyArgs& generalArgs) {Visitor v{[&](const NotifyInputDevicesChangedArgs& args) { notifyInputDevicesChanged(args); },[&](const NotifyConfigurationChangedArgs& args) { notifyConfigurationChanged(args); },[&](const NotifyKeyArgs& args) { notifyKey(args); },[&](const NotifyMotionArgs& args) { notifyMotion(args); },[&](const NotifySwitchArgs& args) { notifySwitch(args); },[&](const NotifySensorArgs& args) { notifySensor(args); },[&](const NotifyVibratorStateArgs& args) { notifyVibratorState(args); },[&](const NotifyDeviceResetArgs& args) { notifyDeviceReset(args); },[&](const NotifyPointerCaptureChangedArgs& args) { notifyPointerCaptureChanged(args); },};std::visit(v, generalArgs);
}void QueuedInputListener::notifyKey(const NotifyKeyArgs& args) {traceEvent(__func__, args.id);mArgsQueue.emplace_back(args);
}2、flush - 逐个将mArgsQueue中的事件通过队列传递给dispatcher处理 - 泛型的简洁
void QueuedInputListener::flush() {for (const NotifyArgs& args : mArgsQueue) {mInnerListener.notify(args);}mArgsQueue.clear();
}
2)高优先级的按键处理介绍

在PhoneWindowManager.java类中会两个地方会对输入按键进行干预处理

为什么这样设计?因为需要优先处理高优先级的按键(如global/system key)

1、mPolicy.interceptKeyBeforeQueueing();

1)分类-global / system /user key

2)处理紧急事件-比如来电显示

2、mPolicy.interceptKeyBeforeDispatching()

1)响应处理global/system key

2)user key则正常传递到app处理

3、在frameworks中进行按键拦截,也主要在PhoneWindowManager下面两个函数加逻辑

interceptKeyBeforeDispatching()

interceptKeyBeforeQueueing()

3)不同事件按键的处理介绍
不同事件按键会走不同的代码分支,这里介绍下重点
场景分析1-global key(如AKEYCODE_TV - 快捷键)
场景分析2-system key(如AKEYCODE_VOLUME_DOWM - 音量按键)
场景分析3-user key(如AKEYCODE_A、上下左右 - 普通按键)一、第一次预处理-interceptKeyBeforeQueueing
interceptKeyBeforeQueueing的处理结果会储存到事件结构体entry中,后续在InputDispatcher.cpp处理会用到
android/frameworks/base/services/core/java/com/android/server/policy/PhoneWindowManager.java
interceptKeyBeforeQueueingentry->interceptKeyResult == KeyEntry::InterceptKeyResult::UNKNOWN  //对于没被处理的输入按键事件都是UNKNOWN & POLICY_FLAG_PASS_TO_USER
entry->policyFlags & POLICY_FLAG_PASS_TO_USER二、第二次预处理
android/frameworks/base/services/core/java/com/android/server/policy/PhoneWindowManager.java
interceptKeyBeforeDispatching1、对于system key直接处理,如
switch(keyCode) {case KeyEvent.KEYCODE_HOME:return handleHomeShortcuts(displayId, focusedToken, event);case KeyEvent.KEYCODE_MENU:
...
}
return key_consumed;2、对于global key会发送广播,如
if (isValidGlobalKey(keyCode)&& mGlobalKeyManager.handleGlobalKey(mContext, keyCode, event)) {return key_consumed;}
return key_consumed;3、对于user key, 在此函数内不处理,直接返回
return key_not_consumed;三、第二次预处理后的几种返回值
android/frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cppvoid InputDispatcher::doInterceptKeyBeforeDispatchingCommand(const sp<IBinder>& focusedWindowToken,KeyEntry& entry) {const KeyEvent event = createKeyEvent(entry);nsecs_t delay = 0;{ // release lockscoped_unlock unlock(mLock);android::base::Timer t;delay = mPolicy.interceptKeyBeforeDispatching(focusedWindowToken, event, entry.policyFlags);if (t.duration() > SLOW_INTERCEPTION_THRESHOLD) {ALOGW("Excessive delay in interceptKeyBeforeDispatching; took %s ms",std::to_string(t.duration().count()).c_str());}} // acquire lockif (delay < 0) {entry.interceptKeyResult = KeyEntry::InterceptKeyResult::SKIP;  //对应system key/global key} else if (delay == 0) {entry.interceptKeyResult = KeyEntry::InterceptKeyResult::CONTINUE;  //对应user key} else {entry.interceptKeyResult = KeyEntry::InterceptKeyResult::TRY_AGAIN_LATER;entry.interceptKeyWakeupTime = now() + delay;}
}四、user key的处理
android/frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp
对于user key而言,dispatchOnce会执行两次,其中第一个循环用于处理global/system key(因为优先级更高一些),第二循环则处理user key(发布到app),代码设计如此,可自行根据代码琢磨。if (runCommandsLockedInterruptable()) {nextWakeupTime = LLONG_MIN;
}
mLooper->pollOnce(timeoutMillis); //下一轮循环将处理user key,最终发布到app
4)涉及的Server和Service介绍
1、SystemServer
2、
3、WindowManagerService
wm = WindowManagerService.main(context, inputManager, !mFirstBoot,new PhoneWindowManager(), mActivityManagerService.mActivityTaskManager);ServiceManager.addService(Context.WINDOW_SERVICE, wm, /* allowIsolated= */ false,DUMP_FLAG_PRIORITY_CRITICAL | DUMP_FLAG_PROTO);4、
public class PhoneWindowManager implements WindowManagerPolicy {static final String TAG = "WindowManager";
}5、LocalServices.addService(WindowManagerPolicy.class, mPolicy);

3、Reader和Dispatcher的创建和启动

了解到Reader和Dispatcher的作用,再来从宏观上看看创建和启动过程

1)流程图

在这里插入图片描述

注意,dispatcher线程要优先与reader线程启动,那样保证消息能第一次时间被处理

3、SystemServer

开机流程:Android系统源码开机启动过程

/android/frameworks/base/services/java/com/android/server/SystemServer.java 

八、APP与输入系统的联系

1、总体交互框图

在这里插入图片描述

1)SystemServer通过(connect →inputchannel → socketpair) 将输入事件传递给app

2、inputchannel

1、场景分析 - inputchannel创建过程分析

1)流程图

在这里插入图片描述

2)值得注意的点
1、APP远程调用WM服务
产生远程调用的语句:mWindowSession.addToDisplay()细节分析追溯下mWindowSession1、构造函数中又调用自身的构造函数
mWindowSession在构造函数中赋值
public ViewRootImpl(Context context, Display display) {this(context, display, WindowManagerGlobal.getWindowSession(), new WindowLayout());
}public ViewRootImpl(@UiContext Context context, Display display, IWindowSession session, WindowLayout windowLayout) {mContext = context;mWindowSession = session;...
}2、获取server端session
android\frameworks\base\core\java\android\view\WindowManagerGlobal.javapublic static IWindowSession getWindowSession() {synchronized (WindowManagerGlobal.class) {if (sWindowSession == null) {try {// Emulate the legacy behavior.  The global instance of InputMethodManager// was instantiated here.// TODO(b/116157766): Remove this hack after cleaning up @UnsupportedAppUsageInputMethodManager.ensureDefaultInstanceForDefaultDisplayIfNecessary();IWindowManager windowManager = getWindowManagerService();sWindowSession = windowManager.openSession( //获取session句柄,后续使用session访问server接口new IWindowSessionCallback.Stub() {@Overridepublic void onAnimatorScaleChanged(float scale) {ValueAnimator.setDurationScale(scale);}});} catch (RemoteException e) {throw e.rethrowFromSystemServer();}}return sWindowSession;}}public static IWindowManager getWindowManagerService() {synchronized (WindowManagerGlobal.class) {if (sWindowManagerService == null) {sWindowManagerService = IWindowManager.Stub.asInterface(  //binder 中 client调用server接口ServiceManager.getService("window"));...}return sWindowManagerService;}
}3、哪里会new ViewRootImpl实例?
android\frameworks\base\core\java\android\view\WindowManagerGlobal.java
mGlobal.addView()中会new一个ViewRootImpl实例
root = new ViewRootImpl(view.getContext(), display);小结:如何找Framework中的远程调用?Binder细节很多,我们只需要关注以下几点即可找到1、client调用:mWindowSession.addToDisplay()2、根据IWindowManager类推断出存在aidl文件:IWindowSession.aidl -> out目录下找IWindowSession.java3、确定server 函数位置:server端必定会extends :IWindowSession.Stub,并根据函数名来双重确定
java/C++层的inputChannel
java/C++层都对应一个 inputChannel类(包含socketpair fd),用于参数传递
1、java层
android\frameworks\base\core\java\android\view\InputChannel.java
2、C++层
android\frameworks\native\libs\input\InputTransport.cpp3、JNI中的转换
jobject inputChannelObj = android_view_InputChannel_createJavaObject(env, std::move(*inputChannel));
C++层inputchannel类
声明:android\frameworks\native\include\input\InputTransport.h
定义:android\frameworks\native\libs\input\InputTransport.cpp
继承于Parcelable,即用于进程间通信的类
1、几个变量
std::string mName;
android::base::unique_fd mFd; //保存socker文件句柄
sp<IBinder> mToken;2、创建client server 对应的两个inputChannel,及对应两个socker
static status_t openInputChannelPair(const std::string& name,std::unique_ptr<InputChannel>& outServerChannel,std::unique_ptr<InputChannel>& outClientChannel);3、status_t sendMessage(const InputMessage* msg); //service向fd写msg(按键信息)4、status_t receiveMessage(InputMessage* msg); //client读fd的msg(按键信息)
sockerpair 传递
android\frameworks\native\libs\input\InputTransport.cpp
1、返回给应用程序的fd需要深拷贝,否则返回后会被释放
base::unique_fd InputChannel::dupFd() const {android::base::unique_fd newFd(::dup(getFd()));...}return newFd;
}

3、APP监听输入事件

APP启动时会,先获取到connection,再设置启动监听输入事件,流程如下

在这里插入图片描述

4、Looper将输入事件传递给APP

1)从底层传递到上层,流程图如下
在这里插入图片描述

2)重点讲解

1、Response、Request结构体struct Response {SequenceNumber seq;int events;Request request;};struct Request {int fd;int ident;int events;sp<LooperCallback> callback;void* data;uint32_t getEpollEvents() const;};2、onInputEvent()是java层处理输入事件的总入口

九、APP层的处理

1、stage

在onInputEvent()函数处理中,还有一系列的传递,用stage来表示各个阶段的处理,其中有针对系统输入法(InputMethodManager)的处理
在这里插入图片描述

1)术语
1、ime :input method(可以理解为系统内置输入法),实现

android/frameworks/base/core/java/android/view/inputmethod/InputMethodManager.java

经过输入法处理(app),输出的应是一个kcm对应的字符。

2、synthetic - 综合处理

2)Android层-ViewrootImpl

当前焦点应用的ViewrootImpl对象会收到该消息,并对消息进行分发处理,最终将其发送到对应的View对象中进行界面响应。

3)注意,对于按键类事件:

1、我们关注ime之后的即可;

2、view处理不了再传递给Activity处理;

3、stage主要关注ViewPreImeInputStage/ViewPostImeInputStage;

2、Framework层的类图

在这里插入图片描述

1)跟踪代码需要理清以上类的关系

3、代码时序图

在这里插入图片描述

重点说明

1、mView/mFocused 以TextView为例

2、按下一个按键的过程(APP)

完成一次按键,按下再松开

按下事件:onkeypreIme -> onkey(setOnKeyListener) -> onkeydown(view) → onkeydown(activity)

松开事件:onkeypreIme -> onkey(setOnKeyListener) -> onkeyup(view) ->onkeyup (activity)

3、mView一般是一个DecorView (extends View) 表示

Activity.java

ViewManager wm = getWindowManager();
wm.addView(mDecor, getWindow().getAttributes());

4、A1 没处理交给 A2,依次类推A3 A4;

十、项目应用

1、系统定制部分-中控-TVAPI

1)从整体的输入系统可知,可以在PhoneWindowManager 中interceptKeyBeforeQueueing/interceptKeyBeforeDispatching加入截取逻辑;

2)PhoneWindowManager 加入中控钩子截取处理,中控处理不了再调用tvapi接口继续处理

3)中控是为了进一步优化系统性能,避免过多的按键影响性能(从整个输入系统来看,流程是相当之多和复杂的,代价就是耗时);

4)在没有中控之前,直接在PhoneWindowManager远程调用tvapi;

1、
public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) {...//CVTE Android Patch Beginif(com.cvte.os.CtvManagerInternal.callGeneric(com.cvte.os.ICtvDataDefine.WHAT_COMMON_HOTKEYMANAGER, "cvteInterceptKeyBeforeQueueing", false, event)){return 0;}//CVTE Android Patch End
}android\vendor\cvte\cvte-central-control\src\com\cvte\server\manager\CvteHotkeyManager.java
public boolean cvteInterceptKeyBeforeQueueing(Object... obj) {... //中控能处理的先处理,处理不了再转发给tvapiif (mHotkeyService != null) {if(event.getKeyCode() == CtvPerformanceService.CVTE_KEYEVENT_BASE){LLog.i(TAG,"TvApi cvteInterceptKeyBeforeQueueing CvtKeyEvent.KEYCODE_UNKNOWN no need process");return true;//skip CvtKeyEvent.KEYCODE_UNKNOWN, no need process}try {long begin = SystemClock.uptimeMillis();boolean ret = mHotkeyService.handleKeyEventsBefore(event) || (IS_KERNEL_AT && isKeyPadEvent(event)); //转到tvapi处理LLog.i(TAG,"TvApi cvteInterceptKeyBeforeQueueing " + KeyEvent.keyCodeToString(event.getKeyCode()) + " : "+ KeyEvent.actionToString(event.getAction()) + ", ret:" + ret + ", cost:"+ (SystemClock.uptimeMillis() - begin) + "ms");return ret;} catch (Exception e) {LLog.e(TAG, "Unknown Exception", e);}} else {bindService();return isIntercept(event) || (IS_KERNEL_AT && isKeyPadEvent(event));}
}2、
public long interceptKeyBeforeDispatching(IBinder focusedToken, KeyEvent event, int policyFlags) {//CVTE Android Patch Beginif (com.cvte.os.CtvManagerInternal.callGeneric(com.cvte.os.ICtvDataDefine.WHAT_COMMON_HOTKEYMANAGER,"cvteInterceptKeyBeforeDispatching", false, event)) {return -1;}
}android\vendor\cvte\cvte-central-control\src\com\cvte\server\manager\CvteHotkeyManager.javapublic boolean cvteInterceptKeyBeforeDispatching(Object... obj) {KeyEvent event = (KeyEvent) obj[0];... //中控能处理的先处理,处理不了再转发给tvapiif (mHotkeyService != null) {try {// LLog.d(TAG, "enter cvteInterceptKeyBeforeDispatching onGlobalKeyEvent");long begin = SystemClock.uptimeMillis();boolean ret = mHotkeyService.handleKeyEvents(event); //转到tvapi处理LLog.i(TAG,"TvApi KeyBeforeDispatching " + KeyEvent.keyCodeToString(event.getKeyCode()) + " : "+ KeyEvent.actionToString(event.getAction()) + ", ret:" + ret + ", cost:"+ (SystemClock.uptimeMillis() - begin) + "ms");return ret;} catch (Exception e) {LLog.e(TAG, "Unknown Exception", e);}} else {bindService();return isIntercept(event);}return false;}

2、CVTE - 无焦点应用优化处理

1、android\frameworks\base\core\java\android\view\ViewRootImpl.java
private int processKeyEvent(QueuedInputEvent q) {final KeyEvent event = (KeyEvent)q.mEvent;//cvte add beginif(FocusAdapterUtil.getInstance().reflectProcessKeyEventMethodCvte(mView, event)){return FINISH_HANDLED;}//cvte add end
}2、
android/frameworks/base/core/java/com/cvte/specialapkviewfocus/FocusAdapterUtil.java
android/frameworks/base/core/java/com/cvte/specialapkviewfocus/CvteSpecialApkViewFocusHandlerImpl.java这个类的主要功能是处理特定海外主流APK在某些界面上没有焦点的问题。代码中提到了多个特定的应用程序,如Amazon PrimeVideo、Netflix、Gaana和Twitter等,并为这些应用提供了特定的焦点处理逻辑。
比如处理Netflix的主页和播放界面焦点问题,Amazon PrimeVideo的播放和主页焦点问题,以及Gaana和Twitter的登录界面焦点问题,它通过模拟用户操作和系统事件来解决焦点问题,提升用户体验。

3、增加按键映射

https://tvgit.gz.cvte.cn/q/message:PT230025-77

4、APP应用编写代码

只需简单两步即可实现input事件处理

1、实现onKey处理逻辑
public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource {public interface OnKeyListener {boolean onKey(View v, int keyCode, KeyEvent event);}
}
2、setOnKeyListener(new View.OnkeyListener(){....});

十一、调试

1、getevent(linux)linux_key 遥控码
/dev/input/event1: 0004 0004 0088908d
/dev/input/event1: 0001 0071 00000001getevent -l
/dev/input/event1: EV_MSC       MSC_SCAN             00889011
/dev/input/event1: EV_KEY       KEY_MUTE             DOWNIR:底层没有配置,则为KEY_UNKNOW
BT:如果缺少hid,会设为默认值Linux ( KEY_UNKNOWN ),此时需要去配HID2、sendevent (linux)
sendevent <目标设备号> <码值>
sendevent /dev/input/event5 1 42 13、dumpsys (android)
dumpsys input //获取当前设备使用的kl和kcm
注意:IR和BT吃的kl不一样,连接蓝牙设备后 dumpsys input会变!4、input (linux)
input keyevent "num"  //模拟遥控器、键盘、鼠标各种输入设备操作

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/19612.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

HarmonyOS-ArkTS基础快速入门

目录 ArkTS 快速入门 ArkTS 快速入门 如图&#xff0c;index.etc里面的内容&#xff08;图中框住的大长方形区域&#xff09;会渲染到预览区中&#xff0c;而console.log(xx,xxx)用于内容的打印&#xff0c;需要在日志中查看打印的内容

FRRouting配置与OSPF介绍,配置,命令,bfd算法:

文章目录 1、frrouting的配置&#xff1a;2、ospf2.1、检测和维护邻居关系2.2、ospfDR和BDR2.3、odpf邻居表2.4、ospf常用命令2.5、bfd配置 1、frrouting的配置&#xff1a; sudo service zebra start sudo service ospfd start telnet localhost 2604 en configure termina…

2-安装YIUI

YIUI框架&#xff1a;GitHub - LiShengYang-yiyi/YIUI: Unity3D UGUI Framework, 基于UI数据事件绑定为核心 数据驱动的UGUI框架, ETUI框架, ET框架官方推荐UI框架 ET框架&#xff1a;egametang/ET: Unity3D Client And C# Server Framework (github.com) 1 - 安装YIUI框架&a…

001-监控你的文件-FSWatch-C++开源库108杰

fswatch 原理与应用简介fswatch 安装fswatch 实践应用具体应用场景与细节补充 1. 简介 有些知识&#xff0c;你知道了不算厉害&#xff0c;但你要是不知道&#xff0c;就容易出乱。 很多时候&#xff0c;程序需要及时获取磁盘上某个文件对象&#xff08;文件夹、文件&#xff0…

机器学习--逻辑回归

机器学习–逻辑回归 一、认知革命&#xff1a;从线性回归到逻辑回归 1.1 本质差异对比 维度线性回归逻辑回归输出类型连续值概率值 (0-1)目标函数最小二乘法极大似然估计数学表达式 y w T x b yw^Txb ywTxb p 1 1 e − ( w T x b ) p\frac{1}{1e^{-(w^Txb)}} p1e−(wTxb…

s1K 数据集:是一个用于提升语言模型推理能力的高质量数据集。包含 1,000 个问题,每个问题都配有详细的 推理路径 和 答案。

2025-02-07&#xff0c; 由斯坦福大学、华盛顿大学等研究机构创建了 s1K 数据集&#xff0c;该数据集包含 1,000 个精心挑选的问题&#xff0c;并配以推理轨迹和答案&#xff0c;为语言模型推理能力的提升提供了重要的数据基础。 一、研究背景 1. 研究背景 近年来&#xff0c;…

DockerDesktop更改默认的磁盘镜像地存储位置

DockerDesktop更改默认的磁盘镜像地存储位置 文章目录 DockerDesktop更改默认的磁盘镜像地存储位置1. 默认存储位置2. 新建一个目录3. 将磁盘镜像存储位置改为新建的目录下 1. 默认存储位置 2. 新建一个目录 如&#xff1a;D:\DiskImagelocationData 3. 将磁盘镜像存储位置改为…

ASP.NET Core SixLabors.ImageSharp 位图图像创建和下载

从 MVC 控制器内部创建位图图像并将其发送到浏览器&#xff1b;用 C# 编写并与 Linux 和 Windows 服务器兼容。 使用从 ASP.NET MVC 中的控制器下载任何文件类型File。 此示例创建一个位图 (jpeg) 并将其发送到浏览器。它需要 NuGet 包SixLabors.ImageSharp v1.0.4。 另请参…

容联云联络中心AICC:深度整合DeepSeek,业务验证结果公开

容联云重磅推出AICC3.2版本&#xff0c;实现了智能化的升级与外呼效率的突破——深度整合DeepSeek-R1大模型、预测式外呼在数据分析侧的增强、全渠道路由能力、一键多呼效率的强化。 同时&#xff0c;全面接入DeepSeek-R1的容联云 AICC3.2 &#xff0c;目前已与某知名汽车金融企…

链表和list

链表和list ‍ ​ ​ ​ ​ ​ ​ ​ ​ ​ 算法题中的经典操作&#xff1a;用空间代替时间​ ​ ​ ​ 双链表头插顺序&#xff1a; 1.先修改新结点的左右指针 2.然后修改结点y的左指针 3.最后修改哨兵位的右指针 双链表在任意位置&#xff08;p&#xff09;之后插入…

Junit——白盒测试

Java单元测试框架&#xff0c;主要用于测试Java程序中的各个单元。 1.验证代码功能是否符合预期 2.及时 发现修复 代码中的缺陷&#xff0c;提高代码质量 入门 最早学习java&#xff0c;代码对不对&#xff0c;通过main 方法运行&#xff0c;观看结果是否符合预期。 packa…

1.MySQL概述

1.1 数据模型 介绍完了Mysql数据库的安装配置之后&#xff0c;接下来我们再来聊一聊Mysql当中的数据模型。学完了这一小节之后&#xff0c;我们就能够知道在Mysql数据库当中到底是如何来存储和管理数据的。 在介绍 Mysql的数据模型之前&#xff0c;需要先了解一个概念&#x…

Deep seek学习日记1

Deepseek最强大的就是它的深度思考&#xff0c;并且展现了它的思考过程。 五种可使用Deep seek的方式&#xff08;应该不限于这五种&#xff0c;后续嵌入deepseek的应该更多&#xff0c;多了解一点因为官网容易崩~~&#xff09;&#xff1a; 1.deep seek官网 2.硅基流动silicon…

JAVA中的异常

一、简介 1.1 什么是异常 异常&#xff0c;是对程序在运行过程中遇到的种种不正常的情况的描述。异常在java中用Exception类来描述。如果程序遇到了未经处理的异常&#xff0c;将会导致程序无法编译或者无法继续运行。 1.2 异常的继承体系 在java中使用类Throwable来描述所有…

数字水印嵌入及提取系统——基于小波变换GUI

数字水印嵌入及提取系统——基于小波变换GUI 基于小波变换的数字水印系统&#xff08;Matlab代码GUI操作&#xff09; 【有简洁程序报告】【可作開题完整文档达辩PPT】 本系统主要的内容包括&#xff1a; &#xff08;1&#xff09;使用小波变换技术实现二值水印图像的加密、…

Linux_帮助指令

man 获得帮助信息 基本语法: man [命令或配置文件] 在linux下,隐藏文件是以 .开头, 选项可以组合使用, 比如 ls -al, 不如 ls -al /root help 指令 基本语法: help 命令 (功能描述: 获取shell内置命名的帮助信息) 英语不好建议百度

Day4 25/2/17 MON

【一周刷爆LeetCode&#xff0c;算法大神左神&#xff08;左程云&#xff09;耗时100天打造算法与数据结构基础到高级全家桶教程&#xff0c;直击BTAJ等一线大厂必问算法面试题真题详解&#xff08;马士兵&#xff09;】https://www.bilibili.com/video/BV13g41157hK?p4&v…

redis集群模式

1.集群模式 作用&#xff1a;解决单点故障问题 集群的模式&#xff1a;1.主从模式&#xff0c;2、哨兵模式&#xff0c;3、集群化模式 1.1主从模式 特点&#xff1a;1个主节点多个从节点&#xff0c;主节点负责读写操作&#xff0c;而从节点只能负责读操作&#xff0c;当主…

力扣 乘积最大子数组

动态规划&#xff0c;注意负负得正&#xff0c;dp交换。 题目 注意这里的dp的乘积要求最大&#xff0c;而两个很大的负数相乘也是大的&#xff0c;因此在每遍历到一个数时要存一个最大值的dp与一个最小值的dp&#xff0c;然后遍历完后再去存ans的dp。由于存在负数&#xff0c;…

【Postgresql】Windows 部署 Postgresql 数据库 (图文教程)

文章目录 准备工作Postgresql 下载Postgresql 安装初始化数据库数据库链接设置允许远程连接测试链接 更多相关内容可查看 准备工作 操作系统&#xff1a;Windows 7 或更高版本&#xff08;推荐 Windows 10 或 Windows Server 2016&#xff09;。 硬件要求&#xff1a; 至少 …