【Flutter 實(shí)戰(zhàn)】文件系統(tǒng)目錄

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

不同的平臺(tái)對(duì)應(yīng)的文件系統(tǒng)是不同的,比如文件路徑,因此 Flutter 中獲取文件路徑需要原生支持,原生端通過(guò) MethodChannel 傳遞文件路徑到 Flutter,如果沒(méi)有特殊的需求,推薦大家使用 Google 官方維護(hù)的插件 path_provider。

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

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

添加依賴

在項(xiàng)目的 pubspec.yaml 文件中添加依賴:

dependencies:
  path_provider: ^1.6.14

執(zhí)行命令:

flutter pub get

文件路徑

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

  • getTemporaryDirectory

    臨時(shí)目錄,適用于下載的緩存文件,此目錄隨時(shí)可以清除,此目錄為應(yīng)用程序私有目錄,其他應(yīng)用程序無(wú)法訪問(wèn)此目錄。

    Android 上對(duì)應(yīng)getCacheDir。

    iOS上對(duì)應(yīng)NSCachesDirectory

  • getApplicationSupportDirectory

    應(yīng)用程序可以在其中放置應(yīng)用程序支持文件的目錄的路徑。

    將此文件用于您不想向用戶公開的文件。 您的應(yīng)用不應(yīng)將此目錄用于存放用戶數(shù)據(jù)文件。

    在iOS上,對(duì)應(yīng)NSApplicationSupportDirectory ,如果此目錄不存在,則會(huì)自動(dòng)創(chuàng)建。
    在Android上,對(duì)應(yīng)getFilesDir

  • getLibraryDirectory

    應(yīng)用程序可以在其中存儲(chǔ)持久性文件,備份文件以及對(duì)用戶不可見的文件的目錄路徑,例如storage.sqlite.db。

    在Android上,此函數(shù)拋出[UnsupportedError]異常,沒(méi)有等效項(xiàng)路徑存在。

  • getApplicationDocumentsDirectory

    應(yīng)用程序可能在其中放置用戶生成的數(shù)據(jù)或應(yīng)用程序無(wú)法重新創(chuàng)建的數(shù)據(jù)的目錄路徑。

    在iOS上,對(duì)應(yīng)NSDocumentDirectory API。 如果數(shù)據(jù)不是用戶生成的,考慮使用[getApplicationSupportDirectory]。

    在Android上,對(duì)應(yīng)getDataDirectory API。 如果要讓用戶看到數(shù)據(jù),請(qǐng)考慮改用[getExternalStorageDirectory]。

  • getExternalStorageDirectory

    應(yīng)用程序可以訪問(wèn)頂級(jí)存儲(chǔ)的目錄的路徑。由于此功能僅在Android上可用,因此應(yīng)在發(fā)出此函數(shù)調(diào)用之前確定當(dāng)前操作系統(tǒng)。

    在iOS上,此功能會(huì)引發(fā)[UnsupportedError]異常,因?yàn)闊o(wú)法在應(yīng)用程序的沙箱外部訪問(wèn)。

    在Android上,對(duì)應(yīng)getExternalFilesDir(null)。

  • getExternalCacheDirectories

    存儲(chǔ)特定于應(yīng)用程序的外部緩存數(shù)據(jù)的目錄的路徑。 這些路徑通常位于外部存儲(chǔ)(如單獨(dú)的分區(qū)或SD卡)上。 電話可能具有多個(gè)可用的存儲(chǔ)目錄。
    由于此功能僅在Android上可用,因此應(yīng)在發(fā)出此函數(shù)調(diào)用之前確定當(dāng)前操作系統(tǒng)。
    在iOS上,此功能會(huì)拋出UnsupportedError,因?yàn)檫@是不可能的在應(yīng)用程序的沙箱外部訪問(wèn)。

    在Android上,對(duì)應(yīng)Context.getExternalCacheDirs()或API Level 低于19的Context.getExternalCacheDir()。

  • getExternalStorageDirectories

    可以存儲(chǔ)應(yīng)用程序特定數(shù)據(jù)的目錄的路徑。 這些路徑通常位于外部存儲(chǔ)(如單獨(dú)的分區(qū)或SD卡)上。
    由于此功能僅在Android上可用,因此應(yīng)在發(fā)出此函數(shù)調(diào)用之前確定當(dāng)前操作系統(tǒng)。
    在iOS上,此功能會(huì)拋出UnsupportedError,因?yàn)檫@是不可能的在應(yīng)用程序的沙箱外部訪問(wèn)。
    在Android上,對(duì)應(yīng)Context.getExternalFilesDirs(String type)或API Level 低于19的Context.getExternalFilesDir(String type)。

  • getDownloadsDirectory

    存儲(chǔ)下載文件的目錄的路徑,這通常僅與臺(tái)式機(jī)操作系統(tǒng)有關(guān)。
    在Android和iOS上,此函數(shù)將引發(fā)[UnsupportedError]異常。

如果沒(méi)有 Android 或者 iOS開發(fā)經(jīng)驗(yàn),看完上面的說(shuō)明應(yīng)該是一臉懵逼的,這么多路徑到底用哪個(gè)?有什么區(qū)別?下面從 Android 和 iOS 平臺(tái)的角度介紹其文件路徑,最后給出路徑使用的建議以及使用過(guò)程中需要注意的事項(xiàng)。

Android 文件存儲(chǔ)

Android 文件存儲(chǔ)分為內(nèi)部存儲(chǔ)外部存儲(chǔ)。

內(nèi)部存儲(chǔ)

用于保存應(yīng)用的私有文件,其他應(yīng)用無(wú)法訪問(wèn)這些數(shù)據(jù),創(chuàng)建的文件在此應(yīng)用的包名目錄下,沒(méi)有 root 權(quán)限 的手機(jī)無(wú)法在手機(jī)的 文件管理 應(yīng)用中看到此目錄,不過(guò)可以通過(guò) Android Studio 工具查看,路徑為:data/data/包名:

看下包名下具體的目錄結(jié)構(gòu):

  • cache 目錄:對(duì)應(yīng) getTemporaryDirectory 方法,用于緩存文件,此目錄隨時(shí)可能被系統(tǒng)清除。
  • files 目錄:對(duì)應(yīng) getApplicationSupportDirectory 方法。
  • code_cache:此目錄存儲(chǔ) Flutter 相關(guān)代碼和資源。
    • flutter_engine/skia:Flutter 渲染引擎。
    • flutter_guidePVWGWK/flutter_guide/build/flutter_assets:Flutter 資源文件。
  • shared_prefs: SharePreferences 的默認(rèn)路徑。
  • app_flutter:對(duì)應(yīng) getApplicationDocumentsDirectory方法。
  • app_flutter/dbName:使用 sqlite 的默認(rèn)路徑,sqlite 也可以指定位置。

SharePreferencessqlite 是兩種保存數(shù)據(jù)的第三方插件。

內(nèi)部存儲(chǔ)的特點(diǎn):

  • 安全性,其他應(yīng)用無(wú)法訪問(wèn)這些數(shù)據(jù)。
  • 當(dāng)應(yīng)用卸載的時(shí)候,這些數(shù)據(jù)也會(huì)被刪除,避免垃圾文件。
  • 不需要申請(qǐng)額外權(quán)限。
  • 存儲(chǔ)的空間有限,此目錄數(shù)據(jù)隨時(shí)可能被系統(tǒng)清除,也可以通過(guò) 設(shè)置 中的 清除數(shù)據(jù) 可以清除此目錄數(shù)據(jù)。
  • 國(guó)內(nèi)特色,不同手機(jī)廠商對(duì)此目錄做了不同的限制,比如總體大小限制、單個(gè)應(yīng)用程序所占空間大小限制、清除數(shù)據(jù)策略不同等。

外部存儲(chǔ)

外部存儲(chǔ)可以通過(guò)手機(jī)的 文件管理 應(yīng)用查看,

這里面有一個(gè)特殊的目錄:Android/data/包名:

看到這個(gè)目錄是不是覺(jué)得和內(nèi)部存儲(chǔ)目錄非常相似,一個(gè)包名代表一個(gè)應(yīng)用程序:

  • cache:緩存目錄,對(duì)應(yīng) getExternalCacheDirectories 方法。
  • files:對(duì)應(yīng) getExternalStorageDirectories 方法。

此目錄的特點(diǎn):

  • 當(dāng)應(yīng)用卸載的時(shí)候,這些數(shù)據(jù)也會(huì)被刪除,避免垃圾文件。
  • 不需要申請(qǐng)額外權(quán)限。
  • 空間大且不會(huì)被系統(tǒng)清除,通過(guò) 設(shè)置 中的 清除數(shù)據(jù) 可以清除此目錄數(shù)據(jù)。
  • 用戶可以直接對(duì)文件進(jìn)行刪除、導(dǎo)入操作。

外部存儲(chǔ)除了 Android/data/ 目錄,還有和此目錄同級(jí)的目錄,特點(diǎn):

  • 所有應(yīng)用程序均可訪問(wèn)。
  • 用戶可以直接對(duì)文件進(jìn)行刪除、導(dǎo)入操作。
  • 需要申請(qǐng)讀寫權(quán)限。

Android 官方對(duì)此目錄的管理越來(lái)越嚴(yán)格, Android 11 系統(tǒng)已經(jīng)開始強(qiáng)制執(zhí)行分區(qū)存儲(chǔ),詳情見:https://developer.android.com/preview/privacy/storage?hl=zh-cn

上面說(shuō)了這么多,總結(jié)如下:

  • SharePreferencessqlite 數(shù)據(jù)建議存放在內(nèi)部存儲(chǔ),插件已經(jīng)幫我們完成了,無(wú)需手動(dòng)處理。
  • 嚴(yán)格保密的數(shù)據(jù),比如用戶數(shù)據(jù),建議存放在內(nèi)部存儲(chǔ),對(duì)應(yīng) getApplicationSupportDirectory 方法。
  • 其余所有的數(shù)據(jù)建議存放 Android/data/包名/ ,對(duì)應(yīng) getExternalCacheDirectoriesgetExternalStorageDirectories 方法。

iOS 文件存儲(chǔ)

iOS 文件存儲(chǔ)相比 Android 要簡(jiǎn)單的多,因?yàn)?iOS 對(duì)用戶隱私保護(hù)非常嚴(yán)格,每個(gè) iOS 應(yīng)用程序都有一個(gè)單獨(dú)的文件系統(tǒng),而且只能在對(duì)應(yīng)的文件系統(tǒng)中進(jìn)行操作,此區(qū)域被稱為沙盒。

每個(gè)應(yīng)用沙盒含有3個(gè)文件夾:Documents, Library 和 tmp:

  • Documents:應(yīng)用程序數(shù)據(jù)文件寫入到這個(gè)目錄下。這個(gè)目錄用于存儲(chǔ)用戶數(shù)據(jù)。保存應(yīng)用程序的重要數(shù)據(jù)文件和用戶數(shù)據(jù)文件等。iTunes 同步時(shí)會(huì)備份該目錄,對(duì)應(yīng) getApplicationDocumentsDirectory 方法。
  • Library:對(duì)應(yīng) getLibraryDirectory 方法。
    • Caches:保存應(yīng)用程序使用時(shí)產(chǎn)生的支持文件、緩存文件、日志文件等,比如下載的音樂(lè),視頻,SDWebImage緩存等。對(duì)應(yīng) getTemporaryDirectory 方法。
    • Preferences:包含應(yīng)用程序的偏好設(shè)置文件,iCloud會(huì)備份設(shè)置信息。
    • Application Support:對(duì)應(yīng) getApplicationSupportDirectory 方法。
  • tmp:存放臨時(shí)文件,不會(huì)被備份,而且這個(gè)文件下的數(shù)據(jù)有可能隨時(shí)被清除的可能,按照官方說(shuō)法每三天清理一次緩存數(shù)據(jù)。

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 系統(tǒng)各個(gè)路徑:

iOS 系統(tǒng)各個(gè)路徑:

交流

老孟Flutter博客(330個(gè)控件用法+實(shí)戰(zhàn)入門系列文章):http://laomengit.com

歡迎加入 Flutter威信交流群 :laomengit

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容