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

Flutter获取手机联系人时UI卡顿,请求优化方案

解决Flutter通讯录列表加载UI卡顿问题

问题根源分析

你的代码逻辑本身没问题,但当联系人数量较多时,主线程被耗时操作阻塞是卡顿的核心原因:

  • 联系人排序是同步操作,大数据量下会占用主线程时间
  • ListView.separated 启用 shrinkWrap: true 会一次性构建所有列表项,瞬间创建大量Widget导致UI卡死
  • 联系人转OnboardingUser的同步操作也可能占用主线程资源

具体优化方案

1. 将耗时操作移至后台线程

用Flutter的compute函数把排序、数据转换这类同步耗时操作放到后台isolate执行,避免阻塞主线程:

修改联系人获取逻辑:

import 'package:fast_contacts/fast_contacts.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:flutter/foundation.dart';

static Future<List<OnboardingUser>> getContacts() async {
  bool isGranted = await getPermission();

  if (isGranted) {
    try {
      List<ContactField> fields = [
        ContactField.displayName,
        ContactField.phoneNumbers,
      ];
      // 获取联系人(本身是异步,不阻塞主线程)
      List<Contact> contacts = await FastContacts.getAllContacts(fields: fields);
      // 后台线程排序
      List<Contact> sortedContacts = await compute(_sortContacts, contacts);
      // 后台线程批量转换为业务模型
      List<OnboardingUser> userList = await compute(_convertToUsers, sortedContacts);
      return userList;
    } catch (e) {
      log.d("$className: GetContacts(): Failed to get contacts");
    }
  }
  log.d("$className: GetContacts(): Failed to get contacts");
  return [];
}

// 供compute调用的排序函数(必须是顶级函数或静态函数)
List<Contact> _sortContacts(List<Contact> contacts) {
  contacts.sort((a, b) => a.displayName.compareTo(b.displayName));
  return contacts;
}

// 供compute调用的模型转换函数
List<OnboardingUser> _convertToUsers(List<Contact> contacts) {
  return contacts.map((contact) => OnboardingUser.fromContacts(contact)).toList();
}

2. 优化ListView构建逻辑

禁用shrinkWrap: true,让ListView采用懒加载模式,只构建当前可见区域的列表项:

修改UI构建代码中的ListView部分:

return ListView.separated(
    padding: EdgeInsets.zero,
    // 移除shrinkWrap和primary: false,恢复ListView的懒加载特性
    itemCount: snapshot.data!.length,
    separatorBuilder: (context, index) {
      return Divider(
        color: Colors.grey.shade300,
        thickness: 1,
      );
    },
    itemBuilder: (context, index) {
      OnboardingUser user = snapshot.data![index];
      return AddFriendLineItemWidget(
          user: user,
          isContact: true,
          isFriend: false,
          loggedInUser: currentUser);
    });

如果必须在嵌套滚动容器中使用列表,可以保留shrinkWrap但添加cacheExtent属性预加载少量内容,减少滚动时的卡顿:

shrinkWrap: true,
cacheExtent: 500, // 预加载可见区域上下500px的内容

3. 确保Future只执行一次

保持当前initState中初始化contacts的逻辑,避免在build方法中直接调用ContactsController.getContacts(),防止每次UI重建都重复请求联系人。

额外优化建议

  • 给AddFriendLineItemWidget添加const构造函数(如果允许),减少不必要的Widget重建
  • 如果联系人数量极大(上千条),可以考虑分页加载或分段加载,进一步降低单次UI渲染压力

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.13 11:33:18