Flutter中基于flutter_map实现OpenStreetMap瓦片可靠离线缓存的方案咨询及优化需求
Flutter中基于flutter_map实现OpenStreetMap瓦片可靠离线缓存的方案咨询及优化需求
我最近在开发一个Flutter应用,使用flutter_map组件加载OpenStreetMap的瓦片地图,核心需求是实现瓦片的本地持久化缓存,确保用户在离线状态下也能正常查看已经加载过的地图区域。
我的实现过程
初始版本的自定义TileProvider
最开始我写了一个基础的CachingTileProvider,逻辑是优先从本地文件读取瓦片,不存在的话就通过网络加载,同时在加载完成后把瓦片数据保存到本地:
import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter_map/flutter_map.dart'; import 'package:http/http.dart' as http; class CachingTileProvider extends TileProvider { final String cacheDir; CachingTileProvider(this.cacheDir); @override ImageProvider<Object> getImage(TileCoordinates coordinates, TileLayer options) { final url = options.tileProvider.getTileUrl(coordinates, options); final filePath = '$cacheDir/${coordinates.z}_${coordinates.x}_${coordinates.y}.png'; final file = File(filePath); if (file.existsSync()) { return FileImage(file); } else { return NetworkImage(url) ..resolve(const ImageConfiguration()).addListener( ImageStreamListener((imageInfo, _) async { final bytes = await _downloadImageBytes(url); if (bytes != null) { await file.create(recursive: true); await file.writeAsBytes(bytes); } }), ); } } Future<List<int>?> _downloadImageBytes(String url) async { try { final response = await http.get(Uri.parse(url)); if (response.statusCode == 200) { return response.bodyBytes; } } catch (_) {} return null; } }
但这个版本的缓存目录是手动指定的,跨平台兼容性和持久化都不太理想,所以我后来集成了path_provider包,把瓦片保存到系统标准的持久化目录中。
集成path_provider的优化版本
为了让缓存目录更符合平台规范且持久化,我改用path_provider获取系统目录,同时为了处理getImage方法中的异步逻辑,还写了一个FutureImageProvider辅助类:
import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter_map/flutter_map.dart'; import 'package:http/http.dart' as http; import 'package:path_provider/path_provider.dart'; class CachingTileProvider extends TileProvider { @override ImageProvider<Object> getImage(TileCoordinates coordinates, TileLayer options) { return FutureImageProvider(() async { final directory = await getTemporaryDirectory(); // 也考虑过用getApplicationSupportDirectory() final path = '${directory.path}/tiles/${coordinates.z}_${coordinates.x}_${coordinates.y}.png'; final file = File(path); if (await file.exists()) { return FileImage(file); } else { final url = options.tileProvider.getTileUrl(coordinates, options); try { final response = await http.get(Uri.parse(url)); if (response.statusCode == 200) { await file.create(recursive: true); await file.writeAsBytes(response.bodyBytes); return FileImage(file); } } catch (_) {} return NetworkImage(url); // 加载失败时的回退 } }); } } class FutureImageProvider extends ImageProvider<FutureImageProvider> { final Future<ImageProvider> Function() _providerLoader; FutureImageProvider(this._providerLoader); @override Future<FutureImageProvider> obtainKey(ImageConfiguration configuration) async => this; @override ImageStreamCompleter load(FutureImageProvider key, DecoderCallback decode) { return OneFrameImageStreamCompleter( _providerLoader().then((provider) => provider.load(provider, decode)), ); } }
目前遇到的问题
虽然能看到瓦片确实被保存到了缓存目录,且在APP运行期间部分瓦片能被复用,但还是存在几个关键问题:
- 缓存的瓦片在APP重启后不能被可靠复用,部分离线状态下的瓦片无法从本地加载
- 离线状态下,部分瓦片请求还是会尝试访问网络,没有完全优先使用本地缓存
- 不确定当前的实现是否足够健壮,能不能用于生产环境
我的疑问
想请教各位有没有更可靠或者官方推荐的flutter_map瓦片缓存方案?针对我当前的实现,有没有可以优化的点来提升缓存的可靠性和离线体验?如果有经过验证的生产级实现例子就更好了。
环境信息
- Flutter版本:3.x
- flutter_map版本:8.1.1
- 目标平台:Android
内容来源于stack exchange
相关产品推荐
相关产品推荐

