在这里插入图片描述
在这里插入图片描述

概述

在移动应用开发中,数据持久化是一个重要的需求。将JSON数据保存到本地文件可以实现数据的离线存储和配置管理。Flutter提供了 dart:io 库来进行文件操作,结合 dart:convert 库可以方便地实现JSON文件的读写。

本章将详细介绍如何在Flutter和鸿蒙平台上实现JSON文件的读写操作,包括:

  • dart:io 文件操作API
  • JSON序列化与文件IO的结合
  • 异步文件操作的最佳实践
  • 配置管理的完整实现

核心概念

文件IO基础

文件IO(Input/Output)是指与文件系统进行数据交换的操作。在Flutter中,dart:io 库提供了丰富的文件操作API:

  • File类:用于操作单个文件
  • Directory类:用于操作目录
  • FileSystemEntity类:文件系统实体的基类

JSON文件的特点

JSON文件是一种轻量级的数据交换格式,具有以下特点:

  1. 可读性强:人类可读的文本格式
  2. 结构清晰:支持嵌套的对象和数组
  3. 跨平台:几乎所有编程语言都支持
  4. 易于解析:有成熟的解析库支持

异步文件操作

文件操作通常是耗时的IO操作,Flutter推荐使用异步方式进行文件操作,避免阻塞UI线程:

  • 使用 async/await 语法
  • 返回 Future 对象
  • 使用 then() 链式调用

实现方案

基本文件读写

写入JSON文件
import 'dart:convert';
import 'dart:io';

Future<void> saveJsonToFile(Map<String, dynamic> data, String filePath) async {
  final File file = File(filePath);
  final String jsonString = JsonEncoder.withIndent('  ').convert(data);
  await file.writeAsString(jsonString);
}
读取JSON文件
import 'dart:convert';
import 'dart:io';

Future<Map<String, dynamic>?> loadJsonFromFile(String filePath) async {
  final File file = File(filePath);
  
  if (!await file.exists()) {
    return null;
  }
  
  final String jsonString = await file.readAsString();
  return json.decode(jsonString) as Map<String, dynamic>;
}

配置管理实现

创建配置管理类
import 'dart:convert';
import 'dart:io';

class ConfigManager {
  static const String _configFileName = 'app_config.json';
  
  static Future<String> get _configPath async {
    final Directory appDir = Directory.current;
    return '${appDir.path}/$_configFileName';
  }
  
  static Future<void> saveConfig(Map<String, dynamic> config) async {
    final String path = await _configPath;
    final File file = File(path);
    final String jsonString = JsonEncoder.withIndent('  ').convert(config);
    await file.writeAsString(jsonString);
  }
  
  static Future<Map<String, dynamic>?> loadConfig() async {
    final String path = await _configPath;
    final File file = File(path);
    
    if (!await file.exists()) {
      return null;
    }
    
    try {
      final String jsonString = await file.readAsString();
      return json.decode(jsonString) as Map<String, dynamic>;
    } catch (e) {
      print('配置文件读取失败: $e');
      return null;
    }
  }
  
  static Future<bool> deleteConfig() async {
    final String path = await _configPath;
    final File file = File(path);
    
    if (await file.exists()) {
      await file.delete();
      return true;
    }
    return false;
  }
  
  static Future<bool> configExists() async {
    final String path = await _configPath;
    return File(path).exists();
  }
}

文件操作方法汇总

方法 说明 返回值
File.exists() 检查文件是否存在 Future<bool>
File.readAsString() 读取文件内容 Future<String>
File.writeAsString() 写入文件内容 Future<void>
File.delete() 删除文件 Future<void>
Directory.create() 创建目录 Future<Directory>
Directory.list() 列出目录内容 Stream<FileSystemEntity>

实战案例

完整的配置管理应用

配置模型
class AppConfig {
  String appName;
  String version;
  String theme;
  String language;
  Settings settings;
  List<String> features;
  String lastUpdated;
  
  AppConfig({
    required this.appName,
    required this.version,
    required this.theme,
    required this.language,
    required this.settings,
    required this.features,
    required this.lastUpdated,
  });
  
  Map<String, dynamic> toJson() {
    return {
      'appName': appName,
      'version': version,
      'theme': theme,
      'language': language,
      'settings': settings.toJson(),
      'features': features,
      'lastUpdated': lastUpdated,
    };
  }
  
  factory AppConfig.fromJson(Map<String, dynamic> json) {
    return AppConfig(
      appName: json['appName'] as String,
      version: json['version'] as String,
      theme: json['theme'] as String,
      language: json['language'] as String,
      settings: Settings.fromJson(json['settings'] as Map<String, dynamic>),
      features: (json['features'] as List).map((e) => e as String).toList(),
      lastUpdated: json['lastUpdated'] as String,
    );
  }
}

class Settings {
  bool notifications;
  bool autoUpdate;
  int cacheSize;
  int timeout;
  
  Settings({
    required this.notifications,
    required this.autoUpdate,
    required this.cacheSize,
    required this.timeout,
  });
  
  Map<String, dynamic> toJson() {
    return {
      'notifications': notifications,
      'autoUpdate': autoUpdate,
      'cacheSize': cacheSize,
      'timeout': timeout,
    };
  }
  
  factory Settings.fromJson(Map<String, dynamic> json) {
    return Settings(
      notifications: json['notifications'] as bool,
      autoUpdate: json['autoUpdate'] as bool,
      cacheSize: json['cacheSize'] as int,
      timeout: json['timeout'] as int,
    );
  }
}
使用配置管理
void main() async {
  // 创建配置
  final AppConfig config = AppConfig(
    appName: 'Flutter HarmonyOS Demo',
    version: '1.0.0',
    theme: 'dark',
    language: 'zh_CN',
    settings: Settings(
      notifications: true,
      autoUpdate: true,
      cacheSize: 50,
      timeout: 30,
    ),
    features: ['weather', 'news', 'calendar'],
    lastUpdated: DateTime.now().toIso8601String(),
  );
  
  // 保存配置
  await ConfigManager.saveConfig(config.toJson());
  print('配置保存成功');
  
  // 检查配置是否存在
  final bool exists = await ConfigManager.configExists();
  print('配置文件存在: $exists');
  
  // 加载配置
  final Map<String, dynamic>? loaded = await ConfigManager.loadConfig();
  if (loaded != null) {
    final AppConfig loadedConfig = AppConfig.fromJson(loaded);
    print('应用名称: ${loadedConfig.appName}');
    print('主题: ${loadedConfig.theme}');
    print('通知: ${loadedConfig.settings.notifications}');
  }
  
  // 删除配置
  final bool deleted = await ConfigManager.deleteConfig();
  print('配置删除成功: $deleted');
}

批量文件操作

import 'dart:convert';
import 'dart:io';

class JsonFileManager {
  static Future<void> saveMultiple(
    Map<String, Map<String, dynamic>> dataMap,
    String directory
  ) async {
    final Directory dir = Directory(directory);
    
    if (!await dir.exists()) {
      await dir.create(recursive: true);
    }
    
    for (final String key in dataMap.keys) {
      final File file = File('$directory/$key.json');
      final String jsonString = JsonEncoder.withIndent('  ').convert(dataMap[key]);
      await file.writeAsString(jsonString);
    }
  }
  
  static Future<Map<String, Map<String, dynamic>>> loadMultiple(
    String directory,
    List<String> fileNames
  ) async {
    final Map<String, Map<String, dynamic>> result = {};
    final Directory dir = Directory(directory);
    
    if (!await dir.exists()) {
      return result;
    }
    
    for (final String fileName in fileNames) {
      final File file = File('$directory/$fileName.json');
      
      if (await file.exists()) {
        final String content = await file.readAsString();
        result[fileName] = json.decode(content) as Map<String, dynamic>;
      }
    }
    
    return result;
  }
}

异步操作模式

使用async/await

Future<void> asyncFileOperation() async {
  final File file = File('data.json');
  
  // 写入文件
  final Map<String, dynamic> data = {'name': '张三', 'age': 25};
  await file.writeAsString(json.encode(data));
  print('写入完成');
  
  // 读取文件
  final String content = await file.readAsString();
  final Map<String, dynamic> loaded = json.decode(content) as Map<String, dynamic>;
  print('读取完成: $loaded');
}

使用then链式调用

void thenFileOperation() {
  final File file = File('data.json');
  
  file.writeAsString(json.encode({'name': '李四'}))
    .then((_) => file.readAsString())
    .then((content) {
      final Map<String, dynamic> loaded = json.decode(content) as Map<String, dynamic>;
      print('数据: $loaded');
    })
    .catchError((error) => print('操作失败: $error'));
}

并行文件操作

Future<void> parallelOperations() async {
  final List<File> files = [
    File('file1.json'),
    File('file2.json'),
    File('file3.json'),
  ];
  
  // 并行写入
  final List<Future<void>> writeFutures = files.map((file) {
    return file.writeAsString(json.encode({'id': files.indexOf(file)}));
  }).toList();
  
  await Future.wait(writeFutures);
  print('所有文件写入完成');
  
  // 并行读取
  final List<Future<String>> readFutures = files.map((file) => file.readAsString()).toList();
  final List<String> contents = await Future.wait(readFutures);
  
  for (final String content in contents) {
    print(content);
  }
}

错误处理

文件不存在的处理

Future<Map<String, dynamic>?> safeLoadFile(String path) async {
  final File file = File(path);
  
  try {
    if (!await file.exists()) {
      print('文件不存在: $path');
      return null;
    }
    
    final String content = await file.readAsString();
    return json.decode(content) as Map<String, dynamic>;
  } on FileSystemException catch (e) {
    print('文件系统错误: $e');
    return null;
  } on FormatException catch (e) {
    print('JSON格式错误: $e');
    return null;
  } catch (e) {
    print('未知错误: $e');
    return null;
  }
}

权限检查

Future<bool> checkFilePermission(String path) async {
  try {
    final File file = File(path);
    await file.exists();
    return true;
  } on FileSystemException catch (e) {
    if (e.osError?.errorCode == 13) {
      print('没有权限访问文件');
    }
    return false;
  }
}

鸿蒙平台注意事项

文件路径管理

在鸿蒙平台上,不同应用有不同的文件存储路径:

import 'dart:io';

class HarmonyPathManager {
  static Future<String> get appDocumentsPath async {
    // 应用文档目录
    final Directory appDir = Directory.current;
    return appDir.path;
  }
  
  static Future<String> get cachePath async {
    // 缓存目录
    final Directory cacheDir = Directory.systemTemp;
    return cacheDir.path;
  }
  
  static String getConfigPath(String fileName) {
    // 构建配置文件路径
    return '$appDocumentsPath/$fileName';
  }
}

资源限制

在鸿蒙平台上需要注意:

  1. 存储空间限制:不同设备的可用空间不同
  2. 文件大小限制:单个文件不宜过大
  3. 权限管理:需要在manifest中声明文件访问权限
  4. 后台操作:避免在后台进行大量文件IO操作

性能优化

class OptimizedFileManager {
  static Future<void> saveWithBackup(Map<String, dynamic> data, String path) async {
    final File file = File(path);
    final File backupFile = File('$path.bak');
    
    // 先写入临时文件
    final String jsonString = JsonEncoder.withIndent('  ').convert(data);
    
    // 如果原文件存在,创建备份
    if (await file.exists()) {
      await file.copy(backupFile.path);
    }
    
    // 写入新内容
    await file.writeAsString(jsonString);
    
    // 删除备份
    if (await backupFile.exists()) {
      await backupFile.delete();
    }
  }
}

应用场景

场景一:用户配置存储

class UserSettingsManager {
  static const String _settingsFile = 'user_settings.json';
  
  static Future<void> saveUserSettings(Map<String, dynamic> settings) async {
    final File file = File(_settingsFile);
    await file.writeAsString(json.encode(settings));
  }
  
  static Future<Map<String, dynamic>> loadUserSettings() async {
    final File file = File(_settingsFile);
    
    if (!await file.exists()) {
      return _getDefaultSettings();
    }
    
    final String content = await file.readAsString();
    return json.decode(content) as Map<String, dynamic>;
  }
  
  static Map<String, dynamic> _getDefaultSettings() {
    return {
      'theme': 'light',
      'language': 'zh_CN',
      'notifications': true,
      'autoUpdate': true,
    };
  }
}

场景二:离线数据缓存

class DataCacheManager {
  static const String _cacheDir = 'cache';
  static const int _maxCacheSize = 100;
  
  static Future<void> cacheData(String key, Map<String, dynamic> data) async {
    final Directory dir = Directory(_cacheDir);
    if (!await dir.exists()) {
      await dir.create();
    }
    
    final File file = File('$_cacheDir/$key.json');
    await file.writeAsString(json.encode(data));
    
    // 检查缓存大小
    await _cleanupCache();
  }
  
  static Future<Map<String, dynamic>?> getCachedData(String key) async {
    final File file = File('$_cacheDir/$key.json');
    
    if (!await file.exists()) {
      return null;
    }
    
    // 更新访问时间
    await file.setLastModified(DateTime.now());
    
    final String content = await file.readAsString();
    return json.decode(content) as Map<String, dynamic>;
  }
  
  static Future<void> _cleanupCache() async {
    final Directory dir = Directory(_cacheDir);
    if (!await dir.exists()) return;
    
    final List<FileSystemEntity> files = await dir.list().toList();
    
    if (files.length > _maxCacheSize) {
      // 按修改时间排序,删除最早的
      files.sort((a, b) => a.statSync().modified.compareTo(b.statSync().modified));
      
      for (int i = 0; i < files.length - _maxCacheSize; i++) {
        await files[i].delete();
      }
    }
  }
}

场景三:日志文件记录

class JsonLogger {
  static const String _logFile = 'app_logs.json';
  static List<Map<String, dynamic>> _logs = [];
  
  static void log(String level, String message, {Map<String, dynamic>? extra}) {
    final Map<String, dynamic> logEntry = {
      'timestamp': DateTime.now().toIso8601String(),
      'level': level,
      'message': message,
      'extra': extra,
    };
    
    _logs.add(logEntry);
    
    // 定期写入文件
    if (_logs.length >= 100) {
      _flushLogs();
    }
  }
  
  static Future<void> _flushLogs() async {
    final File file = File(_logFile);
    
    if (await file.exists()) {
      final String content = await file.readAsString();
      final List<dynamic> existingLogs = json.decode(content) as List<dynamic>;
      _logs = [...existingLogs.cast<Map<String, dynamic>>(), ..._logs];
    }
    
    await file.writeAsString(JsonEncoder.withIndent('  ').convert(_logs));
    _logs.clear();
  }
  
  static Future<void> flush() async {
    if (_logs.isNotEmpty) {
      await _flushLogs();
    }
  }
}

最佳实践

使用文件锁

在多线程环境下,需要使用文件锁避免并发写入:

class SafeFileWriter {
  static Future<void> writeWithLock(File file, String content) async {
    final RandomAccessFile raf = await file.open(mode: FileMode.write);
    
    try {
      await raf.lock();
      await raf.writeString(content);
    } finally {
      await raf.unlock();
      await raf.close();
    }
  }
}

增量更新

对于大型JSON文件,可以使用增量更新策略:

class IncrementalJsonUpdater {
  static Future<void> updateField(
    String filePath,
    String fieldPath,
    dynamic value
  ) async {
    final File file = File(filePath);
    
    if (!await file.exists()) {
      return;
    }
    
    final String content = await file.readAsString();
    final Map<String, dynamic> data = json.decode(content) as Map<String, dynamic>;
    
    // 更新指定字段
    _updateNestedField(data, fieldPath.split('.'), value);
    
    await file.writeAsString(JsonEncoder.withIndent('  ').convert(data));
  }
  
  static void _updateNestedField(
    Map<String, dynamic> data,
    List<String> keys,
    dynamic value
  ) {
    if (keys.length == 1) {
      data[keys[0]] = value;
      return;
    }
    
    final String currentKey = keys[0];
    if (!data.containsKey(currentKey)) {
      data[currentKey] = <String, dynamic>{};
    }
    
    _updateNestedField(
      data[currentKey] as Map<String, dynamic>,
      keys.sublist(1),
      value
    );
  }
}

版本控制

为配置文件添加版本号,便于升级处理:

class VersionedConfigManager {
  static const String _configFile = 'config.json';
  static const int _currentVersion = 2;
  
  static Future<Map<String, dynamic>> loadConfig() async {
    final File file = File(_configFile);
    
    if (!await file.exists()) {
      return _getDefaultConfig();
    }
    
    final String content = await file.readAsString();
    final Map<String, dynamic> data = json.decode(content) as Map<String, dynamic>;
    
    final int version = data['version'] as int? ?? 0;
    if (version < _currentVersion) {
      return _migrateConfig(data, version);
    }
    
    return data;
  }
  
  static Map<String, dynamic> _migrateConfig(Map<String, dynamic> oldConfig, int oldVersion) {
    final Map<String, dynamic> newConfig = Map.from(oldConfig);
    
    if (oldVersion < 2) {
      // 添加新版本的字段
      newConfig['newField'] = defaultValue;
    }
    
    newConfig['version'] = _currentVersion;
    return newConfig;
  }
  
  static Map<String, dynamic> _getDefaultConfig() {
    return {
      'version': _currentVersion,
      // ...其他默认配置
    };
  }
}

总结

JSON文件读写是Flutter应用开发中常用的数据持久化方式。通过 dart:iodart:convert 库的结合使用,我们可以方便地实现数据的保存和读取。

关键要点:

  1. 使用 File 类进行文件操作
  2. 使用 async/await 进行异步操作
  3. 结合 JsonEncoder/JsonDecoder 进行序列化
  4. 注意错误处理和权限管理
  5. 在鸿蒙平台上注意资源限制和性能优化

掌握这些技巧,可以为你的应用提供可靠的数据持久化方案。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐