React Native PDF组件加载/快速滑动后按返回键崩溃,报错java.lang.IllegalStateException: Already Closed
React Native PDF组件加载/快速滑动后按返回键崩溃,报错java.lang.IllegalStateException: Already Closed
这个问题我之前在项目里也踩过坑,本质是PDF的异步渲染任务和页面卸载的时序冲突——当你快速按返回键时,页面组件已经开始销毁,但PDF的后台渲染线程(比如加载、分页渲染的任务)还在运行,这时候线程去访问已经被关闭的PDF文档实例,就会抛出这个Already closed的异常。
问题原因拆解
从你贴的Logcat日志能看出来,错误来自pdfiumandroid的渲染线程:当RenderingHandler还在执行renderPageBitmap时,PdfPage已经被close了,线程还在尝试使用这个已关闭的实例,直接触发了崩溃。
针对性解决方案
结合你的现有代码,我给你几个具体的修改方向,按优先级从高到低:
1. 加组件挂载状态+卸载时清理PDF渲染任务
这是最直接的修复方式,核心是避免组件卸载后还执行异步操作,同时终止后台渲染任务:
- 用
useRef存一个isMounted标记,跟踪组件是否还在页面上; - 用
useRef获取Pdf组件的引用,在组件卸载时终止它的渲染任务; - 所有异步回调(比如
onLoadComplete里的dispatch、文件复制)都先判断isMounted,再执行。
修改后的代码片段(结合你的现有代码):
import { useEffect, useRef, useState } from 'react'; import Pdf from 'react-native-pdf'; const PdfViewScreen = ({ navigation, params, dispatch }) => { const [pdfLoading, setPdfLoading] = useState(true); // 标记组件是否还挂载 const isMounted = useRef(true); // 存Pdf组件的引用,用于后续清理 const pdfRef = useRef(null); // 组件卸载时的清理逻辑 useEffect(() => { return () => { isMounted.current = false; // 终止PDF的渲染任务:如果组件支持,调用内部方法;否则设置source为null强制停止 if (pdfRef.current) { pdfRef.current.setNativeProps({ source: null }); } }; }, []); // 修复onLoadComplete:组件卸载后不执行任何操作 const handleLoadComplete = (numberOfPages, path, {width, height}, tableContents) => { if (!isMounted.current) return; // 组件已销毁,直接返回 setPdfLoading(false); // 你原来的业务逻辑... if (params?.type === 'crop') { let data = { id: params?.id, cachedUrl: path }; dispatch(saveCachedPdfs(data)); } // ...其他type的逻辑 if (Platform.OS === 'ios') { if (!isMounted.current) return; // 再次判断,避免异步延迟 let fileName = params?.url.substring( params?.url.lastIndexOf('/') + 1, params?.url.lastIndexOf('.') ); ReactNativeBlobUtil.fs .cp( path, ReactNativeBlobUtil.fs.dirs.DocumentDir + `/${fileName}.pdf` ) .then(() => {}) .catch(() => {}); } }; // 修复onError:静默处理Already closed的异常 const handlePdfError = (error) => { if (error.message?.includes('Already closed')) { // 这种情况是组件卸载导致的,直接忽略,不提示也不崩溃 return; } showToast(0, t('PdfView.FailedToLoad')); }; return ( <View style={{flex: 1}}> <Pdf ref={pdfRef} // 绑定引用 trustAllCerts={false} onError={handlePdfError} source={source} onLoadComplete={handleLoadComplete} // 你原来的其他props... renderActivityIndicator={(progress: number) => { // 原来的加载指示器逻辑... }} style={pdfStyle} /> </View> ); };
2. 拦截返回动作,避免时序冲突
如果用的是React Navigation,可以监听页面的beforeRemove事件,在PDF还在加载时,先终止渲染再允许返回:
useEffect(() => { const unsubscribe = navigation.addListener('beforeRemove', (e) => { if (pdfLoading) { // 阻止默认返回,先清理PDF任务再跳转 e.preventDefault(); // 标记加载完成,终止任务 setPdfLoading(false); isMounted.current = false; if (pdfRef.current) { pdfRef.current.setNativeProps({ source: null }); } // 延迟200ms再执行返回,确保任务完全终止 setTimeout(() => { navigation.dispatch(e.data.action); }, 200); } }); return unsubscribe; }, [navigation, pdfLoading]);
3. 升级react-native-pdf到最新稳定版
这个bug在旧版本的react-native-pdf里比较常见,升级到最新版(比如v6.x以上),官方可能已经修复了部分时序问题,能减少这类崩溃的发生。
额外注意事项
- 快速滑动导致的崩溃,本质也是滑动触发的分页渲染任务和返回的卸载冲突,上面的
isMounted判断和卸载清理逻辑也能覆盖这种场景; - iOS的文件复制操作一定要加
isMounted判断,避免组件卸载后还操作文件系统,引发其他异常。
内容来源于stack exchange
相关产品推荐
相关产品推荐

