AndroidNougat中通過Intents共享檔案,你準備好了嗎?
從 Android 7.0 Nougat 開始,你將不能使用 Intent 傳遞 file:// URI 的方式訪問你主包之外的檔案,但是無需苦惱:下面將介紹如何解決這個問題。
Android 7.0 Nougat 為了提高安全性引入了一些 檔案系統許可權變更。如果你已經將 app 的 targetSdkVersion 升級為 24 (或者更高),並且你通過 Intent 傳遞 file://URI 來訪問你的主包之外的檔案,那麼你將會遇到 FileUriExposedException 的異常。
為什麼會這樣呢?
根據官方文件介紹:
為提高私有檔案的安全性,在 Android 7.0 及以上的應用中的私有目錄有著更嚴格的訪問許可權 (
0700
)。這個設定可以防止私有檔案後設資料的洩漏(比如檔案的大小或者是否存在)。
當你通過 file:// URI方式共享一個檔案時,你同時修改了它的檔案系統許可權,使得它對所有應用都是可訪問的(直到你再次修改它)。毋庸置疑這種方法是不安全的。
Ok, 但是這個問題只會影響 Nougat, 那我現在還需要修復嗎?
長話短說,當然需要。
確實,目前來說這個問題並不會影響很大範圍的 Android 裝置,但是這不僅僅是你不採用新特性的問題 —— 如果不解決,在 Nougat 裝置上會崩潰,並且在以前的版本上是不安全的。而且修復這個問題並不困難,所以在你的應用發生奔潰以及你的使用者開始抱怨之前,修復這個問題確實是值得的。
是時候亮程式碼了
最典型的例子(我也是通過它發現的這種問題),是當拍照時你給相機傳遞了一個檔案 URI 來獲取拍照後的照片。如果你想具體看看,在本文的結尾你可以找到一個 GitHub 程式碼庫。
我們建立了一個檔案,並把檔案的 URI 傳給了 Intent 來從相機應用接收檔案(我們應用主包之外的路徑)。這段程式碼在 Marshmallow 或更低版本上是正常的,在 Nougat、 SDK 24 版本或更高的版本,你會遇到類似下面的堆疊資訊:
02-06 17:30:00.476 22265-22265/com.quiro.fileproviderexample E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.quiro.fileproviderexample, PID: 22265
android.os.FileUriExposedException: file:///storage/emulated/0/Pictures/pics/JPEG_20170206_173000966174899.jpg exposed beyond app through ClipData.Item.getUri()
at android.os.StrictMode.onFileUriExposed(StrictMode.java:1799)
at android.net.Uri.checkFileUriExposed(Uri.java:2346)
at android.content.ClipData.prepareToLeaveProcess(ClipData.java:845)
at android.content.Intent.prepareToLeaveProcess(Intent.java:8941)
at android.content.Intent.prepareToLeaveProcess(Intent.java:8926)
at android.app.Instrumentation.execStartActivity(Instrumentation.java:1517)
at android.app.Activity.startActivityForResult(Activity.java:4225)
at android.support.v4.app.BaseFragmentActivityJB.startActivityForResult(BaseFragmentActivityJB.java:50)
at android.support.v4.app.FragmentActivity.startActivityForResult(FragmentActivity.java:79)
at android.app.Activity.startActivityForResult(Activity.java:4183)
at android.support.v4.app.FragmentActivity.startActivityForResult(FragmentActivity.java:859)
at com.quiro.fileproviderexample.MainActivity.takePicture(MainActivity.java:70)
at com.quiro.fileproviderexample.MainActivity$1.onClick(MainActivity.java:42)
at android.view.View.performClick(View.java:5637)
at android.view.View$PerformClick.run(View.java:22429)
at android.os.Handler.handleCallback(Handler.java:751)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:154)
at android.app.ActivityThread.main(ActivityThread.java:6119)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:886)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:776)
解決方案 —— FileProvider
FileProvider 是 ContentProvider 的子類,FileProvider 允許我們使用 content:// URI 的方式取代 file:// 實現檔案的安全共享。為什麼這種方法更好?因為你為檔案賦予了臨時的訪問許可權 —— 僅僅允許接收者 activity 和 service 執行時才能訪問。
首先,我們在 AndroidManifest.xml 中新增 FileProvider
<manifest>
...
<application>
...
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="@string/file_provider_authority"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_provider_paths" />
</provider>
...
</application>
</manifest>
我們將 android:exported
設定為禁止,因為我們不需要在其他應用使用;將 android:grantUriPermissions
設定為允許,因為這樣才能給予檔案臨時訪問許可權;以及通過 android:authorities
設定管理的域。如果你的域為com.quiro.fileproviderexample
,你可以使用類似 com.quiro.fileproviderexample.provider
的內容來訪問。提供者的授權標識應該是唯一的,所以我們往往會使用應用的包名加上類似 .fileprovider: 的內容。
<string name="file_provider_authority"
translatable="false">com.quiro.fileproviderexample.fileprovider</string>
接下來我們需要在 res/xml 目錄下建立 file_provider_path。這個檔案用來定義允許安全共享的檔案目錄。在我們的例子中,我們只需要訪問外部儲存目錄:
<?xml version="1.0" encoding="utf-8"?>
<paths>
<external-path
name="external_files" path="." />
</paths>
最後,修改我們的程式碼
用 FileProvider.getUriForFile(context, string, file)
的方式取代 Uri.fromFile(file)
來建立我們的 URI,FileProvider.getUriForFile(context, string, file)
會生成一個有許可權訪問我們所指向檔案的 content://* URI。
接收者應用通過呼叫 ContentResolver.openFileDescriptor 來訪問檔案。在我們程式碼中 Intent
是供相機應用使用的,所以我們無需新增其他程式碼。
相關文章
- 【準備工作—你做好了嗎?】
- AI校園來了,你準備好了嗎?AI
- 寫程式碼前的準備,你做好了嗎?
- Android9.0新特性曝光,你準備好了嗎Android
- 大資料2.0再掀狂潮 你準備好了嗎?大資料
- 程式設計師想月薪過萬?這些面試準備你做好了嗎?程式設計師面試
- 新Web時代,您準備好了嗎?Web
- SFFAI召集人蓄勢待發!你準備好了嗎?AI
- 智慧樓宇辦公時代來啦!你準備好了嗎?
- 挑戰高薪!學習人工智慧,你準備好了嗎?高薪人工智慧
- 【程式設計師創業】創業,你準備好了嗎?程式設計師創業
- 工信部網路安全大檢查來襲,你準備好了嗎?
- laravel8升級到laravel9,你準備好了嗎Laravel
- 深入理解JVM垃圾收集機制,下次面試你準備好了嗎JVM面試
- 2019年9大AI趨勢,你準備好了嗎?AI
- 雙十一即將到來,你的網站真的準備好了嗎?網站
- 傳統企業玩網際網路你真的準備好了嗎?
- 德勤諮詢:與機器人老闆共舞,你準備好了嗎?機器人
- 機器取代人工作的時代來了,你準備好了嗎?
- 扒一扒JVM的垃圾回收機制,下次面試你準備好了嗎JVM面試
- 程式設計師,為未來準備好了嗎?程式設計師
- 大學生想進入IT行業,這7項準備你做好了嗎?行業
- 未來一個月,騰訊將連發7款遊戲,你準備好了嗎?遊戲
- 未來工作報告:亞太地區準備好了嗎
- 區塊鏈-未來網路資料鏈時代的潮流,你準備好了嗎?區塊鏈
- 從天而降的AI“青雲梯”,開發者們準備好了嗎?AI
- 15個 MySQL 基礎面試題,DBA 們準備好了嗎?MySql面試題
- 準備好了嗎?爆iPhone 6s釋出時間iPhone
- 通過android:ssp高效過濾Android IntentsAndroidIntent
- 你的備庫做好準備了嗎
- 女神節到來,影樓的宣傳工作準備好了嗎
- 能救命的Apple Watch和雙卡雙待的iPhone來了,你準備好了嗎?APPiPhone
- ??想快速進入人工智慧領域的Java程式設計師?你準備好了嗎?人工智慧Java程式設計師
- 適配可摺疊裝置,您的應用準備好了嗎?
- 臨床醫生的實際痛點如何解,AI準備好了嗎?AI
- 通過基準配置檔案改善應用效能
- 從GPT定製到Turbo升級再到Assistants API,未來AI世界,你準備好了嗎?GPTAPIAI
- 準備好了嗎?人工智慧已經到來!–(附報告下載)人工智慧