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

使用expo-contacts指定字段获取联系人时应用崩溃求助

Expo Contacts 指定字段获取联系人在iPhone12+ iOS17.5.1机型崩溃问题排查建议

问题概况

  • 环境:Expo SDK 51.0.0,使用ExpoGo运行
  • 核心现象:调用Contacts.getContactsAsync时,使用rawContacts: true参数可正常获取857条联系人数据;但改为指定fields数组(包含FirstName、LastName、PhoneNumbers、Emails)时,应用无报错日志直接崩溃
  • 机型差异:仅iPhone12及以上机型(iOS17.5.1)出现该问题,iPhone8同系统版本可正常运行
  • 权限配置:已按文档完成expo-contacts的权限配置,无权限相关报错

代码对比

正常运行代码

useEffect(() => {
    console.log("Checking current user...");
    if (auth.currentUser) {
      const uid = auth.currentUser.uid;
      fetchUserData(uid)
        .then((data) => {
          console.log("User data fetched:", data);
          setUser(data as User);
          fetchContacts();
        })
        .catch((error) => console.log("Error fetching user data:", error));
    }
  }, [auth.currentUser]);

  const fetchContacts = async () => {
    try {
      console.log("Requesting contacts permission...");
      const { status } = await Contacts.requestPermissionsAsync();
      console.log("Contacts permission status:", status);
      setPermissionStatus(status);
      if (status === "granted") {
        console.log("Fetching contacts...");
        const { data } = await Contacts.getContactsAsync({
          rawContacts: true,
        });
        console.log(`Contacts fetched: ${data.length} contacts.`);
        setContacts(data);
      } else {
        console.log("Contacts permission denied");
        setError("Contacts permission denied");
        Alert.alert("Permission Denied", "Contacts permission not granted.");
      }
    } catch (error: any) {
      console.error("Error fetching contacts:", error);
      setError(error.message);
      Alert.alert("Error", error.message);
    }(error: any) => {
      console.error("Error fetching contacts:", error);
      setError(error.message);
      Alert.alert("Error", error.message);
    }
  };

崩溃触发代码

useEffect(() => {
    console.log("Checking current user...");
    if (auth.currentUser) {
      const uid = auth.currentUser.uid;
      fetchUserData(uid)
        .then((data) => {
          console.log("User data fetched:", data);
          setUser(data as User);
          fetchContacts();
        })
        .catch((error) => console.log("Error fetching user data:", error));
    }
  }, [auth.currentUser]);

  const fetchContacts = async () => {
    try {
      console.log("Requesting contacts permission...");
      const { status } = await Contacts.requestPermissionsAsync();
      console.log("Contacts permission status:", status);
      setPermissionStatus(status);
      if (status === "granted") {
        console.log("Fetching contacts...");
        const { data } = await Contacts.getContactsAsync({
          fields: [Contacts.Fields.FirstName, Contacts.Fields.LastName, Contacts.Fields.PhoneNumbers, Contacts.Fields.Emails],
        });
        console.log(`Contacts fetched: ${data.length} contacts.`);
        setContacts(data);
      } else {
        console.log("Contacts permission denied");
        setError("Contacts permission denied");
        Alert.alert("Permission Denied", "Contacts permission not granted.");
      }
    } catch (error: any) {
      console.error("Error fetching contacts:", error);
      setError(error.message);
      Alert.alert("Error", error.message);
    }(error: any) => {
      console.error("Error fetching contacts:", error);
      setError(error.message);
      Alert.alert("Error", error.message);
    }
  };

日志对比

正常运行日志

LOG  Form submission initiated with values: {"email": "xxx.xxx@xxx.com", "password": "xxx"}
LOG  Set loading action dispatched with isLoading: true
LOG  Handling setLoading in reducer
LOG  Handling login request in reducer
LOG  Dispatching loginSuccess action...
LOG  Handling login success in reducer
LOG  Handling hideLoading in reducer
LOG  Checking current user...
LOG  User data fetched: {"consentReceiveEmailSMS": true, "email": "xxx.xxx@xxx.com", "emailVerified": true, "firstName": "aaa", "lastLoggedIn": {"nanoseconds": 960000000, "seconds": 1716267809}, "lastName": "bbb", "phoneNumber": "(ccc)ccc-cccc", "phoneNumberVerified": true, "registeredOn": "2023-09-24T18:30:41.071Z", "username": "xxx"}
LOG  Requesting contacts permission...
LOG  Contacts permission status: granted
LOG  Fetching contacts...
LOG  Contacts fetched: 857 contacts.

崩溃触发日志

LOG  Checking current user...
LOG  User data fetched: {"consentReceiveEmailSMS": true, "email": "xxx.xxx@xxx.com", "emailVerified": true, "firstName": "aaa", "lastLoggedIn": {"nanoseconds": 960000000, "seconds": 1716267809}, "lastName": "bbb", "phoneNumber": "(ccc)ccc-cccc", "phoneNumberVerified": true, "registeredOn": "2023-09-24T18:30:41.071Z", "username": "xxx"}
LOG  Requesting contacts permission...
LOG  Contacts permission status: granted
LOG  Fetching contacts..

app.json配置

{
  "expo": {
    "plugins": [
      [
        "expo-contacts",
        {
          "contactsPermission": "Allow xxx to access your contacts."
        }
      ]
    ],
    "name": "xxx",
    "slug": "xxx",
    "version": "1.0.0",
    "orientation": "portrait",
    "icon": "./assets/color_logo_with_background.png",
    "userInterfaceStyle": "light",
    "splash": {
      "image": "./assets/splash.png",
      "resizeMode": "contain",
      "backgroundColor": "#ffffff"
    },
    "assetBundlePatterns": ["**/*"],
    "ios": {
      "supportsTablet": true,
      "bundleIdentifier": "com.xxx.zzz",
      "infoPlist": {
        "NSContactsUsageDescription": "xxx"
      }
    },
    "android": {
      "adaptiveIcon": {
        "foregroundImage": "./assets/adaptive-icon.png",
        "backgroundColor": "#ffffff"
      },
      "permissions": ["READ_CONTACTS"]
    },
    "web": {
      "favicon": "./assets/favicon.png"
    },
    "extra": {
      "eas": {
        "projectId": "aaa"
      }
    },
    "sdkVersion": "51.0.0"
  }
}

排查与解决建议

  • 定位触发崩溃的字段:逐个移除fields数组中的字段,每次只保留一个字段测试,确认是否是某一个特定字段导致崩溃(比如PhoneNumbers或Emails在新机型上的处理逻辑有问题)
  • 增强错误捕获:在getContactsAsync调用后添加额外的Promise捕获,尝试捕获原生层的错误:
    const { data } = await Contacts.getContactsAsync({
      fields: [Contacts.Fields.FirstName, Contacts.Fields.LastName, Contacts.Fields.PhoneNumbers, Contacts.Fields.Emails],
    }).catch(err => {
      console.error("原生层捕获错误:", err);
      throw err;
    });
    
  • 测试小批量数据:添加limit参数,先获取10条联系人数据,看是否崩溃,排除是某条异常联系人数据(比如空值、特殊格式的手机号/邮箱)导致的问题
  • 升级依赖版本:检查expo-contacts是否有SDK51兼容的更新版本,部分机型特定bug可能已在新版本修复
  • 脱离ExpoGo测试:使用EAS构建自定义开发客户端,排除ExpoGo本身的兼容性问题
  • 手动处理字段提取:先用rawContacts: true获取全部数据,再在前端手动提取需要的字段,模拟指定fields的场景,验证是否是前端处理逻辑问题
  • 检查联系人数据异常:查看是否存在特殊联系人数据(比如无姓名、手机号格式异常),可以尝试在获取后先过滤异常数据再处理

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 19:49:50