1、前言
從 Android N(7.0) 開始葬凳,將嚴格執(zhí)行 StrictMode 模式。而從 Android N 開始室奏,將不允許在 App 間火焰,使用 file://
的方式,傳遞一個 File 胧沫,否者會拋出 FileUriExposedException 的異常引發(fā) Crash昌简。解決方案就是通過 FileProvider 用 content://
代替 file://
占业,需要開發(fā)者主動升級 targetSdkVersion 到 24 才會執(zhí)行此策略。
2纯赎、讀取目錄
2.1 內(nèi)部存儲
每安裝一個 App 系統(tǒng)都會在內(nèi)部存儲空間的 data/data
目錄下以應(yīng)用包名為名字自動創(chuàng)建與之對應(yīng)的文件夾谦疾,這個文件夾用于持久化 App 中的 WebView 緩存頁面信息、SharedPreferences犬金、SQLiteDatabase 等應(yīng)用相關(guān)數(shù)據(jù)念恍。當(dāng)用戶卸載 App 時,系統(tǒng)自動刪除 data/data
目錄下對應(yīng)包名的文件夾及其內(nèi)容晚顷。
獲取并操作內(nèi)部存儲空間中的應(yīng)用私有目錄的方法如下:
context.getFilesDir()
context.getCacheDir()
context.deleteFile()
context.fileList()
Environment.getDataDirectory()
2.2 外部存儲
考慮到普通用戶無法訪問應(yīng)用的內(nèi)部存儲空間峰伙,比如用戶想從應(yīng)用里面保存一張圖片,那么這張圖片應(yīng)該存儲在外部存儲空間音同,用戶才能訪問的到
外部存儲空間路徑為:/storage/emulated/0/Android/data/<包名>
外部存儲分為應(yīng)用私有目錄和公共目錄
(1)應(yīng)用私有目錄
默認情況下词爬,系統(tǒng)并不會自動創(chuàng)建外部存儲空間的應(yīng)用私有目錄。只有在應(yīng)用需要的時候权均,開發(fā)人員通過 SDK 提供的 API 創(chuàng)建該目錄文件夾和操作文件夾內(nèi)容。
當(dāng)用戶卸載 App 時锅锨,系統(tǒng)也會自動刪除外部存儲空間下的對應(yīng) App 私有目錄文件夾及其內(nèi)容叽赊。
獲取并操作外部存儲空間中的應(yīng)用私有目錄的方法如下:
context.getExternalFilesDir()
context.getExternalCacheDir()
Environment.getExternalStorageDirectory()
(2)公共目錄
外部存儲空間中的公共目錄用來存放當(dāng)應(yīng)用被卸載時,仍然可以保存在設(shè)備中的信息必搞,如:拍照類應(yīng)用的圖片文件必指,用戶是使用瀏覽器手動下載的文件等。恕洲、
外部存儲空間已經(jīng)為用戶默認分類出一些公共目錄塔橡。開發(fā)人員可以通過 Environment 類提供的方法直接獲取相應(yīng)目錄的絕對路徑,傳遞不同的 type 參數(shù)類型即可:
Environment.getExternalStoragePublicDirectory(String type);
Envinonment 類提供諸多 type 參數(shù)的常量霜第,比如:
- DIRECTORY_MUSIC:/storage/emulated/0/Music
- DIRECTORY_MOVIES:/storage/emulated/0/Movies
- DIRECTORY_PICTURES:/storage/emulated/0/Pictures
- DIRECTORY_DOWNLOADS:/storage/emulated/0/Download
3葛家、FileProvider
3.1 什么是 FileProvider
FileProvider 是 ContentProvider的子類 目前 support v4 包 和 androidx的core包里面都有提供。FileProvider 本質(zhì)上就是一個 ContentProvider 泌类,它其實也繼承了 ContentProvider 的特性癞谒。其實ContentProvider 就是在可控的范圍內(nèi),向外部其他的 App 分享數(shù)據(jù)刃榨。而 FileProvider 將這樣的數(shù)據(jù)變成了一個 File 文件而已弹砚。
3.2 使用 FileProvider 的場景
在 App 內(nèi),通過一個 Intent 傳遞了一個 file://
的 Uri 的場景都需要使用 FileProvider 枢希,如:
- 調(diào)用相機拍照
- 剪裁圖片
- 調(diào)用系統(tǒng)安裝器去安裝 Apk
3.3 如何使用 FileProvider
(1)在 AndroidManifest.xml 中聲明
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileProvider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths"/>
</provider>
可以看到桌吃,provider 標(biāo)簽下,配置了幾個屬性:
-
name
:配置當(dāng)前 FileProvider 的實現(xiàn)類苞轿。 -
authorities
:配置一個 FileProvider 的名字茅诱,它在當(dāng)前系統(tǒng)內(nèi)需要是唯一值逗物。 -
exported
:表示該 FileProvider 是否需要公開出去,傳 false 表示不公開让簿。 -
granUriPermissions
:是否允許授權(quán)文件的臨時訪問權(quán)限敬察。傳 true 表示需要 。
(2)指定可分享的文件路徑
沒有 xml 目錄可自行創(chuàng)建
src/main/res/xml/file_paths.xml
<?xml version="1.0" encoding="utf-8"?>
<paths>
<!--支持本地路徑-->
<!--表示Environment.getExternalStorageDirectory() 指向的目錄-->
<external-path name="external_storage_root" path="." />
<!--表示 content.getFileDir() 獲取到的目錄-->
<files-path name="files-path" path="." />
<!--表示 content.getCacheDir() 獲取到的目錄-->
<cache-path name="cache-path" path="." />
<!--表示 ContextCompat.getExternalFilesDirs() 獲取到的目錄-->
<external-files-path name="external_file_path" path="." />
<!--表示 ContextCompat.getExternalCacheDirs() 獲取到的目錄-->
<external-cache-path name="external_cache_path" path="." />
<!--支持根路徑-->
<root-path name="root_path" path="" />
</paths>
TAG | Value | Path |
---|---|---|
TAG_ROOT_PATH | root-path | / |
TAG_FILES_PATH | files-path | /data/data/<包名>/files |
TAG_CACHE_PATH | cache-path | /data/data/<包名>/cache |
TAG_EXTERNAL | external-path | /storage/emulate/0 |
TAG_EXTERNAL_FILES | external-files-path | /storage/emulate/0/Android/data/<包名>/files |
TAG_EXTERNAL_CACHE | external-cache-path | /storage/emulate/0/Android/data/<包名>/cache |
(3)將 file:// 轉(zhuǎn)為 content://
使用 FileProvider.getUriForFile() 方法將 file://
轉(zhuǎn)為 content://
// File 轉(zhuǎn) Uri
private Uri getUriForFile(File file) {
String packageName = getPackage(this).packageName;
Uri contentUri = FileProvider.getUriForFile(this, packageName+".fileProvider", file);
return contentUri;
}
// 獲取當(dāng)前包名
public static PackageInfo getPackage(Context context) {
PackageManager manager = context.getPackageManager();
try {
PackageInfo info = manager.getPackageInfo(context.getPackageName(), 0);
return info;
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
return null;
}
}