React Native Android 16 适配:Modal 弹框返回键失效问题的分析与解决

在 Android 16(API 36)适配过程中,遇到了一个与返回键交互相关的问题:应用中部分弹框在 Android 16 设备上行为异常。当用户按系统返回键时,弹框会直接关闭,但预期行为是弹框保持显示。在 安卓老版本中功能正常。该问题涉及的弹框组件基于 React Native 的 Modal 组件实现,在 Android 16 上出现了不兼容。

尝试通过 BackHandler 监听并返回 true 来拦截事件,但无效。又尝试在页面层级使用 useFocusEffect + BackHandler 也无法拦截。

React Native 的 Modal 组件在 Android 平台上,无论 onRequestClose 回调中是否执行操作,系统都会默认在返回键按下时关闭 Modal。即使通过 BackHandler.addEventListener 注册了监听器并返回 true 来消费事件,Modal 依然会自行关闭。这应该是因为 Modal 在 Android 底层对应的是一个独立的窗口(Dialog 或 PopupWindow),它拥有自己的返回键处理优先级,高于 RN 层的事件监听。

Android 16 引入了"预测性返回手势"(Predictive Back Gesture),改变了返回键事件的传递和处理链路。新的 OnBackInvokedDispatcher API 赋予了系统更灵活的返回键分发能力,但同时也导致 RN 的 BackHandler 在某些场景下无法拦截事件,尤其是当弹框组件创建了独立的窗口时。因此,基于 Modal 的弹框在 Android 16 上无法通过 RN 层阻止其关闭。(在页面和弹框组件中添加日志,确认按返回键时 BackHandler 的回调确实被执行,但 Modal 的关闭行为仍然发生,说明 Modal 内部消费了返回键事件,且优先级高于 RN 监听。Modal 基于原生窗口Dialog 或 PopupWindow,返回键由系统直接处理,RN 的 BackHandler 无法阻止其关闭行为)

Android 16 的预测性返回机制:新机制进一步强化了系统层面对返回键的控制,使得 Modal 的关闭行为不可控。事件传递优先级:原生窗口 > RN 层,因此 RN 侧的拦截无法在 Modal 关闭之前生效。

解决方法,不使用 Modal 组件,改用 纯 JavaScript 实现的弹框组件,通过绝对定位的 View 覆盖全屏,并利用 Animated 实现底部滑入动画,同时使用 BackHandler 完全控制返回键行为。

布局结构:

javascript 复制代码
<View style={styles.overlay}>
    <TouchableOpacity style={styles.background} onPress={handleOverlayPress} />
    <Animated.View style={[styles.dialog, { transform: [{ translateY: slideAnim }] }]}>
        {/* 弹框内容 */}
    </Animated.View>
</View>
javascript 复制代码
const styles = StyleSheet.create({
    overlay: {
        position: 'absolute',
        top: 0, left: 0, right: 0, bottom: 0,
        justifyContent: 'flex-end',
        zIndex: 9999,
    },
    background: {
        position: 'absolute',
        top: 0, left: 0, right: 0, bottom: 0,
        backgroundColor: 'rgba(0,0,0,0.5)',
    },
    dialog: {
        backgroundColor: '#FFF',
        borderTopLeftRadius: 24,
        borderTopRightRadius: 24,
        paddingHorizontal: 24,
    },
});

返回键控制:

javascript 复制代码
useEffect(() => {
    const backHandler = BackHandler.addEventListener('hardwareBackPress', () => {
        if (visible && !closeOnBackPress) {
            // 不关闭弹框,仅消费事件
            return true;
        }
        if (visible && closeOnBackPress) {
            // 关闭弹框,并消费事件(阻止页面退出)
            setVisible(false);
            return true;
        }
        return false;
    });
    return () => backHandler.remove();
}, [visible, closeOnBackPress]);

隐藏/显示动画:

javascript 复制代码
const show = () => {
    setVisible(true);
    Animated.timing(slideAnim, {
        toValue: 0,
        duration: 200,
        useNativeDriver: true,
    }).start();
};

const hide = () => {
    Animated.timing(slideAnim, {
        toValue: screenHeight,
        duration: 200,
        useNativeDriver: true,
    }).start(() => setVisible(false));
};

属性控制,新增 closeOnBackPress 属性,允许调用方决定是否允许返回键关闭弹框:

javascript 复制代码
<AppActionSheet
    closeOnBackPress={false}   // 按返回键不关闭
    closeOnOutsideTouch={false} // 点击背景不关闭
    // ... 
/>

ok. React Native 应用中基于 Modal 的弹框在 Android 16 上返回键失效的问题,通过自定义 View + BackHandler 解决。