移动端大量图片加载性能优化

分享在处理相册应用时遇到的性能问题及解决方案,包括缩略图缓存、懒加载等技术实践。

问题背景

在开发相册清理应用时,需要展示用户手机中的所有照片。一个普通用户可能有几千甚至上万张照片,如何流畅地展示这些图片是一个很大的挑战。

最初的实现方案在照片数量超过1000张时就开始出现明显卡顿,内存占用也会飙升到几百MB。

性能瓶颈分析

通过Flutter DevTools分析,发现主要问题:

优化方案一:分页加载

使用photo_manager的分页API,按需加载图片资源:

class PhotoRepository {
  static const int pageSize = 100;
  
  Future<List<AssetEntity>> loadPage(int page) async {
    final albums = await PhotoManager.getAssetPathList(
      type: RequestType.image,
    );
    
    if (albums.isEmpty) return [];
    
    final album = albums.first;
    return await album.getAssetListPaged(
      page: page,
      size: pageSize,
    );
  }
}

配合ListView的滚动监听,实现无限滚动加载:

class PhotoListController extends GetxController {
  final photos = <AssetEntity>[].obs;
  int _currentPage = 0;
  bool _isLoading = false;
  bool _hasMore = true;
  
  Future<void> loadMore() async {
    if (_isLoading || !_hasMore) return;
    
    _isLoading = true;
    final newPhotos = await PhotoRepository.loadPage(_currentPage);
    
    if (newPhotos.length < PhotoRepository.pageSize) {
      _hasMore = false;
    }
    
    photos.addAll(newPhotos);
    _currentPage++;
    _isLoading = false;
  }
}

优化方案二:缩略图缓存

自定义缩略图缓存管理器,避免重复加载:

class ThumbnailCacheManager {
  static final _cache = LruCache<String, Uint8List>(maximumSize: 200);
  
  static Future<Uint8List?> getThumbnail(AssetEntity asset) async {
    final key = asset.id;
    
    // 先查缓存
    var thumb = _cache.get(key);
    if (thumb != null) return thumb;
    
    // 缓存未命中,加载缩略图
    thumb = await asset.thumbnailDataWithSize(
      const ThumbnailSize(200, 200),
      quality: 80,
    );
    
    if (thumb != null) {
      _cache.put(key, thumb);
    }
    
    return thumb;
  }
  
  static void clear() {
    _cache.clear();
  }
}

优化方案三:使用GridView.builder

GridView.builder只会构建可见区域的Widget,大大减少内存占用:

GridView.builder(
  gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
    crossAxisCount: 4,
    crossAxisSpacing: 2,
    mainAxisSpacing: 2,
  ),
  itemCount: controller.photos.length,
  itemBuilder: (context, index) {
    return PhotoThumbnail(
      asset: controller.photos[index],
      key: ValueKey(controller.photos[index].id),
    );
  },
)

优化方案四:图片组件优化

使用FadeInImage实现占位图和渐入效果,提升视觉体验:

class PhotoThumbnail extends StatelessWidget {
  final AssetEntity asset;
  
  const PhotoThumbnail({super.key, required this.asset});
  
  @override
  Widget build(BuildContext context) {
    return FutureBuilder<Uint8List?>(
      future: ThumbnailCacheManager.getThumbnail(asset),
      builder: (context, snapshot) {
        if (snapshot.hasData && snapshot.data != null) {
          return Image.memory(
            snapshot.data!,
            fit: BoxFit.cover,
            gaplessPlayback: true, // 防止闪烁
          );
        }
        return Container(color: Colors.grey[200]);
      },
    );
  }
}

优化方案五:预加载策略

在用户滚动时,提前加载即将显示的图片:

void onScroll(ScrollController controller) {
  final position = controller.position;
  final maxScroll = position.maxScrollExtent;
  final currentScroll = position.pixels;
  
  // 当滚动到80%时,预加载下一页
  if (currentScroll > maxScroll * 0.8) {
    loadMore();
  }
}

优化效果

经过以上优化后:

总结

移动端大量图片加载的核心优化思路:

  1. 按需加载,不要一次性加载所有数据
  2. 合理使用缓存,避免重复计算
  3. 使用Builder模式的列表组件
  4. 异步加载,不阻塞主线程
  5. 预加载提升用户体验