当前位置: 首页 > news >正文

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"  //模拟遥控器、键盘、鼠标各种输入设备操作

相关文章:

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

一、资料快车 1、深入了解Android输入系统&#xff1a;https://blog.csdn.net/innost/article/details/47660387 2、书籍 - Android系统源代码情景分析 二、Perface 1、参考&#xff1a; 2、系统程序分析方法 1&#xff09;加入log&#xff0c;并跟着log一步步分析 -logc…...

Java中Map循环安全的删除数据的4中方法

文章目录 前言一、使用Iterator删除二、使用 removeIf&#xff08;Java 8&#xff09;三、遍历时记录需要删除的键&#xff08;不推荐&#xff09;四、使用 Stream&#xff08;Java 8&#xff09;总结 前言 在 Java 中&#xff0c;遍历 HashMap 并删除数据时&#xff0c;直接使…...

蓝桥杯(B组)-每日一题(1093字符逆序)

c中函数&#xff1a; reverse(首位置&#xff0c;尾位置&#xff09; reverse(s.begin(),s.end()) 头文件&#xff1a;<algorithm> #include<iostream> #include<algorithm>//运用reverse函数的头文件 using namespace std; int main() {string s;//定义一…...

【数据分析】3 数据分析成长之路

职业发展路径&#xff1a; 向上发展&#xff08;技术方向&#xff09;&#xff1a;可以详细说明成为数据科学家或专家所需的具体技能和步骤&#xff0c;包括学习的算法、工具等。向下发展&#xff08;业务方向&#xff09;&#xff1a;可以探讨结合业务知识的具体领域&#xff…...

循环神经网络RNN原理与优化

目录 前言 RNN背景 RNN原理 上半部分&#xff1a;RNN结构及按时间线展开图 下半部分&#xff1a;RNN在不同时刻的网络连接和计算过程 LSTM RNN存在的问题 LSTM的结构与原理 数学表达层面 与RNN对比优势 应用场景拓展 从简易但严谨的代码来看RNN和LSTM RNN LSTM 前言 绕循环神经…...

Python正则表达式处理中日韩字符过滤全解析

Python正则表达式处理中日韩字符过滤全解析 一、核心原理&#xff1a;Unicode字符范围定位 中日韩字符在Unicode中的分布&#xff1a; 中文&#xff1a;\u4e00-\u9fff&#xff08;基本区&#xff09; \u3400-\u4dbf&#xff08;扩展A区&#xff09; \U00020000-\U0002a6df…...

Zabbix 7.2实操指南:基于OpenEuler系统安装Zabbix 7.2

原文出处&#xff1a;乐维社区 部署环境 openEuler 22.03 LTS PHP 8.0 Apache Mysql 8.0 MySQL数据库 6.0 以上版本需要安装mysql8.0以上版本的数据库&#xff08;以mysql为例子&#xff09;。 欧拉系统自带 mysql8.0 的源&#xff0c;无需要安装额外的源。 安装mysql …...

扩展阅读-Elasticsearch 通过索引阻塞实现数据保护深入解析

目录 前言 1、索引阻塞的种类 2、什么时候使用阻塞&#xff1f; 场景1&#xff1a;进行系统维护场景。 场景2&#xff1a;保护数据不被随意更改场景。 场景3&#xff1a;优化资源使用的场景。 场景4&#xff1a;遵守安全规则场景。 3、添加索引阻塞API 4、解除设置 AP…...

SpringMVC重定向接口,参数暴露在url中解决方案!RedirectAttributes

OK&#xff0c;首先描述下业务场景&#xff0c;终端数量限制登录 1.首先访问项目login的get接口 2.输入账号密码点击登录后&#xff0c;会请求login的POST接口 3.后台对终端数量逻辑处理不允许登录跳回到登录页面 4.因代码原因需在后台进行多次重定向接口&#xff0c;最后跳…...

硬件学习笔记--46 电能表影响量试验梳理

目录 1.电流和电压电路中的谐波影响试验 1&#xff09;电流和电压电路中谐波——第5次谐波试验 2&#xff09;电流和电压电路中谐波——方顶波波形试验 3&#xff09;​​​​​​​电流和电压电路中谐波——尖顶波波形试验 4&#xff09;​​​​​​​电流和电压电路中谐…...

大数据技术之HBase操作归纳

HBase基本命令总结表(实际操作方式) 进入Hbase&#xff1a;hbase shell 方式一&#xff1a;命令行窗口来操作HBase 1.通用性命令 version 版本信息 status 查看集群当前状态 whoami 查看登入者身份 help 帮助2.HBase DDL操作(对象级操作) 2.1、namespace命名空间(相当…...

后端Java Stream数据流的使用=>代替for循环

API讲解 对比 示例代码对比 for循环遍历 package cn.ryanfan.platformback.service.impl;import cn.ryanfan.platformback.entity.Algorithm; import cn.ryanfan.platformback.entity.AlgorithmCategory; import cn.ryanfan.platformback.entity.DTO.AlgorithmInfoDTO; im…...

遗传算法与深度学习实战系列,自动调优深度神经网络和机器学习的超参数

遗传算法与深度学习实战系列文章 目录 进化深度学习生命模拟及其应用生命模拟与进化论遗传算法中常用遗传算子遗传算法框架DEAPDEAP框架初体验使用遗传算法解决N皇后问题使用遗传算法解决旅行商问题使用遗传算法重建图像遗传编程详解与实现粒子群优化详解与实现协同进化详解与…...

体验用ai做了个python小游戏

体验用ai做了个python小游戏 写在前面使用的工具2.增加功能1.要求增加视频作为背景。2.我让增加了一个欢迎页面。3.我发现中文显示有问题。4.我提出了背景修改意见&#xff0c;欢迎页面和结束页面背景是视频&#xff0c;游戏页面背景是静态图片。5.提出增加更多游戏元素。 总结…...

谷粒商城—分布式高级②.md

认证服务 1. 环境搭建 创建gulimall-auth-server模块,导依赖,引入login.html和reg.html,并把静态资源放到nginx的static目录下 2. 注册功能 (1) 验证码倒计时 //点击发送验证码按钮触发下面函数 $("#sendCode").click(function () {//如果有disabled,说明最近…...

阿里云ECS命名规则解析与规格选型实战指南

阿里云ECS实例的命名规则通常采用 “ecs.{实例族}.{规格大小}” 的结构,各部分含义如下: 命名字段说明ecs代表“弹性计算服务”(Elastic Compute Service)。{实例族}标识实例的用途和代次(如 g7、c7、r7),由字母+数字组成。{规格大小}表示实例的资源配置(如 large、2xl…...

Spring MVC 的核心以及执行流程

Spring MVC的核心 Spring MVC是Spring框架中的一个重要模块&#xff0c;它采用了经典的MVC&#xff08;Model-View-Controller&#xff09;设计模式。 MVC是一种软件架构的思想&#xff0c;它将软件按照模型&#xff08;Model&#xff09;、视图&#xff08;View&#xff09;…...

ai json处理提示词

在解析JSON数据时&#xff0c;提示词的设计需要明确任务目标、输入格式以及期望的输出格式。以下是一些常用的提示词示例&#xff0c;适用于不同的JSON解析场景&#xff1a; 1. 提取特定字段 用于从JSON中提取特定字段的值。 示例&#xff1a; 从以下JSON数据中提…...

2025开源数据工程全景图

作者 | Alireza Sadeghi 译自Practical Data Engineering 2025年开源数据工程领域呈现蓬勃创新与生态重构的双重态势&#xff0c;九大技术赛道在实时化、轻量化与云原生架构驱动下加速演进。一份来自外网的2025年开源数据工程全景图全面地展示了这一领域的发展态势与走向&…...

438. 找到字符串中所有字母异位词(LeetCode 热题 100)

题目来源&#xff1a; 438. 找到字符串中所有字母异位词 - 力扣&#xff08;LeetCode&#xff09; 题目内容&#xff1a; 给定两个字符串 s 和 p&#xff0c;找到 s 中所有 p 的 异位词的子串&#xff0c;返回这些子串的起始索引。不考虑答案输出的顺序。 示例 1: 输入: s &…...

label-studio的使用教程(导入本地路径)

文章目录 1. 准备环境2. 脚本启动2.1 Windows2.2 Linux 3. 安装label-studio机器学习后端3.1 pip安装(推荐)3.2 GitHub仓库安装 4. 后端配置4.1 yolo环境4.2 引入后端模型4.3 修改脚本4.4 启动后端 5. 标注工程5.1 创建工程5.2 配置图片路径5.3 配置工程类型标签5.4 配置模型5.…...

DAY 47

三、通道注意力 3.1 通道注意力的定义 # 新增&#xff1a;通道注意力模块&#xff08;SE模块&#xff09; class ChannelAttention(nn.Module):"""通道注意力模块(Squeeze-and-Excitation)"""def __init__(self, in_channels, reduction_rat…...

Keil 中设置 STM32 Flash 和 RAM 地址详解

文章目录 Keil 中设置 STM32 Flash 和 RAM 地址详解一、Flash 和 RAM 配置界面(Target 选项卡)1. IROM1(用于配置 Flash)2. IRAM1(用于配置 RAM)二、链接器设置界面(Linker 选项卡)1. 勾选“Use Memory Layout from Target Dialog”2. 查看链接器参数(如果没有勾选上面…...

反射获取方法和属性

Java反射获取方法 在Java中&#xff0c;反射&#xff08;Reflection&#xff09;是一种强大的机制&#xff0c;允许程序在运行时访问和操作类的内部属性和方法。通过反射&#xff0c;可以动态地创建对象、调用方法、改变属性值&#xff0c;这在很多Java框架中如Spring和Hiberna…...

DBAPI如何优雅的获取单条数据

API如何优雅的获取单条数据 案例一 对于查询类API&#xff0c;查询的是单条数据&#xff0c;比如根据主键ID查询用户信息&#xff0c;sql如下&#xff1a; select id, name, age from user where id #{id}API默认返回的数据格式是多条的&#xff0c;如下&#xff1a; {&qu…...

Java毕业设计:WML信息查询与后端信息发布系统开发

JAVAWML信息查询与后端信息发布系统实现 一、系统概述 本系统基于Java和WML(无线标记语言)技术开发&#xff0c;实现了移动设备上的信息查询与后端信息发布功能。系统采用B/S架构&#xff0c;服务器端使用Java Servlet处理请求&#xff0c;数据库采用MySQL存储信息&#xff0…...

【电力电子】基于STM32F103C8T6单片机双极性SPWM逆变(硬件篇)

本项目是基于 STM32F103C8T6 微控制器的 SPWM(正弦脉宽调制)电源模块,能够生成可调频率和幅值的正弦波交流电源输出。该项目适用于逆变器、UPS电源、变频器等应用场景。 供电电源 输入电压采集 上图为本设计的电源电路,图中 D1 为二极管, 其目的是防止正负极电源反接, …...

宇树科技,改名了!

提到国内具身智能和机器人领域的代表企业&#xff0c;那宇树科技&#xff08;Unitree&#xff09;必须名列其榜。 最近&#xff0c;宇树科技的一项新变动消息在业界引发了不少关注和讨论&#xff0c;即&#xff1a; 宇树向其合作伙伴发布了一封公司名称变更函称&#xff0c;因…...

【Android】Android 开发 ADB 常用指令

查看当前连接的设备 adb devices 连接设备 adb connect 设备IP 断开已连接的设备 adb disconnect 设备IP 安装应用 adb install 安装包的路径 卸载应用 adb uninstall 应用包名 查看已安装的应用包名 adb shell pm list packages 查看已安装的第三方应用包名 adb shell pm list…...

永磁同步电机无速度算法--基于卡尔曼滤波器的滑模观测器

一、原理介绍 传统滑模观测器采用如下结构&#xff1a; 传统SMO中LPF会带来相位延迟和幅值衰减&#xff0c;并且需要额外的相位补偿。 采用扩展卡尔曼滤波器代替常用低通滤波器(LPF)&#xff0c;可以去除高次谐波&#xff0c;并且不用相位补偿就可以获得一个误差较小的转子位…...