IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> Android 选择文件哪些事 -> 正文阅读

[移动开发]Android 选择文件哪些事

开发 App 免不了要和文件打交道,选择文件是最基本的功能。Android 的 Api 和权限管理比较粗犷,特别是在 6.0 之前,该有的 Api 都有,但是很少有人用,这里特指 ContentProvider,Android 在 6.0 才有运行时权限,之前对文件访问可以说是不设限,所以大家都使用 File Api 代替 ContentProvider,即使选择文件的时候返回的都是 content:// 打头的地址,也要费劲心机转换成文件路径。这一点从 Android 7.0 被打破了,因为系统直接限制了 file:// 开头的 Uri 在应用间传递,这时大家才注意到 FileProvider 这个东西,才明白 App 间文件共享使用 ContentProvider 才是正道。

至于大家为什么喜欢使用 File Api,因为太简单了,而且通用,都是 Java 代码,和 Android 平台无关,App 间分享文件,传一个文件路径就行。但是自从引入了运行时权限,文件分享变的复杂了起来,A 应用分享一个文件路径给 B 应用,B 需要申请权限才能读取,如果分享的文件在 A 的私有目录,B 就算申请了读取 sdcard 的权限也访问不了。使用 ContentProvider 就没有这个问题了,A 给 B 分享文件的时候发送的是一个 content:// 开头的 uri,A 成为了一个内容提供者,B 读取 A 提供的内容,分享文件的操作变成了进程间通信,只要 A 有权限的文件都能分享给 B,B 读取 A 分享的内容无需申请运行时权限。

那么上面的例子中 B 能不能在接收到 content:// 地址后转换为文件路径来处理呢?答案非常肯定,不能!这要基于两个方面,一是权限,B 使用 ContentProvider Api 读取用的是进程间通信,权限由 A 授予,使用 File Api 的话权限需要系统授予;二是协议,ContentProvider 的精髓就是使用的时候不用知道提供者是谁,进来的 Uri 除了开头是 content://,其他的都不一样,也可能提供内容的一方根本就没有对应的文件,例如云盘里面的文件,

Android 选择文件有三个 Action,分别是

  • Intent.ACTION_PICK
  • Intent.ACTION_GET_CONTENT
  • Intent.ACTION_OPEN_DOCUMENT

ACTION_PICK 和 ACTION_GET_CONTENT 在 Api Level 1 中添加,ACTION_OPEN_DOCUMENT 是在 Api Level 19(Android 4.4) 添加。从添加时间来看,ACTION_OPEN_DOCUMENT 比其他两个要新,强迫症一定要用最新的,可是 ACTION_PICK 和 ACTION_GET_CONTENT 并没有被标为 Deprecated,所以 ACTION_OPEN_DOCUMENT 只是对现有功能的扩展。

ACTION_PICK

ACTION_PICK 只能选择单个内容,使用 ACTION_PICK 只需要设置 type,代码如下:

val intent = Intent(Intent.ACTION_PICK)
intent.type = input
activity.startActivity(intent, 1)

intent 不要加 Intent.ACTION_OPEN_DOCUMENT,会报 No Activity found to handle Intent 异常。

如果 type 被多个 App 匹配,会弹列表别供用户选择,如图所示:

在这里插入图片描述

ACTION_PICK 的缺点是只能选择一个内容,但是选择图片的时候是直接打开相册,界面对用户比较友好,其他两个 Action 目前是打开文件 App,不如相册直观。

ACTION_GET_CONTENT 和 ACTION_OPEN_DOCUMENT

这两个 Action 很相似,在高版本上都是通过打开文件 App 选择文件,都能选择多个文件,那么他们的不同点是什么呢?官网有这样的描述:

  • 如果您只想让应用读取/导入数据,请使用 ACTION_GET_CONTENT。使用此方法时,应用会导入数据(如图片文件)的副本。
  • 如果您想让应用获得对文档提供程序所拥有文档的长期、持续访问权限,请使用 ACTION_OPEN_DOCUMENT。例如,照片编辑应用可让用户编辑存储在文档提供程序中的图片。

ACTION_OPEN_DOCUMENT 仅限于存储访问框架,具体参考官网的文档。简单来说,存储访问框架有一个客户端,使用 ACTION_OPEN_DOCUMENT 只能在这个客户端内选择文件。其他 App 可以向存储访问框架提供内容,存储访问框架会按照 type 去其他 App读取内容,并显示在自己的客户端中。

ACTION_OPEN_DOCUMENT 的使用方法如下:

val intent = Intent(Intent.ACTION_OPEN_DOCUMENT)
intent.type = "*/*"
activity.startActivity(intent, 1)

打开的界面如下图所示:

在这里插入图片描述

这个界面其实就是系统内置的一个文件管理器,点击“浏览其他应用中的文件”,不会跳转到其他应用,只会打开一个新的界面显示其他应用提供的文件。

如果使用 ACTION_GET_CONTENT,代码变为:

val intent = Intent(Intent.ACTION_OPEN_DOCUMENT)
intent.type = "*/*"
activity.startActivity(intent, 1)

打开的界面如下:

在这里插入图片描述

猛一看和 ACTION_OPEN_DOCUMENT 打开的界面一样,但是应用那一列多了通讯录和相册,点击图标会打开通信录和相册 App,这一点 ACTION_OPEN_DOCUMENT 是做不到的,它只能在自己的客户端操作。

ACTION_GET_CONTENT 选择文件的时候必须添加 Intent.CATEGORY_OPENABLE,只有添加了 Intent.CATEGORY_OPENABLE 才能确保返回的 Uri 能够使用 contentResolveropenInputStream(uri)

ACTION_OPEN_DOCUMENT 官方不建议添加 Intent.CATEGORY_OPENABLE,因为 Android 7.0 开始支持虚拟文件,如果添加了 Intent.CATEGORY_OPENABLE 就不能选择虚拟文件了,可以通过以下代码判断 Uri 是否是虚拟文件:

private fun isVirtualFile(uri: Uri): Boolean {
    if (!DocumentsContract.isDocumentUri(this, uri)) {
        return false
    }

    val cursor: Cursor? = contentResolver.query(
        uri,
        arrayOf(DocumentsContract.Document.COLUMN_FLAGS),
        null,
        null,
        null
    )

    val flags: Int = cursor?.use {
        if (cursor.moveToFirst()) {
            cursor.getInt(0)
        } else {
            0
        }
    } ?: 0

    return flags and DocumentsContract.Document.FLAG_VIRTUAL_DOCUMENT != 0
}

之后可以将虚拟文件转换为其他 MINE 类型来使用,代码如下:

@Throws(IOException::class)
private fun getInputStreamForVirtualFile(
        uri: Uri, mimeTypeFilter: String): InputStream {

    val openableMimeTypes: Array<String>? =
            contentResolver.getStreamTypes(uri, mimeTypeFilter)

    return if (openableMimeTypes?.isNotEmpty() == true) {
        contentResolver
                .openTypedAssetFileDescriptor(uri, openableMimeTypes[0], null)
                .createInputStream()
    } else {
        throw FileNotFoundException()
    }
}

大部分 App 在选择文件的时候还没有处理到虚拟文件这一层,如果只是想简单的选择文件,可以通过给 ACTION_OPEN_DOCUMENT 添加 Intent.CATEGORY_OPENABLE 避免不必要的麻烦。

选择多个文件

ACTION_GET_CONTENT 和 ACTION_OPEN_DOCUMENT 支持选择多个文件,方式都是在新建 Intent 时添加 putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true),选择多个文件后返回的 Uri 要从 ClipData 中读取,示例代码如下:

fun getClipDataUris(intent: Intent): List<Uri> {
    val resultSet = LinkedHashSet<Uri>()

    intent.data?.let {
        resultSet.add(it)
    }
    val clipData = intent.clipData
    if (clipData == null && resultSet.isEmpty()) {
        return emptyList()
    } else if (clipData != null) {
        for (i in 0 until clipData.itemCount) {
            val uri = clipData.getItemAt(i).uri
            if (uri != null) {
                resultSet.add(uri)
            }
        }
    }
    return ArrayList(resultSet)
}

关于权限

同时以上方式选择文件不需要申请权限,获取的 Uri 可以通过 contentResolver.openInputStream(uri) 来读取,如果选择的是图片文件,Glidd、Fresco 可以直接通过 Uri 加载。选择的 Uri 在 Activity 关闭之前都可以使用,如果希望一直保留文件的权限,必须使用 ACTION_OPEN_DOCUMENT,并且调用 takePersistableUriPermission(),如下所示:

val takeFlags: Int = Intent.FLAG_GRANT_READ_URI_PERMISSION or
    Intent.FLAG_GRANT_WRITE_URI_PERMISSION
contentResolver.takePersistableUriPermission(it, takeFlags)

需要注意有的 Uri 没有写权限,调用 takePersistableUriPermission() 的时候不能添加 Intent.FLAG_GRANT_WRITE_URI_PERMISSION

总结

选择文件还是优先使用 ACTION_GET_CONTENT,支持多选,可以打开相册来选择,并且还支持选择文件以外的类型。如果需求仅仅是选择单张图片,可以使用 ACTION_PICK。如果需要持有选择文件的权限,只能使用 ACTION_OPEN_DOCUMENT。

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2021-10-01 17:00:30  更:2021-10-01 17:00:32 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 20:47:47-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码