在 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 解决。