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

Expo应用在TestFlight中启动即崩溃问题排查求助

Expo应用TestFlight启动崩溃但--no-dev模式正常的排查方案

问题描述

我的Expo应用在TestFlight中启动后立即崩溃,但使用--no-dev模式运行一切正常。此前应用存在SplashScreen相关的未捕获异常,将SplashScreen.preventAutoHideAsync();移至应用入口类外后,该异常似乎消失。目前已设置ErrorUtils.setGlobalHandler全局处理函数屏蔽所有错误,需要定位问题根源并修复。

崩溃报告

Incident Identifier: 27B3A01B-8E32-4564-8ABA-4B833E901F4B
Distributor ID:      com.apple.TestFlight
Hardware Model:      iPhone13,2
Process:             XXX [20508]
Path:                /private/var/containers/Bundle/Application/3CE6E33A-1730-4AF2-B773-744CB3184871/XXX.app/XXX
Identifier:          com.xxx
Version:             1.0.4 (5)
AppStoreTools:       15F31e
AppVariant:          1:iPhone13,2:17.4
Beta:                YES
Code Type:           ARM-64 (Native)
Role:                Foreground
Parent Process:      launchd [1]
Coalition:           com.xxx [2937]

Date/Time:           2024-07-15 01:46:02.3170 +0200
Launch Time:         2024-07-15 01:46:00.5863 +0200
OS Version:          iPhone OS 17.5.1 (21F90)
Release Type:        User
Baseband Version:    4.50.06
Report Version:      104

Exception Type:  EXC_CRASH (SIGABRT)
Exception Codes: 0x0000000000000000, 0x0000000000000000
Termination Reason: SIGNAL 6 Abort trap: 6
Terminating Process: XXX [20508]

Triggered by Thread:  19

Last Exception Backtrace:
0   CoreFoundation                  0x1a25ecf20 __exceptionPreprocess + 164 (NSException.m:249)
1   libobjc.A.dylib                 0x19a49a018 objc_exception_throw + 60 (objc-exception.mm:356)
2   CoreFoundation                  0x1a26eb580 -[NSException raise] + 16 (NSException.m:129)
3   XXX                             0x1003cc4ec ErrorRecovery.crash() + 2672 (ErrorRecovery.swift:286)
4   XXX                             0x1003cb1f4 ErrorRecovery.runNextTask() + 824 (ErrorRecovery.swift:199)
5   XXX                             0x1003cc8c4 closure #1 in closure #1 in ErrorRecovery.tryRelaunchFromCache() + 232 (ErrorRecovery.swift:242)
6   XXX                             0x1003b910c thunk for @escaping @callee_guaranteed @Sendable () -> () + 28 (<compiler-generated>:0)
7   libdispatch.dylib               0x1aa49013c _dispatch_call_block_and_release + 32 (init.c:1530)
8   libdispatch.dylib               0x1aa491dd4 _dispatch_client_callout + 20 (object.m:576)
9   libdispatch.dylib               0x1aa499400 _dispatch_lane_serial_drain + 748 (queue.c:3900)
10  libdispatch.dylib               0x1aa499f30 _dispatch_lane_invoke + 380 (queue.c:3991)
11  libdispatch.dylib               0x1aa4a4cb4 _dispatch_root_queue_drain_deferred_wlh + 288 (queue.c:6998)
12  libdispatch.dylib               0x1aa4a4528 _dispatch_workloop_worker_thread + 404 (queue.c:6592)
13  libsystem_pthread.dylib         0x1ff443934 _pthread_wqthread + 288 (pthread.c:2696)
14  libsystem_pthread.dylib         0x1ff4400cc start_wqthread + 8 (:-1)

应用入口代码

SplashScreen.preventAutoHideAsync();

export const Index = () => {
    useKeepAwake()

    const [fontsLoaded] = useFonts({
        'regular': require('../assets/fonts/Regular.ttf'),
        'bold': require('../assets/fonts/Bold.ttf'),
        'monospace': require('../assets/fonts/Regular.ttf'),
    });

    useEffect(() => {
        Appearance.setColorScheme("dark")
        const myErrorHandler:ErrorHandlerCallback = (e, isFatal) => {}
        ErrorUtils.setGlobalHandler(myErrorHandler)
    },[])

    const onLayoutRootView = useCallback(async () => {
        if (fontsLoaded) {
            await SplashScreen.hideAsync();
        }
    }, [fontsLoaded]);

    if (!fontsLoaded) {
        return null;
    }

    return (
        <View onLayout={onLayoutRootView} style={{ backgroundColor: "black", flex: 1, width: "100%" }}>
            <Bootstrap/>
        </View>
    );
}

排查与修复步骤

1. 解析崩溃报告核心信息

从崩溃栈可见,崩溃由Expo的ErrorRecovery.crash()触发,说明应用启动时出现了未被捕获的致命错误,当前的全局错误处理器未覆盖到原生层或异步任务的错误。

2. 恢复全局错误日志,获取真实错误

空的ErrorUtils.setGlobalHandler会掩盖JS层错误,临时修改它以捕获并输出错误:

const myErrorHandler:ErrorHandlerCallback = (e, isFatal) => {
  console.error('全局捕获错误:', e, '是否致命:', isFatal);
  // 若使用日志服务,此处可上报错误
}
ErrorUtils.setGlobalHandler(myErrorHandler)

之后通过Xcode将归档包安装到真机,查看控制台的实时错误输出,或在TestFlight中开启日志收集。

3. 检查SplashScreen处理逻辑

即使已移动preventAutoHideAsync,仍需验证:

  • 确保该方法在组件渲染前调用,无异步竞态问题
  • 给hideAsync添加错误捕获,避免隐藏失败导致的异常:
    const onLayoutRootView = useCallback(async () => {
      if (fontsLoaded) {
        try {
          await SplashScreen.hideAsync();
        } catch (e) {
          console.error('隐藏启动屏失败:', e);
        }
      }
    }, [fontsLoaded]);
    

4. 排查字体加载问题

  • 确认app.json的assetBundlePatterns包含assets/fonts/*,避免打包时遗漏字体文件
  • 手动加载字体并捕获错误,验证路径和字体文件有效性:
    import { Font } from 'expo-font';
    const [fontsLoaded, setFontsLoaded] = useState(false);
    
    useEffect(() => {
      const loadFonts = async () => {
        try {
          await Font.loadAsync({
            'regular': require('../assets/fonts/Regular.ttf'),
            'bold': require('../assets/fonts/Bold.ttf'),
            'monospace': require('../assets/fonts/Regular.ttf'),
          });
          setFontsLoaded(true);
        } catch (e) {
          console.error('字体加载失败:', e);
        }
      };
      loadFonts();
    }, []);
    

5. 检查Bootstrap组件初始化逻辑

Bootstrap组件的异步操作(如API请求、本地存储读取)可能存在未处理的Promise rejection,需为所有异步逻辑添加try/catch,避免触发致命错误。

6. 本地测试归档包

不要直接依赖TestFlight,用expo build:ios -t archive生成归档包,通过Xcode安装到真机,查看控制台日志,能更精准定位崩溃前的错误。

7. 其他建议

  • 移除空的全局错误屏蔽,保留错误上报逻辑,便于后续排查
  • 验证Expo SDK版本与iOS 17.5.1的兼容性,尝试升级到最新稳定版
  • 排查ErrorRecovery重启逻辑:若应用崩溃后重启再次失败,会触发crash,需找到首次崩溃的根源而非禁用该机制

内容的提问来源于stack exchange,提问作者Niclas

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 00:09:52