Android適配總結(jié)之FileProvider

日常夜拍

1 概述

由于前段時間做的應(yīng)用都是車機上的(Android6.0),并不需要太多適配。想想到現(xiàn)在,Android已經(jīng)到9了,貌似我好久都沒做過適配了,這些適配的工作還是有必要做一些記錄。

參考資料

通常開發(fā)中,適配Android7.0主要就是適配其中文件訪問權(quán)限。比如調(diào)用系統(tǒng)拍照,應(yīng)用更新后安裝功能都是涉及到。

在官方7.0的以上的系統(tǒng)中,嘗試傳遞 file://URI可能會觸發(fā)FileUriExposedException

本文內(nèi)容主要參考鴻洋_的文章,僅作記錄。

2 使用FileProvider

2.1 聲明provider,設(shè)置文件

在清單文件中聲明provider標(biāo)簽,如:

<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表示授權(quán)者,這里的格式一般是[appId].fileprovider
android:exported只能為false
android:grantUriPermissions="true"表示授權(quán)Uri權(quán)限 ,且必須為true

meta-data里設(shè)置指定的文件目錄,為引用某個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/> 代表設(shè)備的根目錄new File("/");
  • <files-path/> 代表context.getFilesDir()
  • <cache-path/> 代表context.getCacheDir()
  • <external-path/> 代表Environment.getExternalStorageDirectory()
  • <external-files-path>代表context.getExternalFilesDirs()
  • <external-cache-path>代表getExternalCacheDirs()

比如我常用的:

<?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

調(diào)用系統(tǒng)拍照,構(gòu)造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的實際內(nèi)容為:content://me.newtrekwang.android7.fileprovider/my_images/Pictures/20190210-041411.png,即格式為content://authorities/定義的name屬性/文件的相對路徑

但是這樣統(tǒng)一用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)

因為低版本的系統(tǒng),僅僅是把這個當(dāng)成一個普通的Provider在使用,而我們沒有授權(quán),contentprovider的export設(shè)置的也是false;導(dǎo)致Permission Denial。

所以唯一的辦法就是授權(quán)了,可以使用Context的grantUriPermission(packageName, fileUri, Intent.FLAG_GRANT_READ_URI_PERMISSION| Intent.FLAG_GRANT_WRITE_URI_PERMISSION)!,不過加了步授權(quán),很麻煩。

最簡單的方法是判斷下版本,低版本直接獲取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的業(yè)務(wù)場景下,intent用這樣的Uri還是會報錯,請求安裝的intent,還要加上intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION),否則還是要步授權(quán)設(shè)置

3 總結(jié):統(tǒng)一的方案:單獨一個Module做FileProvder配置

1 創(chuàng)建一個library的Module,然后在該清單文件定義你的FileProvider,然后在路徑配置文件中定義項目中所有要用到的路徑。

2 定義一個工具,功能就是獲取合適的Uri

FileProviderUtils.java

/**
 * @author newtrekWang
 * @fileName FileProviderUtils
 * @createDate 2019/2/14 11:47
 * @email 408030208@qq.com
 * @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獲取。

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

相關(guān)閱讀更多精彩內(nèi)容

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