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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.08 14:49:35