Android適配總結之FileProvider

日常夜拍
1 概述
由於前段時間做的應用都是車機上的(Android6.0),並不需要太多適配。想想到現在,Android已經到9了,貌似我好久都沒做過適配了,這些適配的工作還是有必要做一些記錄。
參考資料
通常開發中,適配Android7.0主要就是適配其中檔案訪問許可權。比如呼叫系統拍照,應用更新後安裝功能都是涉及到。
在官方7.0的以上的系統中,嘗試傳遞 file://URI可能會觸發FileUriExposedException
本文內容主要參考鴻洋_的文章,僅作記錄。
2 使用FileProvider
2.1 宣告provider,設定檔案
在清單檔案中宣告provider標籤,如:
<provider android:name="android.support.v4.content.FileProvider" android:authorities="me.newtrekwang.android7.fileprovider" android:exported="false" android:grantUriPermissions="true"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths" /> </provider>
android:authorities
表示授權者,這裡的格式一般是 [appId].fileprovider
android:exported
只能為false
android:grantUriPermissions="true"
表示授權Uri許可權 ,且必須為true
meta-data
裡設定指定的檔案目錄,為引用某個xml檔案,格式如下
例如:file_paths.xml
<?xml version="1.0" encoding="utf-8"?> <paths xmlns:android="http://schemas.android.com/apk/res/android"> <root-path name="root" path="" /> <files-path name="files" path="" /> <cache-path name="cache" path="" /> <external-path name="external" path="" /> <external-files-path name="name" path="path" /> <external-cache-path name="name" path="path" /> </paths>
<root-path/> <files-path/> <cache-path/> <external-path/> <external-files-path> <external-cache-path>
比如我常用的:
<?xml version="1.0" encoding="utf-8"?> <paths> <!--儲存卡的Pictures目錄放圖片檔案--> <external-path name="my_images" path="Pictures"/> <!--儲存卡的flyaudiosmart2019/apk目錄放安裝包檔案--> <external-path name="my_download" path="flyaudiosmart2019/apk"/> </paths>
2.2 使用FileProvider API
呼叫系統拍照,構造Intent就需要傳入一個Uri,那麼Uri就必須使用FileProvider來獲取,例如:
// 檔名 String filename = new SimpleDateFormat("yyyyMMdd-HHmmss", Locale.CHINA) .format(new Date()) + ".png"; // file物件,注意路徑要和resource xml裡配置的一樣 File file = new File(Environment.getExternalStorageDirectory()+File.separator+"Pictures", filename); // 生成Uri Uri fileUri = FileProvider.getUriForFile(this, "me.newtrekwang.android7.fileprovider", file); takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, fileUri);
其實Uri的實際內容為: content://me.newtrekwang.android7.fileprovider/my_images/Pictures/20190210-041411.png
,即格式為 content://authorities/定義的name屬性/檔案的相對路徑
但是這樣統一用FileProvider獲取Uri是不夠的,在Android7可以正常執行,但是Android4.4就會報錯
Caused by: java.lang.SecurityException: Permission Denial: opening provider android.support.v4.content.FileProvider from ProcessRecord{52b029b8 1670:com.android.camera/u0a36} (pid=1670, uid=10036) that is not exported from uid 10052 at android.os.Parcel.readException(Parcel.java:1465) at android.os.Parcel.readException(Parcel.java:1419) at android.app.ActivityManagerProxy.getContentProvider(ActivityManagerNative.java:2848) at android.app.ActivityThread.acquireProvider(ActivityThread.java:4399)
因為低版本的系統,僅僅是把這個當成一個普通的Provider在使用,而我們沒有授權,contentprovider的export設定的也是false;導致Permission Denial。
所以唯一的辦法就是授權了,可以使用Context的 grantUriPermission(packageName, fileUri, Intent.FLAG_GRANT_READ_URI_PERMISSION| Intent.FLAG_GRANT_WRITE_URI_PERMISSION)!
,不過加了步授權,很麻煩。
最簡單的方法是判斷下版本,低版本直接獲取Uri就行了。
即:
Uri fileUri = null; if (Build.VERSION.SDK_INT >= 24) { fileUri = FileProvider.getUriForFile(this, "me.newtrekwang.android7.fileprovider", file); } else { fileUri = Uri.fromFile(file); }
在拍照場景這樣的Uri是不會出問題的,注意:在安裝apk的業務場景下,intent用這樣的Uri還是會報錯,請求安裝的intent,還要加上 intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
,否則還是要步授權設定
3 總結:統一的方案:單獨一個Module做FileProvder配置
1 建立一個 library
的Module,然後在該清單檔案定義你的FileProvider,然後在路徑配置檔案中定義專案中所有要用到的路徑。
2 定義一個工具,功能就是獲取合適的Uri
FileProviderUtils.java
/** * @author newtrekWang * @fileName FileProviderUtils * @createDate 2019/2/14 11:47 * @email [email protected] * @desc FileProvider工具類,用於適配Android7.0的檔案訪問變更 */ public final class FileProviderUtils { /** * 獲取合適的File Uri * @param context * @param file * @return */ public static Uri getUriForFile(Context context, File file) { Uri fileUri = null; if (Build.VERSION.SDK_INT >= 24) { fileUri = getUriForFile24(context, file); } else { fileUri = Uri.fromFile(file); } return fileUri; } /** * 獲取File Uri from 安卓7及以上版本 * @param context * @param file * @return */ public static Uri getUriForFile24(Context context, File file) { Uri fileUri = android.support.v4.content.FileProvider.getUriForFile(context, "你自己的authorities", file); return fileUri; } /** * 使用者安裝apk場景 * @param context * @param intent * @param type * @param file * @param writeAble */ public static void setIntentDataAndType(Context context, Intent intent, String type, File file, boolean writeAble) { if (Build.VERSION.SDK_INT >= 24) { intent.setDataAndType(getUriForFile(context, file), type); intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); if (writeAble) { intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION); } } else { intent.setDataAndType(Uri.fromFile(file), type); } }
3 其他有需要的module依賴此module即可,獲取Uri從FileProviderUtils獲取。