Android系统编程入门系列之应用级文件在应用程序间的共享
在上篇文章了解到应用级文件只能被其所创建的应用程序所访问,那么其他应用程序是不是就无论如何都无法访问了呢?肯定不是的,只要文件经过其创建的应用程序授权,还是可以被其他应用程序所访问的。这也就是应用级文件的共享。
系统只允许共享包含实际数据的纯文件类型,而不推荐共享包含文件的目录类型。
对于文件的访问可以使用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>
根标签以指定要分享的文件路径。在根标签中可以根据要分享的文件所属路径不同而使用不同类型的标签名作为子标签插入,子标签中包含有name
和path
两个属性,分别设置该路径的别名和子目录。其中子标签可用类型与代码中获取路径的对应关系可参考下表。
代码获取路径 | 子标签名 |
---|---|
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系统编程入门系列之应用级文件在应用程序间的共享的更多相关文章
- Android系统编程入门系列之应用数据文件化保存
应用中关于数据的持久化保存,不管是简单的SharedPreferences还是数据库SQLiteDatabase,本质上都是将数据保存到系统的某种类型的文件中.因此可以直接使用java.io.File ...
- Android系统编程入门系列之加载界面Activity
上回说到应用初始化加载及其生命周期,在Android系统调用Applicaiton.onCreate()之后,继续创建并加载清单文件中注册的首个界面即主Activity,也可称之为入口界面.主Acti ...
- Android系统编程入门系列之应用初始化Application
在上一篇文章中我们了解到Android系统启动应用的时候,会首先加载AndroidManifest.xml清单文件中的一系列信息,在清单文件中如果不指定<application></ ...
- Android系统编程入门系列之清单文件
在上一篇文章中已经提到,Android系统加载应用程序之后,首先会读取该应用程序的AndroidManifest.xml清单文件,之后根据该清单文件加载后边的东西.所以要开发应用程序,自然要先知道清单 ...
- Android系统编程入门系列之应用环境及开发环境介绍
作为移动端操作系统,目前最新的Android 11.0已经发展的比较完善了,现在也到了系统的整理一番的时间,接下来的系列文章将以Android开发者为中心,争取用归纳总结的态度对初级入门者所应 ...
- Android系统编程入门系列之应用内键值对数据的简单保存
在应用程序间及与用户的通信交互过程中,会产生并传递一系列数据.针对这些数据,有部分是只在应用程序中使用的缓存数据,还有一部分是在不同位置多次或长时间使用的持久化数据. 对于缓存数据来说,通常以代码中定 ...
- Android系统编程入门系列之应用内数据保存数据库
上篇文章已经介绍了如何使用SharedPreferences存储键值对形式的轻量级数据,对于那些相同结构的多组数据,类似于存储Java中定义的类的多个对象属性值,如果按照键值对的形式一条条读写,需要分 ...
- Android系统编程入门系列之界面Activity绘制展示
上篇文章介绍了界面Activity的启动方式和生命周期,本篇将继续介绍在界面Activity中的内容是如何绘制展示给用户的. 在Android系统上运行新创建的界面Activtiy,给用户展示的是空白 ...
- Android系统编程入门系列之界面Activity交互响应
在上篇文章中已经了解到界面Activity的绘制完全依赖其加载的视图组件View,不仅如此,用户的每次触摸操作都可以在界面Activity内接收并响应,也可以直接传递给其中的某个视图View响应.本文 ...
随机推荐
- WPF教程(四)RelativeSource属性
我们进行Bingding时,如果明确知道数据源的Name,就能用Source或者ElementName进行绑定,但是有时候我们需要绑定的数据源可能没有明确的Name,此时我们就需要利用Bingding ...
- mybatis传入参数为0被误认为是空字符串的解决方法
在mbatis中使用Xml配置sql语句时,出现了这样一个问题.当我传入的参数为0去做判断时,mybatis会把参数0当成是空字符串去判断而引起查询结果错误 所以在做项目时一定要注意,用到MyBati ...
- 使用dom4工具:增删改xml文件(七)
package dom4j_write; import java.io.File; import java.io.FileOutputStream; import org.dom4j.Attribut ...
- C++回调机制
一直对回调机制不是很了解,今天索性搜了很多资料顺便整理一下,进步一点点. 1.Callback方式(回调函数) 什么是回调函数? 简而言之,回调函数就是一个通过函数指针调用的函数.如果你把函数的指针( ...
- golang context包
go context标准库 context包在Go1.7版本时加入到标准库中.其设计目标是给Golang提供一个标准接口来给其他任务发送取消信号和传递数据.其具体作用为: 可以通过context发送取 ...
- android activity pass data to accessibilityservice 数据传递
不同类型的 service 传递数据的方式不同,accessibilityservice 运行在独立进程,且被系统接管,比较特别 在 AccessibilityService 的 onCreate 内 ...
- 最长回文子序列---DP
问题描述 给定一个字符串s,找到其中最长的回文子序列.可以假设s的最大长度为1000. 解题思路 1.说明 首先要弄清楚回文子串和回文子序列的区别,如果一个字符串是"bbbab", ...
- Python实现Thrift Server
近期在项目中存在跨编程语言协作的需求,使用到了Thrift.本文将记录用python实现Thrift服务端的方法. 环境准备 根据自身实际情况下载对应的Thrift编译器,比如我在Windows系统上 ...
- 第20篇-加载与存储指令之ldc与_fast_aldc指令(2)
ldc指令将int.float.或者一个类.方法类型或方法句柄的符号引用.还可能是String型常量值从常量池中推送至栈顶. 这一篇介绍一个虚拟机规范中定义的一个字节码指令ldc,另外还有一个虚拟机内 ...
- golang sync.noCopy 类型 —— 初探 copylocks 与 empty struct
问题引入 学习golang(v1.16)的 WaitGroup 代码时,看到了一处奇怪的用法,见下方类型定义: type WaitGroup struct { noCopy noCopy ... } ...