Android适配总结之FileProvider

作者: WangGavin | 来源:发表于2019-02-17 18:05 被阅读10次
    日常夜拍

    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/> 代表设备的根目录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

    调用系统拍照,构造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 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获取。

    相关文章

      网友评论

        本文标题:Android适配总结之FileProvider

        本文链接:https://www.haomeiwen.com/subject/iwtneqtx.html