其实很早之前我的应用就已经兼容到Android7.0了,此次写这个文章就是想详细梳理一下android的文件系统,以及做一下FileProvider的解析。
        Android7.0 (N) 开始,将严格执行 StrictMode 模式,也就是说,将对安全做更严格的校验。而从 Android N 开始,将不允许在 App 间,使用 file:// 的方式,传递一个 File ,否者会抛出 FileUriExposedException的错误,会直接引发 Crash。
        但是,既然官方对文件的分享做了一个这么强硬的修改(直接抛出异常),实际上也提供了解决方案,那就是 FileProvider,通过 content://的模式替换掉 file://,同时,需要开发者主动升级 targetSdkVersion 到 24 才会执行此策略。
        FileProvider是android support v4包提供的,是ContentProvider的子类,便于将自己app的数据提供给其他app访问。
        在app开发过程中需要用到FileProvider的主要有

  1. 相机拍照以及图片裁剪
  2. 调用系统应用安装器安装apk(应用升级)

具体使用的方法
1、配置AndroidManifest文件

                      

authorities:一个标识,在当前系统内必须是唯一值,一般用包名。
exported:表示该 FileProvider 是否需要公开出去。
granUriPermissions:是否允许授权文件的临时访问权限。这里需要,所以是 true。

2、在res的建xml目录,放入provider_paths.xml文件

<?xml version="1.0" encoding="utf-8"?>                                    /paths>

这个配置的标签参照FileProvider里面的TAG配置。

之后FileProvider的path解析策略如下

    private static FileProvider.PathStrategy parsePathStrategy(Context context, String authority) throws IOException, XmlPullParserException {        FileProvider.SimplePathStrategy strat = new FileProvider.SimplePathStrategy(authority);        ProviderInfo info = context.getPackageManager().resolveContentProvider(authority, 128);        XmlResourceParser in = info.loadXmlMetaData(context.getPackageManager(), "android.support.FILE_PROVIDER_PATHS");        if (in == null) {            throw new IllegalArgumentException("Missing android.support.FILE_PROVIDER_PATHS meta-data");        } else {            int type;            while((type = in.next()) != 1) {                if (type == 2) {                    String tag = in.getName();                    String name = in.getAttributeValue((String)null, "name");                    String path = in.getAttributeValue((String)null, "path");                    File target = null;                    if ("root-path".equals(tag)) {                        target = DEVICE_ROOT;                    } else if ("files-path".equals(tag)) {                        target = context.getFilesDir();                    } else if ("cache-path".equals(tag)) {                        target = context.getCacheDir();                    } else if ("external-path".equals(tag)) {                        target = Environment.getExternalStorageDirectory();                    } else {                        File[] externalMediaDirs;                        if ("external-files-path".equals(tag)) {                            externalMediaDirs = ContextCompat.getExternalFilesDirs(context, (String)null);                            if (externalMediaDirs.length > 0) {                                target = externalMediaDirs[0];                            }                        } else if ("external-cache-path".equals(tag)) {                            externalMediaDirs = ContextCompat.getExternalCacheDirs(context);                            if (externalMediaDirs.length > 0) {                                target = externalMediaDirs[0];                            }                        } else if (VERSION.SDK_INT >= 21 && "external-media-path".equals(tag)) {                            externalMediaDirs = context.getExternalMediaDirs();                            if (externalMediaDirs.length > 0) {                                target = externalMediaDirs[0];                            }                        }                    }                    if (target != null) {                        strat.addRoot(name, buildPath(target, path));                    }                }            }            return strat;        }    }

root-path 对应DEVICE_ROOT,也就是File DEVICE_ROOT = new File("/"),即根目录,一般不需要配置。
files-path对应 content.getFileDir() 获取到的目录。
cache-path对应 content.getCacheDir() 获取到的目录
external-path对应 Environment.getExternalStorageDirectory() 指向的目录。
external-files-path对应 ContextCompat.getExternalFilesDirs() 获取到的目录。
external-cache-path对应 ContextCompat.getExternalCacheDirs() 获取到的目录。

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、使用,以安装apk为例

          Intent intent = new Intent(Intent.ACTION_VIEW);            intent.addCategory(Intent.CATEGORY_DEFAULT);            Uri uri;            File file = new File(saveFolder, updateSaveName);            if (Build.VERSION.SDK_INT >= 24) {//android 7.0以上                uri = FileProvider.getUriForFile(activity, BuildConfig.APPLICATION_ID.concat(".provider"), file);            } else {                uri = Uri.fromFile(file);            }            String type = "application/vnd.android.package-archive";            intent.setDataAndType(uri, type);            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);            if (Build.VERSION.SDK_INT >= 24) {                intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);            }            activity.startActivityForResult(intent, 10);

注意点
        经过大量用户使用,后期反馈,在小米6,开启微信分身之后,分身微信保存的图片,使用FileProvider将一张图片的path转成Uri的过程中crash了。这张图片路径如下

/storage/emulated/999/tencent/MicroMsg/WeiXin/mmexport1544062754693.jpg

你一定觉得很奇怪,正常路径是/storage/emulate/0,怎么会有/storage/emulate/999的路径,查找原因是应用分身导致的。之后会抛

 java.lang.IllegalArgumentException: Failed to find configured root that contains /storage/emulated/999/tencent/MicroMsg/WeiXin/mmexport1544062754693.jpg

那个时候,我的代码的xml的path里面是没有配置root-path节点的。debug时,fileProvide的mRoots是5个元素

后面我添加了root-path节点之后,mRoots变成了6个

之后就完美实现了将path转成Uri。
        部分手机可以插外置sdcard,比如红米手机,之后就导致找不到sdcard的root,这时候也是需要配置root-path
        下面在聊一聊Android的文件系统
外部存储的公共目录
DIRECTORY_MUSIC:音乐类型 /storage/emulate/0/music
DIRECTORY_PICTURES:图片类型
DIRECTORY_MOVIES:电影类型
DIRECTORY_DCIM:照片类型,相机拍摄的照片视频都在这个目录(digital camera in memory) /storage/emulate/0/DCIM
DIRECTORY_DOWNLOADS:下载文件类型 /storage/emulate/0/downloads
DIRECTORY_DOCUMENTS:文档类型
DIRECTORY_RINGTONES:铃声类型
DIRECTORY_ALARMS:闹钟提示音类型
DIRECTORY_NOTIFICATIONS:通知提示音类型
DIRECTORY_PODCASTS:播客音频类型

这些可以通过Environment的getExternalStoragePublicDirectory()来获取

public static File getExternalStoragePublicDirectory(String type);

其他的就不写了,好累呀
找了两篇还不错的文章,贴一下,偷个懒
Android文件系统详解
彻底理解android中的内部存储与外部存储
Android文件系统的结构及目录用途、操作方法 整理
后面那位大兄弟,写的很详细,很不错哦!


致敬前辈,砥砺前行!

更多相关文章

  1. JNI和NKD入门系列二,mac环境下配置NDk,并在android studio上进行JN
  2. 关于在Android(安卓)Studio中使用Assets目录下的资源的问题
  3. 简约之美Kotlin(二)Kotlin入门基础知识
  4. Android使用FFmpeg 解码H264并播放(一)
  5. Android(安卓)2.2 源码结构分析
  6. android Uri使用
  7. Ubuntu 下搭建 Android(安卓)开发环境
  8. Android(安卓)ADB 找不到设备的解决方法
  9. Android开机自启动应用开发

随机推荐

  1. hcidump用法
  2. Android(安卓)TV -1.1- Get Started with
  3. android百度地图
  4. androidstudio加载项目问题
  5. android SharedPreferences初步
  6. Android(安卓)Tabs 中使用listview,mapvie
  7. Building the Android(安卓)JDBC Driver
  8. 【Android动画九章】-帧动画
  9. Android(安卓)Binder IPC
  10. Android(安卓)支持的媒体格式(音频,视频,