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

相关推荐
aaajj5 小时前
【Android】appops学习
android·学习
煤球王子6 小时前
学习记录:Android14中的Wifi_Direct(P2P)
android
找藉口是失败者的习惯6 小时前
【Android】Android 车机 + AI Agent 有没有搞头?
android·人工智能
用户004452159306 小时前
从 Gradle 到 Transform:Android 编译开发 Part 1 - Gradle 构建初探
android
用户004452159306 小时前
Android studio连接GitLab及使用(Windows版本)
android
zhouping@6 小时前
polarctf2025秋
android·web安全·php
0xSec笔记本挖呀瓦呀挖6 小时前
OpenClawWeComzh 实战:安卓 APK 分析与手机取证全自动化基础玩法
android·web安全·网络安全·智能手机·自动化·取证·电子数据取证
jolimark7 小时前
【mysql部署】在ubuntu22.04上安装和配置mysql教程
android·mysql·adb
YSoup7 小时前
MAT最新下载地址及Android内存泄露排查简单使用
android