Skip to content

路径常见问题

Flutter 开发中,路径问题是新手最常遇到的坑之一。不同平台的文件系统差异很大,缓存、数据库、文件各自应该存放在哪里?本文逐一说明。

path_provider 核心目录

path_provider 是 Flutter 官方提供的路径获取插件,几乎所有存储方案都依赖它。

bash
flutter pub add path_provider

常用 API 一览

API说明是否会被系统清理
getTemporaryDirectory()临时目录,相当于缓存
getApplicationDocumentsDirectory()文档目录,用户可见数据
getApplicationSupportDirectory()应用支持目录,用户不可见数据
getExternalStorageDirectory()外部存储目录(仅 Android)
getDownloadsDirectory()下载目录(仅桌面端)
getLibraryDirectory()Library 目录(仅 iOS/macOS)

各平台实际路径对照表

APIAndroidiOS鸿蒙macOSWindowsLinux
临时目录/data/data/<包名>/cacheNSCachesDirectory<沙盒>/cache~/Library/Caches%TEMP%/tmp
文档目录/data/data/<包名>/app_flutterNSDocumentDirectory<沙盒>/files~/Documents%APPDATA%~/.local/share
支持目录/data/data/<包名>/filesNSApplicationSupportDirectory<沙盒>/preferences~/Library/Application Support%APPDATA%~/.local/share
  • Android 路径中的 <包名>pubspec.yaml 中的 nameandroid/app/build.gradle 中的 applicationId,例如 com.example.my_app
  • 鸿蒙路径中的 <沙盒> 指应用沙盒根目录,实际路径形如 /data/app/el2/100/base/<包名>/haps/entry/files/,开发者通常无需关心完整路径,通过 API 获取即可。

缓存文件路径

缓存文件应存放在 临时目录getTemporaryDirectory()),系统可在存储不足时自动清理。

dart
import 'package:path_provider/path_provider.dart';
import 'package:path/path.dart' as p;

Future<String> getCacheFilePath(String filename) async {
  final dir = await getTemporaryDirectory();
  return p.join(dir.path, filename);
}

各方案缓存路径对比

方案缓存路径获取方式实际位置(以 Android 为例)
path_providergetTemporaryDirectory()/data/data/<包名>/cache
shared_preferences自动管理,无需关心/data/data/<包名>/shared_prefs/
get_storage自动管理,无需关心/data/data/<包名>/app_flutter/
cached_network_image自动管理/data/data/<包名>/cache/libCachedImageData/

缓存注意事项

  • 临时目录会被系统清理:Android 在存储空间不足时可能自动清除缓存;iOS 更激进,系统可能在应用未运行时清理。不要在临时目录存放不可丢失的数据。
  • 缓存大小管理:建议自行实现缓存清理逻辑,避免无限增长。
dart
// 清理超过 7 天的缓存文件
Future<void> cleanExpiredCache() async {
  final dir = await getTemporaryDirectory();
  final now = DateTime.now();
  final expireDays = 7;

  if (await dir.exists()) {
    await for (final entity in dir.list()) {
      if (entity is File) {
        final stat = await entity.stat();
        if (now.difference(stat.modified).inDays > expireDays) {
          await entity.delete();
        }
      }
    }
  }
}

SQLite 数据库文件路径

sqflitegetDatabasesPath() 在不同平台上指向不同目录:

dart
import 'package:sqflite/sqflite.dart';
import 'package:path/path.dart' as p;

Future<Database> openMyDatabase() async {
  final dbPath = await getDatabasesPath();
  return openDatabase(
    p.join(dbPath, 'app.db'),
    version: 1,
    onCreate: (db, version) async {
      await db.execute('CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)');
    },
  );
}

getDatabasesPath() 各平台实际路径

平台路径说明
Android/data/data/<包名>/databases/应用私有目录,卸载即删除
iOSDocuments/ 目录下应用沙盒内,卸载即删除
鸿蒙<沙盒>/preferences/rdb/应用沙盒内,卸载即删除
macOS~/Library/Application Support/-
WindowsC:\Users\<用户>\AppData\Roaming\-
Linux~/.local/share/-

Android 外部存储

getDatabasesPath() 返回的是内部存储路径。如果需要将数据库放在外部存储(SD 卡),需手动指定路径,并申请 WRITE_EXTERNAL_STORAGE 权限(Android 10+ 有作用域存储限制,推荐使用内部存储)。

各平台路径差异详解

鸿蒙(HarmonyOS NEXT):应用沙盒

Flutter 从 3.22 版本开始支持鸿蒙平台。鸿蒙 NEXT 采用严格的应用沙盒机制,每个应用只能访问自己沙盒内的目录,与 Android 的内部存储概念类似但更加严格。

鸿蒙沙盒目录结构

目录路径(相对于沙盒根)说明对应 path_provider API
沙盒根目录/data/app/el2/100/base/<包名>/haps/entry/files/应用私有空间-
files<沙盒>/files/应用文件目录getApplicationDocumentsDirectory()
cache<沙盒>/cache/缓存目录,系统可清理getTemporaryDirectory()
preferences<沙盒>/preferences/偏好设置目录getApplicationSupportDirectory()
temp<沙盒>/temp/临时文件目录getTemporaryDirectory()
rdb<沙盒>/preferences/rdb/关系型数据库目录getDatabasesPath()

鸿蒙路径注意事项

  • 沙盒隔离:鸿蒙应用只能访问自己的沙盒目录,无法像 Android 那样通过权限访问其他应用的目录
  • 分布式文件:如需跨设备访问文件,需使用鸿蒙的分布式文件服务,不能直接读写路径
  • 用户目录:鸿蒙没有类似 Android 外部存储的"公共目录"概念,用户文件通过系统 Picker 选择
  • sqflite 兼容性:鸿蒙平台上 sqflitegetDatabasesPath() 返回 <沙盒>/preferences/rdb/,数据库文件默认存放在此
  • path_provider 支持:Flutter 鸿蒙分支已适配 path_provider,API 用法与其他平台一致
dart
import 'package:path_provider/path_provider.dart';
import 'package:flutter/foundation.dart' show kIsWeb;

// 跨平台获取文档目录(鸿蒙自动映射到沙盒 files 目录)
Future<String> getDataDir() async {
  final dir = await getApplicationDocumentsDirectory();
  return dir.path;
  // Android → /data/data/<包名>/app_flutter/
  // iOS     → NSDocumentDirectory
  // 鸿蒙    → <沙盒>/files/
}

鸿蒙与 Android 路径对比

场景Android鸿蒙
应用私有文件/data/data/<包名>/files/<沙盒>/files/
缓存/data/data/<包名>/cache/<沙盒>/cache/
数据库/data/data/<包名>/databases/<沙盒>/preferences/rdb/
偏好设置/data/data/<包名>/shared_prefs/<沙盒>/preferences/
外部存储/sdcard/Android/data/<包名>/不支持,使用沙盒或系统 Picker

鸿蒙没有"外部存储"概念,所有应用数据都在沙盒内。如需用户可见的文件,使用系统文件选择器让用户指定保存位置。

Android:内部存储 vs 外部存储

Android 的文件存储是最容易混淆的,搞清以下概念:

概念路径特点需要权限
内部存储/data/data/<包名>/应用私有,卸载删除
外部存储-应用专属/sdcard/Android/data/<包名>/卸载删除,用户可见Android 10+ 不需要
外部存储-公共目录/sdcard/Download/卸载不删除,所有应用共享Android 10+ 需 MediaStore API
dart
import 'package:path_provider/path_provider.dart';

// 内部存储(应用私有)
final internalDir = await getApplicationDocumentsDirectory();
// → /data/data/<包名>/app_flutter/

// 外部存储-应用专属目录(Android 独有)
final externalDir = await getExternalStorageDirectory();
// → /sdcard/Android/data/<包名>/files/

Android 10+ 作用域存储

从 Android 10 开始引入作用域存储(Scoped Storage),限制了对公共目录的直接访问。推荐做法:

  • 应用私有数据 → 内部存储(getApplicationDocumentsDirectory
  • 需要用户访问的文件 → 使用 MediaStore API 或系统文件选择器
  • 避免直接读写 /sdcard/ 根目录

iOS:沙盒机制

iOS 应用运行在沙盒中,每个应用只能访问自己的目录:

目录API说明
DocumentsgetApplicationDocumentsDirectory()用户数据,iTunes 备份
Library/CachesgetTemporaryDirectory()缓存,不备份,系统可清理
Library/Application SupportgetApplicationSupportDirectory()持久数据,iTunes 备份
tmp系统临时目录随时可清理

iOS 数据备份

  • 需要 iTunes/iCloud 备份的数据 → DocumentsApplication Support
  • 可重新下载的缓存 → Cachestmp
  • 大文件(如视频缓存)务必放 Caches,避免备份膨胀

Web:无本地文件系统

Web 平台没有传统文件系统path_provider 的大多数 API 不可用:

APIWeb 支持
getTemporaryDirectory()
getApplicationDocumentsDirectory()
getApplicationSupportDirectory()

Web 平台的替代方案:

dart
import 'package:flutter/foundation.dart' show kIsWeb;

if (kIsWeb) {
  // Web: 使用 SharedPreferences(底层是 localStorage)
  // 或使用 indexedDB(sqflite 的 Web 实现)
} else {
  // 移动端/桌面端: 正常使用文件系统
}

桌面端(Windows/macOS/Linux)

桌面端路径遵循各操作系统的惯例,path_provider 提供了完整支持。需要注意:

  • macOS 沙盒:如果启用了 App Sandbox,文件访问受限,需要在 com.apple.security.files.user-selected.* 声明权限
  • Windows:路径使用反斜杠 \,建议始终使用 path 包的 join() 拼接路径,避免手动拼接

常见坑与解决方案

1. 手动拼接路径导致跨平台问题

dart
// ❌ 错误:Windows 上分隔符是 \
final path = dir.path + '/data.json';

// ✅ 正确:使用 path 包的 join
import 'package:path/path.dart' as p;
final path = p.join(dir.path, 'data.json');

2. 目录不存在导致写入失败

dart
// ❌ 错误:直接写入可能不存在的子目录
final file = File(p.join(dir.path, 'cache', 'data.json'));
await file.writeAsString('{}');  // NoSuchDirectoryException

// ✅ 正确:先创建目录
final cacheDir = Directory(p.join(dir.path, 'cache'));
if (!await cacheDir.exists()) {
  await cacheDir.create(recursive: true);
}
final file = File(p.join(cacheDir.path, 'data.json'));
await file.writeAsString('{}');

3. Android 上获取外部存储路径返回 null

getExternalStorageDirectory() 在以下情况可能返回 null

  • 模拟器没有挂载 SD 卡
  • Android 10+ 作用域存储限制
  • 设备没有外部存储
dart
// 安全做法
final externalDir = await getExternalStorageDirectory();
if (externalDir != null) {
  // 使用外部存储
} else {
  // 回退到内部存储
  final internalDir = await getApplicationDocumentsDirectory();
}

4. iOS 缓存被清理后应用崩溃

getTemporaryDirectory() 的内容可能被系统清理,每次读取前务必检查文件是否存在:

dart
Future<String?> readCacheFile(String filename) async {
  final dir = await getTemporaryDirectory();
  final file = File(p.join(dir.path, filename));
  if (await file.exists()) {
    return await file.readAsString();
  }
  return null;  // 缓存已清理,重新从网络获取
}

5. 数据库文件和缓存文件混放

dart
// ❌ 错误:数据库文件放在临时目录
final dbPath = await getTemporaryDirectory();  // 可能被清理!

// ✅ 正确:使用 getDatabasesPath() 或应用文档目录
final dbPath = await getDatabasesPath();
// 或
final dbPath = (await getApplicationDocumentsDirectory()).path;

6. Web 平台直接使用文件 API 报错

dart
import 'package:flutter/foundation.dart' show kIsWeb;

Future<void> saveData(String key, String value) async {
  if (kIsWeb) {
    // Web: 使用 SharedPreferences 或 sembast_web
    final prefs = await SharedPreferences.getInstance();
    await prefs.setString(key, value);
  } else {
    // 移动端/桌面端: 使用文件系统
    final dir = await getApplicationDocumentsDirectory();
    final file = File(p.join(dir.path, '$key.dat'));
    await file.writeAsString(value);
  }
}

7. 鸿蒙平台使用 Android 路径逻辑报错

鸿蒙平台的文件系统与 Android 不同,不要在鸿蒙上硬编码 Android 路径:

dart
// ❌ 错误:硬编码 Android 路径
final dbPath = '/data/data/$packageName/databases/';

// ✅ 正确:使用 path_provider API,自动适配各平台
final dbPath = await getDatabasesPath();
// Android → /data/data/<包名>/databases/
// 鸿蒙    → <沙盒>/preferences/rdb/

鸿蒙平台特有的注意事项:

  • 不支持 getExternalStorageDirectory(),调用会返回 null
  • 不支持直接访问"公共目录"(如 Download、Pictures),需使用系统 Picker
  • SharedPreferences 底层映射到 <沙盒>/preferences/ 目录

路径选择速查表

数据类型推荐 API是否备份是否可被清理
用户文档、数据库getApplicationDocumentsDirectory()
应用配置、隐藏数据getApplicationSupportDirectory()
图片/文件缓存getTemporaryDirectory()
SQLite 数据库getDatabasesPath()
SharedPreferences自动管理
Android 外部文件getExternalStorageDirectory()

简单原则:不可丢失的数据放文档目录,可重新获取的放缓存目录,数据库用 getDatabasesPath()。鸿蒙没有外部存储概念,所有数据都在沙盒内。

基于 Flutter 官方文档整理