上篇文章了解到应用级文件只能被其所创建的应用程序所访问,那么其他应用程序是不是就无论如何都无法访问了呢?肯定不是的,只要文件经过其创建的应用程序授权,还是可以被其他应用程序所访问的。这也就是应用级文件的共享。

系统只允许共享包含实际数据的纯文件类型,而不推荐共享包含文件的目录类型。

对于文件的访问可以使用java.io.File系统文件类,但是如果想将该文件分享出去,则需要借助android.net.Uri路径定位符类。Uri类是Android系统下自定义的路径定位符规则,其符合Java中定义的java.net.URI标准资源定位符规范,并新增了getPathSegments()获取分割路径列表的方法和getQueryParameter(String key)获取指定的额外参数方法等。

通常思路是将要分享的File文件对象转换成特定的Uri路径定位符对象。在Android7即API 24版本及以后的版本中,系统对Uri增加了授权处理,这将在后文详细解释。

之后将该Uri对象可以作android.os.Bundle数据对象通过android.content.Intent意图类传递。

最终在接收到意图的应用程序中,可以收到Uri路径并对其做不同处理,即可访问该路径下的文件,针对Uri中的内容不同,一般需要做不同的转换处理,这将在后文详细解释。

文件分享方

目标版本级别小于24的应用程序

在Android7即API 24版本以前,应用程序内的任何File对象,都可以通过静态方法Uri.fromFile(File file)将参数 file 直接转换为Uri对象。

得到的Uri对象可直接操作赋值给Intent意图对象的setData(Uri data)方法中的参数 data ,或putExtra(String key, Parcelable value)方法中的参数 value,从而分享传递出去。

目标版本级别大于等于24的应用程序

从Android7即API 24版本开始,应用程序内部共享文件,仍然可以使用Uri.fromFile(File file)方法,但是如果想在应用程序中对得到的Uri对象直接传递给其他应用程序使用,会抛出android.os.FileUriExposedException异常。因此,在使用androidx依赖包的应用程序中可以借助androidx.core.content.FileProvider文件分享提供类,在使用support-v4依赖包的应用程序中可以借助android.support.v4.content.FileProvider文件分享提供类,获取相关Uri对象,并为其授权,之后才可在不同应用程序间共享访问。

对于FileProvider的使用,要先做准备工作。

首先需要在应用程序的清单文件中注册。在<application></application>标签中增加<provider></provider>标签。

在该标签中指定属性android:name="androidx.core.content.FileProvider"以绑定文件分享提供类;

另外在该标签中指定属性android:authorities="xxx",这里的属性值xxx是应用程序所在系统内唯一的,其定义规则通常建议以应用程序包名为前缀的域名,且在后文代码中有使用;

同时在改标签中指定属性android:grantUriPermissions="true",属性值为 true 时,表示允许对得到的Uri所定位的File文件授予临时读写权限。

最后在改标签中增加<meta-data/>标签,以指定所使用的数据信息。在该数据标签中,必须定义其属性为android:name="android.support.FILE_PROVIDER_PATHS",同时定义属性android:resource="@xml/file_paths" ,这里的属性值@xml/file_paths是指向定义的资源文件file_paths.xml

在自定义的资源目录 res 下创建子目录 xml,该目录中创建文件 file_paths.xml 。定义<paths></paths>根标签以指定要分享的文件路径。在根标签中可以根据要分享的文件所属路径不同而使用不同类型的标签名作为子标签插入,子标签中包含有namepath两个属性,分别设置该路径的别名和子目录。其中子标签可用类型与代码中获取路径的对应关系可参考下表。

代码获取路径 子标签名
context.getFilesDir() <files-path>
context.getCacheDir() <cache-path>
Environment.getExternalStorageDirectory() <external-path>
context.getExternalMediaDirs() <external-media-path>
context.getExternalFilesDir() <external-file-path>
context.getExternalCacheDir() <external-cache-path>

在清单文件中注册FileProvider之后,就可以在代码获取Uri的地方,与低版本中获取Uri的方法Uri.fromFile(File file)所不同的是,替换为FileProvider的静态方法getUriForFile(Context context, String authority, File file)。参数 context 是当前应用程序所在的上下文环境;参数 authority 则是在清单文件中注册FileProvider时,属性android:authorities所表示的值;参数 file 依然是应用程序中要分享的文件对象。

在使用FileProvider.getUriForFile(Context context, String authority, File file)获取到Uri之后,需要授权给要使用该路径的应用程序,在能获取到上下文环境Context对象的地方,调用grantUriPermission (String toPackage, Uri uri, int modeFlags)方法授权。参数 toPackage 即为要授权使用该路径的应用程序所在包名;参数 uri 是上文获取到的路径定位符对象;参数 modeFlags 是用来表示权限类型的标志位,其值目前包括四种:Intent.FLAG_GRANT_READ_URI_PERMISSION=1的读权限,Intent.FLAG_GRANT_WRITE_URI_PERMISSION=2的写权限,Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION=4表示长久授权,和Intent.FLAG_GRANT_PREFIX_URI_PERMISSION=8对匹配参数 uri 的前缀的所有路径定位符均授权。

在对Uri对象授权之后,也就可以像老版本一样将其封装到Intent意图对象中发送给其他应用了。

文件接收方

一般地,在分享文件的接收方,如果只是对接收到的文件内容做读写处理,可以借助java.io.FileDescriptor文件描述类,使用官方推荐的简单方式操作。

在接收方收到的Intent意图对象调用getData()方法,得到要接收的文件所对应的Uri路径定位符对象。

在可以使用上下文环境Context对象的位置,调用其getContentResolver()方法,获取上下文环境中的ContentResolver内容解析类对象。

借助ContentResolver对象的openFileDescriptor (Uri uri, String mode)方法,返回android.os.ParcelFileDescriptor系统封装的文件描述类对象。其中参数 uri 就是上文接收到的路径定位符对象;参数 mode 则标记了文件的打开方式,包括只读权限"r",只写权限"w",追加写入权限"wa",读写权限"rw"等。

在得到ParcelFileDescriptor对象后,调用其getFileDescriptor()方法,可以获得FileDescriptor文件描述对象。

最终通过得到的文件描述对象,创建FileInputStream(FileDescriptor fdObj)构造方法或FileOutputStream(FileDescriptor fdObj)构造方法获取文件输入流对象或文件输出流对象,以此操作文件内容。

还有些特殊需求,对于分享文件接收方来说,是要对收到的文件获取其所在目录并做进一步操作,也就是需要对得到的Uri路径定位符对象转换为File文件对象。这方面官方并没有像上文那样简单统一的调用方式,而需要开发者针对不同的版本做单独处理。

目标版本级别小于29的应用程序

在Android10即API 29之前的版本中,网上有一堆代码可参考,其思路就是查询系统数据库中不同媒体文件是否与Uri对象所指向位置匹配,进而确定该Uri对象指向的媒体文件目录,在应用程序内借助上下文环境获取其媒体文件所在目录File对象,从而匹配要查询的Uri路径定位符对象。

目标版本级别大于等于29的应用程序

从Android10及API 29版本开始,由于应用程序仅可访问自己分区存储内部的文件,对于收到的分享文件路径定位符Uri对象,也只能先使用上文官方推荐的读取文件方式,将Uri对象所指向的文件先保存一份到自己应用程序内部的私有目录下,再将这份私有目录下的保存文件转为File对象使用。

Android系统编程入门系列之应用级文件在应用程序间的共享的更多相关文章

  1. Android系统编程入门系列之应用数据文件化保存

    应用中关于数据的持久化保存,不管是简单的SharedPreferences还是数据库SQLiteDatabase,本质上都是将数据保存到系统的某种类型的文件中.因此可以直接使用java.io.File ...

  2. Android系统编程入门系列之加载界面Activity

    上回说到应用初始化加载及其生命周期,在Android系统调用Applicaiton.onCreate()之后,继续创建并加载清单文件中注册的首个界面即主Activity,也可称之为入口界面.主Acti ...

  3. Android系统编程入门系列之应用初始化Application

    在上一篇文章中我们了解到Android系统启动应用的时候,会首先加载AndroidManifest.xml清单文件中的一系列信息,在清单文件中如果不指定<application></ ...

  4. Android系统编程入门系列之清单文件

    在上一篇文章中已经提到,Android系统加载应用程序之后,首先会读取该应用程序的AndroidManifest.xml清单文件,之后根据该清单文件加载后边的东西.所以要开发应用程序,自然要先知道清单 ...

  5. Android系统编程入门系列之应用环境及开发环境介绍

        作为移动端操作系统,目前最新的Android 11.0已经发展的比较完善了,现在也到了系统的整理一番的时间,接下来的系列文章将以Android开发者为中心,争取用归纳总结的态度对初级入门者所应 ...

  6. Android系统编程入门系列之应用内键值对数据的简单保存

    在应用程序间及与用户的通信交互过程中,会产生并传递一系列数据.针对这些数据,有部分是只在应用程序中使用的缓存数据,还有一部分是在不同位置多次或长时间使用的持久化数据. 对于缓存数据来说,通常以代码中定 ...

  7. Android系统编程入门系列之应用内数据保存数据库

    上篇文章已经介绍了如何使用SharedPreferences存储键值对形式的轻量级数据,对于那些相同结构的多组数据,类似于存储Java中定义的类的多个对象属性值,如果按照键值对的形式一条条读写,需要分 ...

  8. Android系统编程入门系列之界面Activity绘制展示

    上篇文章介绍了界面Activity的启动方式和生命周期,本篇将继续介绍在界面Activity中的内容是如何绘制展示给用户的. 在Android系统上运行新创建的界面Activtiy,给用户展示的是空白 ...

  9. Android系统编程入门系列之界面Activity交互响应

    在上篇文章中已经了解到界面Activity的绘制完全依赖其加载的视图组件View,不仅如此,用户的每次触摸操作都可以在界面Activity内接收并响应,也可以直接传递给其中的某个视图View响应.本文 ...

随机推荐

  1. wpf实现轮播效果

    在web上面轮播非常常见 WPF中似乎要自己搞,那么我依葫芦画瓢搞一个 如下,平时按一定的时间轮播,点击右下角的灰色圆点(不是很明显0.0),则切换到对应图片  先放 源码:https://gitee ...

  2. C# wpf中DataGrid 支持汇总行

    最近有一个需求,需要汇总金额,份额等字段.我们使用的是原生的WPF控件,自己开发了一套Template.而没有使用比较成熟的第三方控件.所以这个功能得自己开发.并且要做成控件层次的功能. 当然也可以这 ...

  3. Commons-Collections(二)之set

    MultiSet set我们都知道,它是无序的,并且是不允许出现重复元素的.但有些场景我们不需要顺序,但是我们需要知道指定key出现的个数(比如每样产品ID对应的剩余数量这种统计信息),那么用Mult ...

  4. 关于Ubuntu18.04 linux系统使用搜狗输入法 出现乱码

    解决: 执行下面的命令即可!无需重启系统 killall fcitx

  5. vue2.0中模拟数据的配置

    在开发过程中,有时候接口跟不上我们的进度,我们要测试,就需要自测. 现在vue已经升级到2.0版本了,早期在vue构建工程文件在build里面有dev-server.js,但是后来构建去除了该文件集成 ...

  6. 二、vue组件化开发(轻松入门vue)

    轻松入门vue系列 Vue组件化开发 五.组件化开发 1. 组件注册 组件命名规范 组件注册注意事项 全局组件注册 局部组件注册 2. Vue调试工具下载 3. 组件间数据交互 父组件向子组件传值 p ...

  7. MySQL常用权限操作

    MySQL常用权限操作 ** ubuntu mysql 8.0.21修改root密码 ** 1.查看默认安装密码: sudo cat /etc/mysql/debian.cnf 2. 登录mysql ...

  8. kettle 乱码问题处理方案

    一.同下图加上 "-Dfile.encoding=UTF-8" ,两都都加没有试过,可先加一处,如果没有处理到问题,再加另外一处

  9. etcd学习(7)-etcd中的线性一致性实现

    线性一致性 CAP 什么是CAP CAP的权衡 AP wihtout C CA without P CP without A 线性一致性 etcd中如何实现线性一致性 线性一致性写 线性一致性读 1. ...

  10. Java编程:为什么Class实例可以不是全局唯一

    通过定义两个类加载器加载同一字节码文件来证明Class实例为什么不是全局唯一的 1.将一个名为Demo(没有后缀)的字节码文件放在D盘根目录 2.定义两个类加载器 自定义ClassLoader三要素: ...