Flutter OpenHarmony 三方库 path_provider 文件路径获取适配详解

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
本文基于 Flutter 3.27.5 与 HarmonyOS 6.0 环境,深入讲解 path_provider 三方库在 OpenHarmony 平台的适配使用方法,带你全面掌握临时目录、应用文档目录、缓存目录、外部存储等各类文件路径的获取方式。


一、path_provider 库简介

path_provider 是 Flutter 官方提供的文件路径获取插件,用于获取设备上各种常用目录的路径。无论是临时文件、应用文档、缓存文件,还是外部存储,path_provider 都能提供统一且跨平台的路径获取方式。

path_provider 核心特点

特点 说明
跨平台兼容 支持 Android、iOS、macOS、Windows、Linux、Web、OpenHarmony
统一接口 一套 API 适配所有平台
类型安全 返回 Directory 对象而非字符串
简单易用 直接调用函数即可获取路径
官方维护 Flutter 官方插件,稳定可靠

支持的目录类型

目录类型 说明 适用场景
临时目录 系统可随时清理的临时文件 缓存下载文件
应用支持目录 应用支持文件,用户不可见 数据库、配置文件
应用文档目录 用户生成的文档文件 用户创建的文件
应用缓存目录 应用特定的缓存文件 图片缓存、数据缓存
外部存储目录 外部存储(如 SD 卡) 大文件存储
外部缓存目录 外部存储上的缓存目录 大型缓存文件
下载目录 下载文件目录 下载管理器

使用场景

  • 文件下载与缓存
  • 数据库存储
  • 图片/视频缓存
  • 日志文件存储
  • 用户数据持久化
  • 应用配置存储

二、OpenHarmony 适配版本

2.1 环境说明

组件 版本
Flutter 3.27.5
HarmonyOS 6.0
path_provider 2.1.5 (OpenHarmony 适配版本)

2.2 引入方式

pubspec.yaml 文件中添加以下依赖配置:

yaml 复制代码
dependencies:
  path_provider:
    git:
      url: https://atomgit.com/openharmony-sig/flutter_packages.git
      path: packages/path_provider/path_provider
      ref: br_path_provider-v2.1.5_ohos

说明: 必须使用 OpenHarmony 适配版本,通过 path_provider_ohos 作为平台实现自动注册。不能使用官方 pub.dev 的版本,因为官方版本不包含 OpenHarmony 平台支持。

2.3 权限配置

在 OpenHarmony 平台,访问外部存储需要申请对应权限。在 ohos/entry/src/main/module.json5 中添加:

json 复制代码
"requestPermissions": [
  {
    "name": "ohos.permission.READ_MEDIA",
    "reason": "$string:read_media_reason",
    "usedScene": {
      "abilities": ["EntryAbility"],
      "when": "inuse"
    }
  },
  {
    "name": "ohos.permission.WRITE_MEDIA",
    "reason": "$string:write_media_reason",
    "usedScene": {
      "abilities": ["EntryAbility"],
      "when": "inuse"
    }
  }
]

ohos/entry/src/main/resources/base/element/string.json 中添加:

json 复制代码
{
  "name": "read_media_reason",
  "value": "读取媒体文件"
},
{
  "name": "write_media_reason",
  "value": "写入媒体文件"
}

⚠️ 重要说明: 在 OpenHarmony 平台上,READ_MEDIAIMAGEWRITE_MEDIAIMAGE 权限无法通过常规方式申请。如果需要访问媒体文件,建议使用应用沙箱路径(如 getApplicationDocumentsDirectorygetTemporaryDirectory),这些路径不需要额外权限。


三、核心 API 讲解

3.1 getTemporaryDirectory - 获取临时目录

获取设备上的临时目录,该目录中的文件可能会被系统随时清理。

dart 复制代码
Future<Directory> getTemporaryDirectory()

返回值: Directory 对象,表示临时目录。

说明:

  • 该目录用于存储临时缓存文件
  • 系统可能会在存储空间不足时清理该目录
  • 不应存储重要数据
  • 在 iOS/macOS 上对应 NSCachesDirectory
  • 在 Android 上对应 Context.getCacheDir
  • 在 OpenHarmony 上对应应用缓存目录

使用示例:

dart 复制代码
import 'package:path_provider/path_provider.dart';

final tempDir = await getTemporaryDirectory();
print('临时目录: ${tempDir.path}');

// 创建临时文件
final tempFile = File('${tempDir.path}/temp_data.txt');
await tempFile.writeAsString('临时数据');

3.2 getApplicationSupportDirectory - 获取应用支持目录

获取应用支持文件目录,用于存储应用的支持文件。

dart 复制代码
Future<Directory> getApplicationSupportDirectory()

返回值: Directory 对象,表示应用支持目录。

说明:

  • 如果目录不存在,会自动创建
  • 用于存储用户不可见的支持文件
  • 不应存储用户数据文件
  • 在 iOS/macOS 上对应 NSApplicationSupportDirectory
  • 在 Android 上对应 Flutter 引擎的 PathUtils.getFilesDir
  • 在 OpenHarmony 上对应应用文件目录

使用示例:

dart 复制代码
final supportDir = await getApplicationSupportDirectory();
print('应用支持目录: ${supportDir.path}');

// 存储数据库文件
final dbFile = File('${supportDir.path}/app_database.db');

3.3 getApplicationDocumentsDirectory - 获取应用文档目录

获取应用文档目录,用于存储用户生成的数据。

dart 复制代码
Future<Directory> getApplicationDocumentsDirectory()

返回值: Directory 对象,表示应用文档目录。

说明:

  • 用于存储用户生成的或无法重新创建的数据
  • 在 iOS/macOS 上对应 NSDocumentDirectory
  • 在 Android 上对应 Flutter 引擎的 PathUtils.getDataDirectory
  • 在 OpenHarmony 上对应应用文档目录
  • 推荐用于持久化存储用户数据

使用示例:

dart 复制代码
final docDir = await getApplicationDocumentsDirectory();
print('应用文档目录: ${docDir.path}');

// 存储用户文档
final userFile = File('${docDir.path}/user_document.txt');
await userFile.writeAsString('用户创建的内容');

3.4 getApplicationCacheDirectory - 获取应用缓存目录

获取应用特定的缓存目录。

dart 复制代码
Future<Directory> getApplicationCacheDirectory()

返回值: Directory 对象,表示应用缓存目录。

说明:

  • 如果目录不存在,会自动创建
  • 用于存储应用特定的缓存文件
  • 比临时目录更适合应用级缓存
  • 在 OpenHarmony 上对应应用缓存目录

使用示例:

dart 复制代码
final cacheDir = await getApplicationCacheDirectory();
print('应用缓存目录: ${cacheDir.path}');

// 缓存图片
final imageCache = File('${cacheDir.path}/image_001.jpg');

3.5 getLibraryDirectory - 获取库目录

获取库目录,用于存储持久化、备份且用户不可见的文件。

dart 复制代码
Future<Directory> getLibraryDirectory()

返回值: Directory 对象,表示库目录。

说明:

  • 在 iOS/macOS 上对应 NSApplicationSupportDirectory
  • 在 OpenHarmony 上不支持,会抛出 UnsupportedError
  • 在 Android 上也不支持

使用示例:

dart 复制代码
try {
  final libraryDir = await getLibraryDirectory();
  print('库目录: ${libraryDir.path}');
} on UnsupportedError catch (e) {
  print('当前平台不支持获取库目录: $e');
}

3.6 getExternalStorageDirectory - 获取外部存储目录

获取外部存储目录(如 SD 卡)。

dart 复制代码
Future<Directory?> getExternalStorageDirectory()

返回值: Directory? 对象,表示外部存储目录,如果不可用则返回 null

说明:

  • 在 Android 上对应 getExternalFilesDir(null)
  • 在 OpenHarmony 上可能返回 null 或外部存储路径
  • 在 iOS 上不支持(应用沙箱限制)
  • 需要外部存储权限

使用示例:

dart 复制代码
final externalDir = await getExternalStorageDirectory();
if (externalDir != null) {
  print('外部存储目录: ${externalDir.path}');
} else {
  print('外部存储不可用');
}

3.7 getExternalCacheDirectories - 获取外部缓存目录列表

获取外部存储上的所有缓存目录。

dart 复制代码
Future<List<Directory>?> getExternalCacheDirectories()

返回值: List<Directory>? 对象列表,表示外部缓存目录。

说明:

  • 在 Android 上对应 Context.getExternalCacheDirs()
  • 可能返回多个目录(如内部存储和 SD 卡)
  • 在 OpenHarmony 上可能返回空列表或 null

使用示例:

dart 复制代码
final externalCaches = await getExternalCacheDirectories();
if (externalCaches != null && externalCaches.isNotEmpty) {
  for (final dir in externalCaches) {
    print('外部缓存目录: ${dir.path}');
  }
}

3.8 getExternalStorageDirectories - 获取外部存储目录列表

获取外部存储上的所有数据目录。

dart 复制代码
Future<List<Directory>?> getExternalStorageDirectories({
  StorageDirectory? type,
})

参数说明:

参数 类型 必填 默认值 说明
type StorageDirectory? null 存储目录类型

返回值: List<Directory>? 对象列表,表示外部存储目录。

StorageDirectory 枚举说明:

说明
music 音乐文件目录
podcasts 播客文件目录
ringtones 铃声文件目录
alarms 闹钟声音目录
notifications 通知声音目录
pictures 图片文件目录
movies 电影文件目录
downloads 下载文件目录
dcim 相机照片目录
documents 文档文件目录

使用示例:

dart 复制代码
// 获取图片目录
final pictureDirs = await getExternalStorageDirectories(
  type: StorageDirectory.pictures,
);
if (pictureDirs != null && pictureDirs.isNotEmpty) {
  print('图片目录: ${pictureDirs.first.path}');
}

// 获取下载目录
final downloadDirs = await getExternalStorageDirectories(
  type: StorageDirectory.downloads,
);

3.9 getDownloadsDirectory - 获取下载目录

获取下载文件目录。

dart 复制代码
Future<Directory?> getDownloadsDirectory()

返回值: Directory? 对象,表示下载目录。

说明:

  • 返回的目录不一定存在,使用前应验证
  • 在桌面操作系统上更常用
  • 在 OpenHarmony 上可能返回 null

使用示例:

dart 复制代码
final downloadsDir = await getDownloadsDirectory();
if (downloadsDir != null) {
  print('下载目录: ${downloadsDir.path}');
} else {
  print('下载目录不可用');
}

3.10 MissingPlatformDirectoryException 异常

当无法获取目录时抛出的异常。

dart 复制代码
class MissingPlatformDirectoryException implements Exception {
  final String message;
  final Object? details;
}

属性说明:

属性 类型 说明
message String 错误消息
details Object? 详细信息(如平台错误对象)

使用示例:

dart 复制代码
try {
  final docDir = await getApplicationDocumentsDirectory();
} on MissingPlatformDirectoryException catch (e) {
  print('获取目录失败: ${e.message}');
  print('详细信息: ${e.details}');
}

四、应用级别完整代码:文件管理器助手

以下是一个完整的文件管理器助手应用,包含以下功能:

  • 查看所有可用目录路径
  • 创建和管理文件
  • 文件读写操作
  • 目录浏览
  • 文件大小显示
  • 文件删除功能
dart 复制代码
import 'dart:io';

import 'package:flutter/material.dart';
import 'package:path_provider/path_provider.dart';

void main() {
  runApp(const FileManagerApp());
}

class FileManagerApp extends StatelessWidget {
  const FileManagerApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: '文件管理器助手',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(
          seedColor: const Color(0xFF4CAF50),
          brightness: Brightness.light,
        ),
        useMaterial3: true,
        appBarTheme: const AppBarTheme(
          centerTitle: true,
          elevation: 0,
        ),
      ),
      home: const FileManagerHomePage(),
    );
  }
}

class FileManagerHomePage extends StatefulWidget {
  const FileManagerHomePage({super.key});

  @override
  State<FileManagerHomePage> createState() => _FileManagerHomePageState();
}

class _FileManagerHomePageState extends State<FileManagerHomePage> {
  Map<String, Directory?> _directories = {};
  List<FileItem> _currentFiles = [];
  Directory? _currentDirectory;
  bool _isLoading = false;

  @override
  void initState() {
    super.initState();
    _loadAllDirectories();
  }

  Future<void> _loadAllDirectories() async {
    setState(() => _isLoading = true);

    try {
      final tempDir = await getTemporaryDirectory();
      _directories['临时目录'] = tempDir;
    } catch (e) {
      _directories['临时目录'] = null;
    }

    try {
      final supportDir = await getApplicationSupportDirectory();
      _directories['应用支持目录'] = supportDir;
    } catch (e) {
      _directories['应用支持目录'] = null;
    }

    try {
      final docDir = await getApplicationDocumentsDirectory();
      _directories['应用文档目录'] = docDir;
    } catch (e) {
      _directories['应用文档目录'] = null;
    }

    try {
      final cacheDir = await getApplicationCacheDirectory();
      _directories['应用缓存目录'] = cacheDir;
    } catch (e) {
      _directories['应用缓存目录'] = null;
    }

    try {
      final externalDir = await getExternalStorageDirectory();
      _directories['外部存储目录'] = externalDir;
    } catch (e) {
      _directories['外部存储目录'] = null;
    }

    try {
      final downloadsDir = await getDownloadsDirectory();
      _directories['下载目录'] = downloadsDir;
    } catch (e) {
      _directories['下载目录'] = null;
    }

    if (mounted) {
      setState(() => _isLoading = false);
    }
  }

  Future<void> _openDirectory(Directory dir) async {
    setState(() {
      _currentDirectory = dir;
      _isLoading = true;
    });

    try {
      final entities = await dir.list().toList();
      final files = <FileItem>[];

      for (final entity in entities) {
        if (entity is File) {
          final stat = await entity.stat();
          files.add(FileItem(
            name: entity.path.split('/').last,
            path: entity.path,
            isDirectory: false,
            size: stat.size,
            modified: stat.modified,
          ));
        } else if (entity is Directory) {
          files.add(FileItem(
            name: entity.path.split('/').last,
            path: entity.path,
            isDirectory: true,
            size: 0,
            modified: DateTime.now(),
          ));
        }
      }

      if (mounted) {
        setState(() {
          _currentFiles = files;
          _isLoading = false;
        });
      }
    } catch (e) {
      if (mounted) {
        setState(() => _isLoading = false);
        _showError('读取目录失败: $e');
      }
    }
  }

  Future<void> _createFile() async {
    if (_currentDirectory == null) return;

    final controller = TextEditingController();
    final result = await showDialog<String>(
      context: context,
      builder: (context) => AlertDialog(
        title: const Text('创建文件'),
        content: TextField(
          controller: controller,
          decoration: const InputDecoration(
            hintText: '输入文件名(如 test.txt)',
          ),
          autofocus: true,
        ),
        actions: [
          TextButton(
            onPressed: () => Navigator.pop(context),
            child: const Text('取消'),
          ),
          TextButton(
            onPressed: () => Navigator.pop(context, controller.text),
            child: const Text('创建'),
          ),
        ],
      ),
    );

    if (result != null && result.isNotEmpty) {
      try {
        final file = File('${_currentDirectory!.path}/$result');
        await file.writeAsString('这是创建的文件内容\n创建时间: ${DateTime.now()}');
        _showSuccess('文件创建成功');
        _openDirectory(_currentDirectory!);
      } catch (e) {
        _showError('创建文件失败: $e');
      }
    }
  }

  Future<void> _createDirectory() async {
    if (_currentDirectory == null) return;

    final controller = TextEditingController();
    final result = await showDialog<String>(
      context: context,
      builder: (context) => AlertDialog(
        title: const Text('创建文件夹'),
        content: TextField(
          controller: controller,
          decoration: const InputDecoration(
            hintText: '输入文件夹名',
          ),
          autofocus: true,
        ),
        actions: [
          TextButton(
            onPressed: () => Navigator.pop(context),
            child: const Text('取消'),
          ),
          TextButton(
            onPressed: () => Navigator.pop(context, controller.text),
            child: const Text('创建'),
          ),
        ],
      ),
    );

    if (result != null && result.isNotEmpty) {
      try {
        final dir = Directory('${_currentDirectory!.path}/$result');
        await dir.create();
        _showSuccess('文件夹创建成功');
        _openDirectory(_currentDirectory!);
      } catch (e) {
        _showError('创建文件夹失败: $e');
      }
    }
  }

  Future<void> _deleteFile(FileItem file) async {
    final confirmed = await showDialog<bool>(
      context: context,
      builder: (context) => AlertDialog(
        title: const Text('确认删除'),
        content: Text('确定要删除 "${file.name}" 吗?'),
        actions: [
          TextButton(
            onPressed: () => Navigator.pop(context, false),
            child: const Text('取消'),
          ),
          TextButton(
            onPressed: () => Navigator.pop(context, true),
            child: const Text('删除', style: TextStyle(color: Colors.red)),
          ),
        ],
      ),
    );

    if (confirmed == true) {
      try {
        if (file.isDirectory) {
          await Directory(file.path).delete(recursive: true);
        } else {
          await File(file.path).delete();
        }
        _showSuccess('删除成功');
        if (_currentDirectory != null) {
          _openDirectory(_currentDirectory!);
        }
      } catch (e) {
        _showError('删除失败: $e');
      }
    }
  }

  Future<void> _viewFile(FileItem file) async {
    try {
      final content = await File(file.path).readAsString();
      if (mounted) {
        showDialog(
          context: context,
          builder: (context) => AlertDialog(
            title: Text(file.name),
            content: SizedBox(
              width: double.maxFinite,
              child: SingleChildScrollView(
                child: Text(content),
              ),
            ),
            actions: [
              TextButton(
                onPressed: () => Navigator.pop(context),
                child: const Text('关闭'),
              ),
            ],
          ),
        );
      }
    } catch (e) {
      _showError('读取文件失败: $e');
    }
  }

  void _showError(String message) {
    if (!mounted) return;
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(
        content: Text(message),
        backgroundColor: Colors.red,
      ),
    );
  }

  void _showSuccess(String message) {
    if (!mounted) return;
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(
        content: Text(message),
        backgroundColor: Colors.green,
      ),
    );
  }

  String _formatFileSize(int bytes) {
    if (bytes < 1024) return '$bytes B';
    if (bytes < 1024 * 1024) return '${(bytes / 1024).toStringAsFixed(1)} KB';
    if (bytes < 1024 * 1024 * 1024) {
      return '${(bytes / (1024 * 1024)).toStringAsFixed(1)} MB';
    }
    return '${(bytes / (1024 * 1024 * 1024)).toStringAsFixed(1)} GB';
  }

  String _formatDateTime(DateTime dt) {
    return '${dt.year}-${dt.month.toString().padLeft(2, '0')}-${dt.day.toString().padLeft(2, '0')} ${dt.hour.toString().padLeft(2, '0')}:${dt.minute.toString().padLeft(2, '0')}';
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(
          _currentDirectory == null
              ? '文件管理器助手'
              : _currentDirectory!.path.split('/').last,
          style: const TextStyle(fontWeight: FontWeight.bold),
        ),
        backgroundColor: const Color(0xFF4CAF50),
        foregroundColor: Colors.white,
        leading: _currentDirectory != null
            ? IconButton(
                icon: const Icon(Icons.arrow_back),
                onPressed: () {
                  setState(() {
                    _currentDirectory = null;
                    _currentFiles = [];
                  });
                },
              )
            : null,
      ),
      body: _isLoading
          ? const Center(child: CircularProgressIndicator())
          : _currentDirectory == null
              ? _buildDirectoryList()
              : _buildFileList(),
      floatingActionButton: _currentDirectory != null
          ? Row(
              mainAxisSize: MainAxisSize.min,
              children: [
                FloatingActionButton(
                  heroTag: 'file',
                  onPressed: _createFile,
                  backgroundColor: const Color(0xFF4CAF50),
                  child: const Icon(Icons.note_add),
                ),
                const SizedBox(width: 12),
                FloatingActionButton(
                  heroTag: 'folder',
                  onPressed: _createDirectory,
                  backgroundColor: const Color(0xFF2196F3),
                  child: const Icon(Icons.create_new_folder),
                ),
              ],
            )
          : null,
    );
  }

  Widget _buildDirectoryList() {
    return ListView(
      padding: const EdgeInsets.all(16),
      children: [
        const Text(
          '可用目录',
          style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
        ),
        const SizedBox(height: 12),
        ..._directories.entries.map((entry) {
          final dir = entry.value;
          final isAvailable = dir != null;

          return Card(
            margin: const EdgeInsets.only(bottom: 8),
            child: ListTile(
              leading: Icon(
                isAvailable ? Icons.folder : Icons.folder_off,
                color: isAvailable ? const Color(0xFF4CAF50) : Colors.grey,
              ),
              title: Text(entry.key),
              subtitle: Text(
                isAvailable ? dir.path : '不可用',
                maxLines: 1,
                overflow: TextOverflow.ellipsis,
                style: TextStyle(
                  fontSize: 12,
                  color: isAvailable ? null : Colors.grey,
                ),
              ),
              trailing: isAvailable
                  ? const Icon(Icons.chevron_right)
                  : null,
              onTap: isAvailable ? () => _openDirectory(dir!) : null,
            ),
          );
        }),
      ],
    );
  }

  Widget _buildFileList() {
    if (_currentFiles.isEmpty) {
      return const Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Icon(Icons.folder_open, size: 64, color: Colors.grey),
            SizedBox(height: 16),
            Text(
              '目录为空',
              style: TextStyle(fontSize: 16, color: Colors.grey),
            ),
          ],
        ),
      );
    }

    return ListView.builder(
      padding: const EdgeInsets.all(8),
      itemCount: _currentFiles.length,
      itemBuilder: (context, index) {
        final file = _currentFiles[index];

        return Card(
          margin: const EdgeInsets.symmetric(vertical: 4, horizontal: 8),
          child: ListTile(
            leading: Icon(
              file.isDirectory ? Icons.folder : Icons.insert_drive_file,
              color: file.isDirectory
                  ? const Color(0xFFFFC107)
                  : const Color(0xFF2196F3),
            ),
            title: Text(
              file.name,
              maxLines: 1,
              overflow: TextOverflow.ellipsis,
            ),
            subtitle: Text(
              file.isDirectory
                  ? '文件夹'
                  : '${_formatFileSize(file.size)} · ${_formatDateTime(file.modified)}',
              style: const TextStyle(fontSize: 12),
            ),
            trailing: PopupMenuButton<String>(
              itemBuilder: (context) => [
                if (!file.isDirectory)
                  const PopupMenuItem(
                    value: 'view',
                    child: Row(
                      children: [
                        Icon(Icons.visibility, size: 20),
                        SizedBox(width: 8),
                        Text('查看'),
                      ],
                    ),
                  ),
                const PopupMenuItem(
                  value: 'delete',
                  child: Row(
                    children: [
                      Icon(Icons.delete, size: 20, color: Colors.red),
                      SizedBox(width: 8),
                      Text('删除', style: TextStyle(color: Colors.red)),
                    ],
                  ),
                ),
              ],
              onSelected: (value) {
                if (value == 'view') {
                  _viewFile(file);
                } else if (value == 'delete') {
                  _deleteFile(file);
                }
              },
            ),
            onTap: file.isDirectory ? () => _openDirectory(Directory(file.path)) : null,
          ),
        );
      },
    );
  }
}

class FileItem {
  final String name;
  final String path;
  final bool isDirectory;
  final int size;
  final DateTime modified;

  FileItem({
    required this.name,
    required this.path,
    required this.isDirectory,
    required this.size,
    required this.modified,
  });
}

五、OpenHarmony 适配要点

5.1 权限配置

在 OpenHarmony 平台,访问外部存储需要申请对应权限:

json 复制代码
{
  "name": "ohos.permission.READ_MEDIA",
  "reason": "$string:read_media_reason",
  "usedScene": {
    "abilities": ["EntryAbility"],
    "when": "inuse"
  }
}

5.2 应用沙箱路径

⚠️ 重要: READ_MEDIAIMAGEWRITE_MEDIAIMAGE 权限在 OpenHarmony 平台上无法通过常规方式申请。推荐使用应用沙箱路径,这些路径不需要额外权限:

dart 复制代码
// 推荐:应用文档目录(持久存储)
final docDir = await getApplicationDocumentsDirectory();

// 推荐:应用缓存目录(可能被清理)
final cacheDir = await getApplicationCacheDirectory();

// 推荐:临时目录(可能被清理)
final tempDir = await getTemporaryDirectory();

5.3 目录创建

获取目录后,可能需要创建子目录:

dart 复制代码
final docDir = await getApplicationDocumentsDirectory();
final photosDir = Directory('${docDir.path}/photos');
if (!await photosDir.exists()) {
  await photosDir.create(recursive: true);
}

5.4 文件操作

使用 dart:io 进行文件操作:

dart 复制代码
import 'dart:io';

final file = File('${docDir.path}/test.txt');

// 写入文件
await file.writeAsString('Hello World');

// 读取文件
final content = await file.readAsString();

// 删除文件
await file.delete();

// 检查文件是否存在
final exists = await file.exists();

5.5 不支持的功能

在 OpenHarmony 平台上,以下功能可能不受支持:

dart 复制代码
// getLibraryDirectory 不受支持
try {
  final libraryDir = await getLibraryDirectory();
} on UnsupportedError catch (e) {
  print('不支持: $e');
}

// 外部存储目录可能返回 null
final externalDir = await getExternalStorageDirectory();
if (externalDir == null) {
  print('外部存储不可用');
}

六、常见问题

Q1: 如何获取应用文档目录?

解决方案:

dart 复制代码
final docDir = await getApplicationDocumentsDirectory();
print('文档目录: ${docDir.path}');

Q2: 临时目录和缓存目录有什么区别?

解决方案:

  • 临时目录 (getTemporaryDirectory):系统可能随时清理,适合临时文件
  • 缓存目录 (getApplicationCacheDirectory):应用特定的缓存,更适合应用级缓存

Q3: 如何创建子目录?

解决方案:

dart 复制代码
final docDir = await getApplicationDocumentsDirectory();
final subDir = Directory('${docDir.path}/subfolder');
await subDir.create(recursive: true);

Q4: 如何列出目录内容?

解决方案:

dart 复制代码
final dir = await getApplicationDocumentsDirectory();
final entities = await dir.list().toList();
for (final entity in entities) {
  print(entity.path);
}

Q5: 如何获取文件大小?

解决方案:

dart 复制代码
final file = File('${docDir.path}/test.txt');
final stat = await file.stat();
print('文件大小: ${stat.size} 字节');

Q6: 外部存储目录返回 null 怎么办?

解决方案: 使用应用沙箱目录替代:

dart 复制代码
final externalDir = await getExternalStorageDirectory();
if (externalDir == null) {
  // 使用应用文档目录
  final docDir = await getApplicationDocumentsDirectory();
}

Q7: 如何安全地删除目录?

解决方案:

dart 复制代码
final dir = Directory('${docDir.path}/temp');
if (await dir.exists()) {
  await dir.delete(recursive: true);
}

Q8: 如何在应用中持久化存储数据?

解决方案: 使用应用文档目录:

dart 复制代码
final docDir = await getApplicationDocumentsDirectory();
final dataFile = File('${docDir.path}/user_data.json');
await dataFile.writeAsString(jsonEncode(data));

七、总结

path_provider 是 Flutter 官方提供的文件路径获取插件,在 OpenHarmony 平台的适配已经非常成熟。通过本文的介绍,你应该已经掌握了:

  1. 各种目录类型的获取方式和适用场景
  2. getTemporaryDirectory、getApplicationDocumentsDirectory、getApplicationCacheDirectory 等核心 API
  3. 外部存储目录的使用方法和限制
  4. 完整的文件管理器助手应用实现
  5. OpenHarmony 平台的权限和文件存储注意事项
  6. 文件操作的最佳实践
相关推荐
哈撒Ki3 小时前
快速入门 Dart 语言
前端·flutter·dart
小蜜蜂嗡嗡3 小时前
flutter 自定义走马灯,内部为Widget控件的走马灯效果二:横向无限匀速滚动+每个Item与屏幕左侧对齐时,停靠3秒再继续滚动
开发语言·flutter
浮芷.3 小时前
生命科学数据视界防御:基于鸿蒙Flutter陀螺仪云台与三维体积光栅的视轴锁定架构
flutter·华为·架构·开源·harmonyos·鸿蒙
千码君20163 小时前
Flutter:在win10上第一次安装和尝试开发记录
flutter·gradle·android-studio·安卓模拟器
浮芷.3 小时前
微观搜打撤:基于鸿蒙flutter的内存快照算法的局内外状态隔离与高阶背包系统设计
算法·flutter·华为·开源·harmonyos·鸿蒙
浮芷.3 小时前
东方修仙模拟器:基于 鸿蒙Flutter 状态机与 CustomPainter 的境界跃升与天劫渲染架构
科技·flutter·华为·架构·开源·harmonyos·鸿蒙
2601_949593653 小时前
Flutter OpenHarmony 三方库 animations 动画效果适配详解
flutter
Ww.xh3 小时前
KMP与Flutter选型实战指南
flutter
2601_949593654 小时前
Flutter OpenHarmony 三方库 camera 相机拍照录像适配详解
flutter