浅谈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() 不会被回调,但按钮点击事件仍能正常触发。

相关推荐
Kapaseker1 小时前
一杯美式搞懂 Any、Unit、Nothing
android·kotlin
黄林晴1 小时前
你的 Android App 还没接 AI?Gemini API 接入全攻略
android
恋猫de小郭11 小时前
2026 Flutter VS React Native ,同时在 AI 时代 VS Native 开发,你没见过的版本
android·前端·flutter
冬奇Lab12 小时前
PowerManagerService(上):电源状态与WakeLock管理
android·源码阅读
BoomHe18 小时前
Now in Android 架构模式全面分析
android·android jetpack
二流小码农1 天前
鸿蒙开发:上传一张参考图片便可实现页面功能
android·ios·harmonyos
鹏程十八少1 天前
4.Android 30分钟手写一个简单版shadow, 从零理解shadow插件化零反射插件化原理
android·前端·面试
Kapaseker1 天前
一杯美式搞定 Kotlin 空安全
android·kotlin
三少爷的鞋1 天前
Android 协程时代,Handler 应该退休了吗?
android
火柴就是我2 天前
让我们实现一个更好看的内部阴影按钮
android·flutter