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

相关推荐
松仔log2 小时前
Java中级——组合和继承
android·java·开发语言
我命由我123455 小时前
Jetpack Compose - MaterialExpressiveTheme 与 MaterialTheme、ColorScheme
android·java·开发语言·java-ee·kotlin·android jetpack·android runtime
lilian2336 小时前
Harmony os 技术实战|拼豆制图06:收藏 ID、生成记录与重启恢复怎么不打架
android·java·数据库·harmonyos
2601_955759626 小时前
ClaudeAPI成本中心与业务标签设计指南
android·java·数据库
余衫马6 小时前
2026最新版!Android Studio 极速安装与配置指南
android·ide·android studio
AFinalStone6 小时前
Android 7系统异常问题排查(五)Framework层(下)—System Server崩溃
android·tombstone·系统异常
AFinalStone6 小时前
Android 7系统异常问题排查(八)系统追踪—Trace机制与性能诊断
android·系统异常
极客猴子10 小时前
Android录音转写频繁卡顿?多款APP长时间会议场景稳定性实测
android·人工智能·智能手机·飞书
Kapaseker10 小时前
Boolean 变量到底该怎么命名?
android·kotlin
AFinalStone10 小时前
Android 7系统异常问题排查(六)应用异常(上)—ANR机制全解
android·系统异常