【Flutter 實戰】文件系統目錄

老孟導讀:Flutter 中獲取文件路徑,我們都知道使用 path_provider,但對其目錄對含義不是很清楚,此文介紹 Android、iOS 系統的文件目錄,不同場景下建議使用的目錄。

不同的平台對應的文件系統是不同的,比如文件路徑,因此 Flutter 中獲取文件路徑需要原生支援,原生端通過 MethodChannel 傳遞文件路徑到 Flutter,如果沒有特殊的需求,推薦大家使用 Google 官方維護的插件 path_provider

pub 地址://pub.flutter-io.cn/packages/path_provider

Github 地址://github.com/flutter/plugins/tree/master/packages/path_provider/path_provider

添加依賴

在項目的 pubspec.yaml 文件中添加依賴:

dependencies:
  path_provider: ^1.6.14

執行命令:

flutter pub get

文件路徑

path_provider(版本:1.6.14)提供了8個方法獲取不同的文件路徑,目前 Flutter(Flutter 1.20.1 • channel stable )只發布了正式版本的 Android 和 iOS,因此下面僅介紹 Android 和 iOS 平台的文件路徑。

  • getTemporaryDirectory

    臨時目錄,適用於下載的快取文件,此目錄隨時可以清除,此目錄為應用程式私有目錄,其他應用程式無法訪問此目錄。

    Android 上對應getCacheDir

    iOS上對應NSCachesDirectory

  • getApplicationSupportDirectory

    應用程式可以在其中放置應用程式支援文件的目錄的路徑。

    將此文件用於您不想向用戶公開的文件。 您的應用不應將此目錄用於存放用戶數據文件。

    在iOS上,對應NSApplicationSupportDirectory ,如果此目錄不存在,則會自動創建。
    在Android上,對應getFilesDir

  • getLibraryDirectory

    應用程式可以在其中存儲持久性文件,備份文件以及對用戶不可見的文件的目錄路徑,例如storage.sqlite.db。

    在Android上,此函數拋出[UnsupportedError]異常,沒有等效項路徑存在。

  • getApplicationDocumentsDirectory

    應用程式可能在其中放置用戶生成的數據或應用程式無法重新創建的數據的目錄路徑。

    在iOS上,對應NSDocumentDirectory API。 如果數據不是用戶生成的,考慮使用[getApplicationSupportDirectory]。

    在Android上,對應getDataDirectory API。 如果要讓用戶看到數據,請考慮改用[getExternalStorageDirectory]。

  • getExternalStorageDirectory

    應用程式可以訪問頂級存儲的目錄的路徑。由於此功能僅在Android上可用,因此應在發出此函數調用之前確定當前作業系統。

    在iOS上,此功能會引發[UnsupportedError]異常,因為無法在應用程式的沙箱外部訪問。

    在Android上,對應getExternalFilesDir(null)

  • getExternalCacheDirectories

    存儲特定於應用程式的外部快取數據的目錄的路徑。 這些路徑通常位於外部存儲(如單獨的分區或SD卡)上。 電話可能具有多個可用的存儲目錄。
    由於此功能僅在Android上可用,因此應在發出此函數調用之前確定當前作業系統。
    在iOS上,此功能會拋出UnsupportedError,因為這是不可能的在應用程式的沙箱外部訪問。

    在Android上,對應Context.getExternalCacheDirs()或API Level 低於19的Context.getExternalCacheDir()

  • getExternalStorageDirectories

    可以存儲應用程式特定數據的目錄的路徑。 這些路徑通常位於外部存儲(如單獨的分區或SD卡)上。
    由於此功能僅在Android上可用,因此應在發出此函數調用之前確定當前作業系統。
    在iOS上,此功能會拋出UnsupportedError,因為這是不可能的在應用程式的沙箱外部訪問。
    在Android上,對應Context.getExternalFilesDirs(String type)或API Level 低於19的Context.getExternalFilesDir(String type)

  • getDownloadsDirectory

    存儲下載文件的目錄的路徑,這通常僅與台式機作業系統有關。
    在Android和iOS上,此函數將引發[UnsupportedError]異常。

如果沒有 Android 或者 iOS開發經驗,看完上面的說明應該是一臉懵逼的,這麼多路徑到底用哪個?有什麼區別?下面從 Android 和 iOS 平台的角度介紹其文件路徑,最後給出路徑使用的建議以及使用過程中需要注意的事項。

Android 文件存儲

Android 文件存儲分為內部存儲外部存儲

內部存儲

用於保存應用的私有文件,其他應用無法訪問這些數據,創建的文件在此應用的包名目錄下,沒有 root 許可權 的手機無法在手機的 文件管理 應用中看到此目錄,不過可以通過 Android Studio 工具查看,路徑為:data/data/包名:

看下包名下具體的目錄結構:

  • cache 目錄:對應 getTemporaryDirectory 方法,用於快取文件,此目錄隨時可能被系統清除。
  • files 目錄:對應 getApplicationSupportDirectory 方法。
  • code_cache:此目錄存儲 Flutter 相關程式碼和資源。
    • flutter_engine/skia:Flutter 渲染引擎。
    • flutter_guidePVWGWK/flutter_guide/build/flutter_assets:Flutter 資源文件。
  • shared_prefs: SharePreferences 的默認路徑。
  • app_flutter:對應 getApplicationDocumentsDirectory方法。
  • app_flutter/dbName:使用 sqlite 的默認路徑,sqlite 也可以指定位置。

SharePreferencessqlite 是兩種保存數據的第三方插件。

內部存儲的特點:

  • 安全性,其他應用無法訪問這些數據。
  • 當應用卸載的時候,這些數據也會被刪除,避免垃圾文件。
  • 不需要申請額外許可權。
  • 存儲的空間有限,此目錄數據隨時可能被系統清除,也可以通過 設置 中的 清除數據 可以清除此目錄數據。
  • 中國特色,不同手機廠商對此目錄做了不同的限制,比如總體大小限制、單個應用程式所佔空間大小限制、清除數據策略不同等。

外部存儲

外部存儲可以通過手機的 文件管理 應用查看,

這裡面有一個特殊的目錄:Android/data/包名:

看到這個目錄是不是覺得和內部存儲目錄非常相似,一個包名代表一個應用程式:

  • cache:快取目錄,對應 getExternalCacheDirectories 方法。
  • files:對應 getExternalStorageDirectories 方法。

此目錄的特點:

  • 當應用卸載的時候,這些數據也會被刪除,避免垃圾文件。
  • 不需要申請額外許可權。
  • 空間大且不會被系統清除,通過 設置 中的 清除數據 可以清除此目錄數據。
  • 用戶可以直接對文件進行刪除、導入操作。

外部存儲除了 Android/data/ 目錄,還有和此目錄同級的目錄,特點:

  • 所有應用程式均可訪問。
  • 用戶可以直接對文件進行刪除、導入操作。
  • 需要申請讀寫許可權

Android 官方對此目錄的管理越來越嚴格, Android 11 系統已經開始強制執行分區存儲,詳情見://developer.android.com/preview/privacy/storage?hl=zh-cn

上面說了這麼多,總結如下:

  • SharePreferencessqlite 數據建議存放在內部存儲,插件已經幫我們完成了,無需手動處理。
  • 嚴格保密的數據,比如用戶數據,建議存放在內部存儲,對應 getApplicationSupportDirectory 方法。
  • 其餘所有的數據建議存放 Android/data/包名/ ,對應 getExternalCacheDirectoriesgetExternalStorageDirectories 方法。

iOS 文件存儲

iOS 文件存儲相比 Android 要簡單的多,因為 iOS 對用戶隱私保護非常嚴格,每個 iOS 應用程式都有一個單獨的文件系統,而且只能在對應的文件系統中進行操作,此區域被稱為沙盒。

每個應用沙盒含有3個文件夾:Documents, Library 和 tmp:

  • Documents:應用程式數據文件寫入到這個目錄下。這個目錄用於存儲用戶數據。保存應用程式的重要數據文件和用戶數據文件等。iTunes 同步時會備份該目錄,對應 getApplicationDocumentsDirectory 方法。
  • Library:對應 getLibraryDirectory 方法。
    • Caches:保存應用程式使用時產生的支援文件、快取文件、日誌文件等,比如下載的音樂,影片,SDWebImage快取等。對應 getTemporaryDirectory 方法。
    • Preferences:包含應用程式的偏好設置文件,iCloud會備份設置資訊。
    • Application Support:對應 getApplicationSupportDirectory 方法。
  • tmp:存放臨時文件,不會被備份,而且這個文件下的數據有可能隨時被清除的可能,按照官方說法每三天清理一次快取數據。

path_provider 使用

import 'dart:io';

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

///
/// desc:
///

class PathProviderDemo extends StatefulWidget {
  @override
  _PathProviderDemoState createState() => _PathProviderDemoState();
}

class _PathProviderDemoState extends State<PathProviderDemo> {
  Future<Directory> _tempDirectory;
  Future<Directory> _appSupportDirectory;
  Future<Directory> _appLibraryDirectory;
  Future<Directory> _appDocumentsDirectory;
  Future<Directory> _externalStorageDirectory;
  Future<List<Directory>> _externalStorageDirectories;
  Future<List<Directory>> _externalCacheDirectories;
  Future<Directory> _downloadDirectory;

  @override
  void initState() {
    super.initState();
    setState(() {
      _tempDirectory = getTemporaryDirectory();
      _appSupportDirectory = getApplicationSupportDirectory();
      _appLibraryDirectory = getLibraryDirectory();
      _appDocumentsDirectory = getApplicationDocumentsDirectory();
      _externalStorageDirectory = getExternalStorageDirectory();
      _externalCacheDirectories = getExternalCacheDirectories();
      _externalStorageDirectories = getExternalStorageDirectories();
      _downloadDirectory = getDownloadsDirectory();
    });
  }

  Widget _buildDirectory(
      BuildContext context, AsyncSnapshot<Directory> snapshot) {
    Text text = const Text('');
    if (snapshot.connectionState == ConnectionState.done) {
      if (snapshot.hasError) {
        text = Text('Error: ${snapshot.error}');
      } else if (snapshot.hasData) {
        text = Text('path: ${snapshot.data.path}');
      } else {
        text = const Text('path unavailable');
      }
    }
    return Padding(padding: EdgeInsets.symmetric(horizontal: 16), child: text);
  }

  Widget _buildDirectories(
      BuildContext context, AsyncSnapshot<List<Directory>> snapshot) {
    Text text = const Text('');
    if (snapshot.connectionState == ConnectionState.done) {
      if (snapshot.hasError) {
        text = Text('Error: ${snapshot.error}');
      } else if (snapshot.hasData) {
        final String combined =
            snapshot.data.map((Directory d) => d.path).join(', ');
        text = Text('paths: $combined');
      } else {
        text = const Text('path unavailable');
      }
    }
    return Padding(
        padding: const EdgeInsets.symmetric(horizontal: 16), child: text);
  }

  Widget _buildItem(String title, Future<Directory> future) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Padding(
          padding: const EdgeInsets.symmetric(horizontal: 16),
          child: Text(title),
        ),
        FutureBuilder<Directory>(future: future, builder: _buildDirectory),
      ],
    );
  }

  Widget _buildItem1(String title, Future<List<Directory>> future) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Padding(
          padding: const EdgeInsets.symmetric(horizontal: 16),
          child: Text(title),
        ),
        FutureBuilder<List<Directory>>(
            future: future,
            builder: _buildDirectories),
      ],
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(),
      body: Center(
        child: ListView(
          itemExtent: 120,
          children: <Widget>[
            _buildItem('getTemporaryDirectory', _tempDirectory),
            _buildItem('getApplicationSupportDirectory', _appSupportDirectory),
            _buildItem('getLibraryDirectory', _appLibraryDirectory),
            _buildItem(
                'getApplicationDocumentsDirectory', _appDocumentsDirectory),
            _buildItem(
                'getExternalStorageDirectory', _externalStorageDirectory),
            _buildItem('getDownloadsDirectory', _downloadDirectory),

            _buildItem1('getExternalStorageDirectories',_externalStorageDirectories),
            _buildItem1('getExternalCacheDirectories',_externalCacheDirectories),

          ],
        ),
      ),
    );
  }
}

Android 系統各個路徑:

iOS 系統各個路徑:

交流

交流

老孟Flutter部落格(330個控制項用法+實戰入門系列文章)://laomengit.com

歡迎加入Flutter交流群(微信:laomengit)、關注公眾號【老孟Flutter】:

Tags: