Android Focus小结

Android Focus的管理

本篇题目是"Android Focus管理",内容包括Input focus和Accessibility focus。

文章大纲:

  • focus的分类

  • Input focus

    window focus的控制逻辑

    window内view focus的控制逻辑

    touch mode如何发生变化,发生变化后对view的影响,比如已经highlight的UI是否会清除?

  • Accessibility focus

    哪些内容?

Touch mode: Touch mode 下,大部分view是不需要有focus 效果的;很多view也不愿意在touch mode下设置为focusable。

1. Focus

类型 Input Focus Accessibility Focus
定义 用于标识当前接收用户输入(如键盘、方向键)的视图。 无障碍服务(如屏幕阅读器)用于标识当前正在操作的界面元素,帮助残障用户感知界面内容
用户群体 所有用户。 主要服务于残障用户(如视力障碍)。
触发方式 用户点击、键盘导航、代码设置。 无障碍服务手势(如 TalkBack 的滑动导航)。
可见性 可能伴随光标或高亮效果。 通常通过无障碍服务的高亮框或语音反馈体现。

2. Input Focus

  • 由系统或开发者直接控制,通过 View.requestFocus() 设置。
    setFocusable: view是否可以收到焦点。
    setFocusableInTouchMode(...) 这个view在touch mode是否可以收到焦点。
  • 通过 View.isFocused() 判断是否获得焦点。
  • 直接影响用户输入事件(如键盘输入、方向键导航)。

Input focus会影响Key event的派发,可以分为两个层级来理解:

  1. Window 层级,Input 模块在派发key event时会找到focused window
  2. View 层级,ViewRootImpl接收到Input 模块派发给的key event后,在View hierarchy中找到focused view,将event派发给它
    View对象作为view hierarchy中的一员,持有焦点时:
    • event派发时可以找到它
    • 为了用户容易注意到它,highlight的UI效果是有必要的

Q:Focus是怎么在不同window之间转移的?
A: DisplayContent中的findFocusedWindowIfNeeded(int topFocusedDisplayId)方法用于查找focused window,查找的具体逻辑在lamda function mFindFocusedWindow中,updateFocusedWindowLocked(...)方法会调用findFocusedWindowIfNeeded(int topFocusedDisplayId)方法,找到focused window后会更新mCurrentFocusmCurrentFocus是一个WindowState类型的包内可见全局变量。InputMonitor的updateInputFocusRequest(...)方法会用到该变量,将window相关信息设置到input module。Input module会上报FOCUS event给ViewRootImpl通知Window focus发生了变化,继而处理view hierarchy内focus的逻辑,比如 focus清理逻辑,旧UI hightlight 效果清除和新UI highlight 绘制等。DisplayContent方法的调用触发场景很多,调用代码也有多处;ActiviytRecord,ActivityTaskManagerService,RootWindowContainer,WindowManagerService,WindowState和WindowToken都有调用。

关注InputDispatcher中的如下两个Event:

  • EventEntry::Type::FOCUS
    通知ViewRootImpl, window Focus发生了变化
  • EventEntry::Type::TOUCH_MODE_CHANGED
    通知ViewRootImpl,touch mode发生了变化

View.java中的requestFocus()方法

View.java中requestFocus()方法会触发其parent的requestChildFocus(...)方法,最终会调用到ViewRootImpl的requestChildFocus(...)方法,继而会调用performTraversals()方法,该方法会把measuge, relayout和draw都执行一遍,在这个过程中focused window会通知到input module。

view调用requestFocus申请焦点,如果成功拿到焦点会发送sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);

InputDispatcher中的几个方法的调用:

  1. setFocusedWindow(...)
    该方法调用的路径有很多,把能看懂或者比较明显可能触发的操作总结如下:
  • WindowManagerService中的relayoutWindow(...) 方法可能会触发
  • WindowManagerService中的addWindow() 方法可能会触发

SurfaceComposerClient android_view_surfaceControl SurfaceControl#Transaction InputMonitor#UpdateInputForAllWindowsConsumer InputMonitor DisplayContent RootWindowContainer WindowManagerService X SurfaceComposerClient android_view_surfaceControl SurfaceControl#Transaction InputMonitor#UpdateInputForAllWindowsConsumer InputMonitor DisplayContent RootWindowContainer WindowManagerService X ActivityRecord,ActivityTaskManagerService, WindowState和 RootWindowContainer等都可能会调用该方法 mCurrentFocus = newFocus 该方法调用的地方很多 :Transaction: mInputWindowCommands.focusRequests.push_back(request) opt [updateInputWindows] updateFocusedWindowLocked(int mode, boolean updateInputWindows)) updateFocusedWindowLocked(int mode, boolean updateInputWindows)) updateFocusedWindowLocked(int mode, boolean updateInputWindows)) setInputFocusLw(int mode, boolean updateInputWindows)) updateInputWindowsLw(false) scheduleUpdateInputWindows() updateInputWindows(...) updateInputFocusRequest(...) requestFocus(...) setFocusedWindow(...) nativeSetFocusedWindow(...) :Transaction:setFocusedWindow(...)
ViewRootImpl WindowInputEventReceiver InputChannel InputPublisher InputDispatcher InputManager SurfaceFlinger Scheduler HandlerMessageQueue ViewRootImpl WindowInputEventReceiver InputChannel InputPublisher InputDispatcher InputManager SurfaceFlinger Scheduler HandlerMessageQueue EventEntry::Type::FOCUS 中间省略了几个方法 InputTransport.cpp InputMessage::Type::FOCUS ViewRootImpl的内部类 onFrameSignal(...) commit(...) updateInputFlinger(...) setFocusedWindow(...) setFocusedWindow(...) enqueueFocusEventLocked(...) dispatchFocusLocked(...) dispatchEventLocked(...) startDispatchCycleLocked(...) publishFocusEvent(...) sendMessage(...) onFocusEvent(...) windowFocusChanged(bool hasFocus)

  1. setInputWindowsLocked(...)

    根据该方法的注释,该方法会从InputManagerService中被调用,但是从source code 中并未从InputManagerService中发现相关code。在InputDispatcher内部有调用。

  2. onWindowInfosChanged(...)

InputDispatcher DispatcherWindowListenerInputDispatcher WindowInfosListenerInvoker SurfaceFlinger Scheduler HandlerMessageQueue InputDispatcher DispatcherWindowListenerInputDispatcher WindowInfosListenerInvoker SurfaceFlinger Scheduler HandlerMessageQueue 继承了android::impl::MessageQueue SurfaceFlinger的initScheduler方法构造了Scheduler 对象 onFrameSignal(...) commit(...) updateInputFlinger(...) windowInfosChanged(gui::WindowInfosUpdate update, reportedListeners, forceImmediateCall) onWindowInfosChanged(const gui::WindowInfosUpdate& update) onWindowInfosChanged(update) setInputWindowsLocked(windowInfoHandles, displayId)

  1. setFocusedApplication
    Java层调用DisplayContent.java中的setFocusedApp(...) 方法可以更新Input 模块中的mFocucusedApplicationHandlesByDisplay中的值,在派发Key event时该变量会被用到,处理ANR相关逻辑。

InputDispatcher InputManagerService InputMonitor DisplayContent X InputDispatcher InputManagerService InputMonitor DisplayContent X ActivityRecord.java, ActivityTaskManagerService.java, Task.java, TaskFragment.java和WindowOrganizerController.java都会调用该方法 update mFocusedApplicationHandlesByDisplay setFocusedApp(ActivityRecord newFocus) setFocusedAppLw(newFocus) setFocusedApplication(displayId, applicationHandle) setFocusedApplication(displayId, applicationHandle) setFocusedApplicationLocked(displayId, applicationHandle)

打开input cycle的log

在'frameworks/native/services/inputfliger/dispatcher/DebugConfig.h'中介绍了打开不同input log的方式,下面是一个例子:

复制代码
adb shell setprop persist.log.tag.InputDispatcherDispatchCycle D

不过有次尝试不成功,没去查证为什么,deepseek 给了下面的命令,倒是起作用了:

复制代码
adb shell setprop persist.log.tag.InputDispatcher DEBUG
adb shell setprop persist.log.tag.InputDispatcher VERBOSE

打开input trace

复制代码
# 启用 INPUT 标签(0x80)
adb shell setprop debug.atrace.tags.enableflags 0x80

# 启动跟踪(需要 root 或 userdebug 设备)
adb shell cmd stats print-logs

# 开始记录跟踪数据(保存到 /data/local/tmp/input_trace)
adb shell atrace --async_start -b 4096 input

# 停止跟踪
adb shell atrace --async_stop > /data/local/tmp/input_trace

# 导出到本地
adb pull /data/local/tmp/input_trace .

# 使用 Perfetto 打开跟踪文件
https://ui.perfetto.dev/

InputDispatcher在派发motion 时,依照前后顺序-from front to back 确认window是否可以接受该event:

  1. window没有FLAG_NOT_TOUCHABLE flag
    FLAG_NOT_TOUCH_MODAL :允许将其window范围外的pointer event(比如,touch screen)派发给其后面的window。
    FLAG_NOT_FOCUSABLE :表明window不take input focus,用户发的key或者button event会给后面可以take input focus的window。这个flag会enable FLAG_NOT_TOUCH_MODAL
    FLAG_NOT_TOUCHABLE: 表明window不能接收touch events,以便将touch events留给它后面(z order)的window。
  2. window的touchable region包含当前motion event的坐标

3.Accessibility Focus

  • 由无障碍服务(如 TalkBack)独立管理。

  • 通过 AccessibilityNodeInfo 的isAccessibilityFocused() 判断。

  • 开发者可通过AccessibilityNodeInfo来让相应的View申请Accessibility focus

    复制代码
    accessibilityNodeInfo.performAction(ACTION_ACCESSIBILITY_FOCUS);

View.java内在处理该action时会调用requestAccessibilityFocus()方法来请求Accessibility focus。


frameworks/native/libs/gui/include/gui/WindowInfo.h

相关推荐
y = xⁿ18 分钟前
MySQL八股知识合集
android·mysql·adb
andr_gale1 小时前
04_rc文件语法规则
android·framework·aosp
祖国的好青年2 小时前
VS Code 搭建 React Native 开发环境(Windows 实战指南)
android·windows·react native·react.js
黄林晴2 小时前
警惕!AGP 9.2 别只改版本号,R8 规则与构建链路全线收紧
android·gradle
小米渣的逆袭3 小时前
Android ADB 完全使用指南
android·adb
儿歌八万首3 小时前
Jetpack Compose Canvas 进阶:结合 animateFloatAsState 让自定义图形动起来
android·动画·compose
zhangphil4 小时前
Android Page 3 Flow读sql数据库媒体文件,Kotlin
android·kotlin
神探小白牙4 小时前
echarts,3d堆叠图
android·3d·echarts
李白的天不白4 小时前
如何项目发布到github上
android·vue.js