
老孟導(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)
NSDocumentDirectoryAPI。 如果數(shù)據(jù)不是用戶生成的,考慮使用[getApplicationSupportDirectory]。在Android上,對(duì)應(yīng)
getDataDirectoryAPI。 如果要讓用戶看到數(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 也可以指定位置。
SharePreferences 和 sqlite 是兩種保存數(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é)如下:
- SharePreferences 和 sqlite 數(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) getExternalCacheDirectories 和 getExternalStorageDirectories 方法。
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