Flutter+Getx联系人获取性能优化:缓存与分批加载咨询
优化Flutter Getx联系人获取性能(1500+联系人场景)
当前使用flutter_contacts 1.1.9+2结合Getx实现联系人获取,但1500+联系人全量加载导致UI卡顿、性能下降,以下从缓存机制和分批加载两个方向给出优化方案。
一、缓存机制:避免重复全量拉取
缓存完全有效,核心思路是首次加载后将联系人数据持久化存储,后续启动优先读取缓存,同时后台静默同步最新联系人,既保证启动速度,又能获取最新数据。
实现步骤
- 用
shared_preferences做持久化存储(需将Contact对象序列化为JSON,无法直接存储复杂对象) - 记录缓存更新时间,通过
FlutterContacts.getContactsLastUpdated()对比系统联系人更新时间,判断是否需要刷新缓存 - 启动时先加载缓存展示,后台异步检查并更新缓存,更新完成后刷新UI
缓存实现代码
import 'dart:convert'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:flutter_contacts/flutter_contacts.dart'; import 'package:get/get.dart'; class PhoneBookController extends GetxController{ RxList<Contact> filteredContacts = <Contact>[].obs; RxList<Contact> allContacts = <Contact>[].obs; RxBool isLoading = true.obs; RxBool isRefreshing = false.obs; late SharedPreferences _prefs; final String _cacheKey = 'contacts_cache'; final String _cacheTimeKey = 'contacts_cache_time'; @override void onInit() { super.onInit(); _initPrefs(); _loadCachedContacts(); } Future<void> _initPrefs() async { _prefs = await SharedPreferences.getInstance(); } Future<void> _loadCachedContacts() async { try { final cachedData = _prefs.getString(_cacheKey); final cachedTime = _prefs.getInt(_cacheTimeKey); if (cachedData != null && cachedTime != null) { // 解析缓存的联系人数据 final List<dynamic> jsonList = jsonDecode(cachedData); final cachedContacts = jsonList.map((json) => Contact.fromJson(json)).toList(); allContacts.value = cachedContacts; filteredContacts.value = cachedContacts; } // 后台同步最新联系人 _syncLatestContacts(); } catch (e) { // 缓存加载失败,直接拉取最新数据 await fetchContacts(); } finally { isLoading.value = false; } } Future<void> _syncLatestContacts() async { isRefreshing.value = true; try { if (await FlutterContacts.requestPermission()) { final lastUpdated = await FlutterContacts.getContactsLastUpdated(); final cachedTime = _prefs.getInt(_cacheTimeKey); // 对比更新时间,判断是否需要拉取最新数据 if (cachedTime == null || lastUpdated.millisecondsSinceEpoch > cachedTime) { final contacts = await FlutterContacts.getContacts(withProperties: true, withPhoto: true); if (contacts.isNotEmpty) { allContacts.value = contacts; filteredContacts.value = contacts; // 更新缓存 final jsonList = contacts.map((contact) => contact.toJson()).toList(); await _prefs.setString(_cacheKey, jsonEncode(jsonList)); await _prefs.setInt(_cacheTimeKey, lastUpdated.millisecondsSinceEpoch); } } } } catch (e) { Get.snackbar('同步失败', '无法获取最新联系人'); } finally { isRefreshing.value = false; } } // 保留原有的fetchContacts方法作为主动刷新用 Future<void> fetchContacts() async { try { isLoading.value = true; if (await FlutterContacts.requestPermission()) { final contacts = await FlutterContacts.getContacts(withProperties: true, withPhoto: true); if (contacts.isNotEmpty) { allContacts.value = contacts; filteredContacts.value = contacts; // 更新缓存 final jsonList = contacts.map((contact) => contact.toJson()).toList(); await _prefs.setString(_cacheKey, jsonEncode(jsonList)); final lastUpdated = await FlutterContacts.getContactsLastUpdated(); await _prefs.setInt(_cacheTimeKey, lastUpdated.millisecondsSinceEpoch); } else { Get.snackbar('无联系人', '未找到任何联系人'); } } else { Get.snackbar('权限拒绝', '请授予联系人访问权限'); } } catch (e) { Get.snackbar('错误', '获取联系人失败'); } finally{ isLoading.value = false; } } }
二、分批加载:减少单次加载压力
利用flutter_contacts的offset和limit参数实现分批拉取,结合ListView滚动监听,滚动到底部时自动加载下一批,避免一次性加载大量联系人导致UI阻塞。
实现步骤
- 添加分页变量:当前偏移量
currentOffset、每页加载数量pageSize、是否还有更多数据hasMore - 实现分批拉取方法
fetchBatchContacts,每次拉取指定数量的联系人 - 监听ListView滚动事件,触发下一批加载
- 处理加载状态,避免重复请求
分批加载实现代码
import 'package:flutter_contacts/flutter_contacts.dart'; import 'package:get/get.dart'; class PhoneBookController extends GetxController{ RxList<Contact> filteredContacts = <Contact>[].obs; RxList<Contact> allContacts = <Contact>[].obs; RxBool isLoading = true.obs; RxBool isLoadingMore = false.obs; RxInt currentOffset = 0.obs; final int pageSize = 50; // 每页加载50条,可根据设备性能调整 RxBool hasMore = true.obs; @override void onInit() { super.onInit(); fetchBatchContacts(); } Future<void> fetchBatchContacts() async { if (isLoadingMore.value || !hasMore.value) return; isLoadingMore.value = true; try { if (await FlutterContacts.requestPermission()) { final contacts = await FlutterContacts.getContacts( withProperties: true, withPhoto: true, offset: currentOffset.value, limit: pageSize, ); if (contacts.isNotEmpty) { allContacts.addAll(contacts); filteredContacts.addAll(contacts); currentOffset.value += pageSize; // 判断是否还有更多数据 hasMore.value = contacts.length == pageSize; } else { hasMore.value = false; } } else { Get.snackbar('权限拒绝', '请授予联系人访问权限'); } } catch (e) { Get.snackbar('错误', '获取联系人失败'); } finally{ isLoading.value = false; isLoadingMore.value = false; } } // 重置分页,用于搜索或刷新场景 void resetPagination() { currentOffset.value = 0; hasMore.value = true; allContacts.clear(); filteredContacts.clear(); isLoading.value = true; fetchBatchContacts(); } }
UI层滚动监听示例
Obx(() { return ListView.builder( itemCount: controller.allContacts.length + (controller.hasMore.value ? 1 : 0), itemBuilder: (context, index) { if (index == controller.allContacts.length) { // 加载更多提示 return const Center(child: CircularProgressIndicator()); } final contact = controller.allContacts[index]; // 渲染联系人项 return ListTile( leading: contact.photo != null ? CircleAvatar(backgroundImage: MemoryImage(contact.photo!)) : const CircleAvatar(child: Icon(Icons.person)), title: Text(contact.displayName), subtitle: Text(contact.phones.isNotEmpty ? contact.phones.first.number : ''), ); }, controller: ScrollController()..addListener(() { // 滚动到底部时触发加载更多 final scrollController = ScrollController(); if (scrollController.position.pixels == scrollController.position.maxScrollExtent) { controller.fetchBatchContacts(); } }), ); })
三、综合优化建议
- 结合缓存与分批加载:首次启动先加载缓存的分批数据,后台同步最新联系人时也用分批方式,进一步降低性能消耗
- 延迟加载照片:照片属于大资源,可在列表项滚动到可见区域时再加载,避免一次性加载所有照片导致内存飙升
- 优化搜索逻辑:搜索时直接在已加载的联系人中过滤,避免重新拉取数据
内容的提问来源于stack exchange,提问作者Nammar Mahsud
相关产品推荐
相关产品推荐

