浅谈FLAG_NOT_FOCUSABLE对窗口事件的影响

要深入理解为何设置 FLAG_NOT_FOCUSABLE 会导致 onBackPressed() 不被回调,但View点击事件却不受影响,需要从 Android 系统的源码层面分析事件分发的核心逻辑。以下是基于 Android 12 源码的详细分析:

1. 窗口焦点与事件分发的核心逻辑

当调用 getWindow().setFlags(FLAG_NOT_FOCUSABLE, ...) 时,窗口会被标记为不可聚焦。这一标记直接影响了窗口在 WindowManagerService (WMS) 中的事件分发策略。

源码分析:

  • WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE 的定义

    java 复制代码
    // frameworks/base/core/java/android/view/WindowManager.java
    public static final int FLAG_NOT_FOCUSABLE = 0x00000008;
  • WMS 对焦点窗口的判断

    WindowManagerService 中,处理按键事件时会优先检查目标窗口是否可聚焦:

    java

    ruby 复制代码
    // frameworks/base/services/core/java/com/android/server/wm/InputConsumerImpl.java
    private boolean canReceiveKeys(WindowState win) {
        return win.canReceiveKeys() && !win.isHiddenOrObscuredBehindDialog();
    }
    
    // frameworks/base/services/core/java/com/android/server/wm/WindowState.java
    boolean canReceiveKeys() {
        return !mAttrs.flags.contains(FLAG_NOT_FOCUSABLE) && 
               !mAttrs.flags.contains(FLAG_NOT_TOUCHABLE);
    }

    关键点 :若窗口被标记为 FLAG_NOT_FOCUSABLEcanReceiveKeys() 返回 false,按键事件将不会分发给该窗口。

2. 返回键事件的分发路径

onBackPressed() 是在 Activity 接收到返回键事件后被调用的。当窗口不可聚焦时,事件流程如下:

源码分析:

  1. InputManagerService (IMS) 接收物理按键

    java

    ruby 复制代码
    // frameworks/base/services/core/java/com/android/server/input/InputManagerService.java
    // IMS 接收到返回键事件后,通过 WindowManagerPolicy 决定分发目标
  2. WMS 过滤不可聚焦窗口

    java

    csharp 复制代码
    // frameworks/base/services/core/java/com/android/server/wm/InputConsumerImpl.java
    @Override
    public void dispatchKey(InputEvent event, int displayId, ...) {
        // 查找可接收按键的窗口
        WindowState win = mWmService.findKeyEventTargetLocked(...);
        if (win == null || !canReceiveKeys(win)) {
            // 若窗口不可聚焦,事件被系统拦截,不会传递给应用
            interceptKeyBeforeDispatching(win, event, ...);
            return;
        }
        // 否则分发给目标窗口
        dispatchKeyToWindow(win, event, ...);
    }
  3. Activity 接收按键事件的条件

    java

    csharp 复制代码
    // frameworks/base/core/java/android/app/Activity.java
    public boolean dispatchKeyEvent(KeyEvent event) {
        if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
            if (getApplicationInfo().targetSdkVersion
                    >= Build.VERSION_CODES.ECLAIR) {
                // 只有事件到达 Activity 才会调用 onBackPressed()
                event.startTracking();
                return true;
            } else {
                // 旧版本处理逻辑
                if (event.getAction() == KeyEvent.ACTION_UP) {
                    onBackPressed();
                    return true;
                }
                return super.dispatchKeyEvent(event);
            }
        }
        return super.dispatchKeyEvent(event);
    }

    结论 :由于 FLAG_NOT_FOCUSABLE 导致窗口无法接收按键事件,dispatchKeyEvent() 不会被调用,因此 onBackPressed() 也不会被触发。

3. 触摸事件的分发机制

触摸事件(如按钮点击)的分发路径与按键事件不同,它不依赖窗口焦点。

源码分析:

  1. WMS 对触摸事件的处理

    java

    csharp 复制代码
    // frameworks/base/services/core/java/com/android/server/wm/InputConsumerImpl.java
    @Override
    public void dispatchMotion(InputEvent event, int displayId, ...) {
        // 查找触摸目标窗口(基于坐标和可见性)
        WindowState win = mWmService.findTouchTargetWindowLocked(...);
        if (win != null) {
            // 即使窗口不可聚焦,只要可见且可触摸,仍会分发触摸事件
            dispatchMotionToWindow(win, event, ...);
        }
    }
  2. 触摸事件在 View 层级的分发

    java

    scss 复制代码
    // frameworks/base/core/java/android/view/ViewGroup.java
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        // ViewGroup 遍历子 View 寻找触摸目标
        if (!onFilterTouchEventForSecurity(ev)) {
            return false;
        }
        // 即使窗口不可聚焦,只要 View 可见且可点击,仍会处理触摸事件
        final int action = ev.getAction();
        if (action == MotionEvent.ACTION_DOWN) {
            // 重置触摸状态
            cancelAndClearTouchTargets(ev);
            resetTouchState();
        }
        // 后续逻辑处理触摸事件分发
    }
  3. 按钮点击事件的触发

    java

    java 复制代码
    // frameworks/base/core/java/android/widget/Button.java
    // Button 继承自 TextView,点击事件由 View 类处理
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE)
                || ((viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
                || ((viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE);
        
        if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
            // 处理触摸事件(DOWN、MOVE、UP)
            // 当检测到点击操作时,触发 onClickListener
        }
        return super.onTouchEvent(event);
    }

    结论 :触摸事件的分发只依赖窗口的可见性和 TOUCHABLE 标记,而非焦点状态。因此,即使窗口不可聚焦,按钮仍能正常响应点击事件。

4. 验证实验

通过以下代码可以验证上述分析:

java

typescript 复制代码
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    
    // 禁用窗口焦点
    getWindow().setFlags(
        WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
        WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
    );
    
    // 按钮点击事件(正常触发)
    findViewById(R.id.myButton).setOnClickListener(v -> {
        Log.d("MainActivity", "按钮点击事件触发");
    });
    
    // 重写 dispatchKeyEvent(不会被调用)
    @Override
    public boolean dispatchKeyEvent(KeyEvent event) {
        Log.d("MainActivity", "dispatchKeyEvent 被调用");
        if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
            Log.d("MainActivity", "返回键事件到达 Activity");
            return true;
        }
        return super.dispatchKeyEvent(event);
    }
}

@Override
public void onBackPressed() {
    // 设置 FLAG_NOT_FOCUSABLE 后,此方法不会被调用
    Log.d("MainActivity", "onBackPressed 未被调用");
    super.onBackPressed();
}

总结

事件类型 依赖窗口焦点 FLAG_NOT_FOCUSABLE 的影响 关键源码位置
按键事件 事件被 WMS 拦截,无法到达 Activity WindowState.canReceiveKeys()
触摸事件 事件正常分发到 View 层级 InputConsumerImpl.dispatchMotion()

核心区别 :按键事件需要窗口可聚焦才能接收,而触摸事件只需窗口可见且可触摸。因此,设置 FLAG_NOT_FOCUSABLE 后,onBackPressed() 不会被回调,但按钮点击事件仍能正常触发。

相关推荐
程序员JerrySUN2 小时前
Valgrind Memcheck 全解析教程:6个程序说明基础内存错误
android·java·linux·运维·开发语言·学习
经典19923 小时前
mysql 性能优化之Explain讲解
android·mysql·性能优化
Kiri霧5 小时前
Kotlin集合与空值
android·开发语言·kotlin
Glacien6 小时前
compose动画从底层基础到顶层高级应用(三)核心API之--Transition
android
亿刀7 小时前
为什么要学习Flutter编译过程
android·flutter
suqingxiao7 小时前
android虚拟机(AVD)报错The emulator process for AVD xxx has terminated
android
whysqwhw7 小时前
OkHttp Cookie 处理机制全解析
android
Evan_ZGYF丶7 小时前
【RK3576】【Android14】ADB工具说明与使用
android·驱动开发·android14·rk3576
幻雨様8 小时前
UE5多人MOBA+GAS 番外篇:移植Lyra的伤害特效(没用GameplayCue,因为我失败了┭┮﹏┭┮)
android·ue5
狂浪天涯8 小时前
Android 16 显示系统 | 从View 到屏幕系列 - 4 | GraphicBuffer & Gralloc
android·操作系统