You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.07 08:29:29