赞
踩
本文链接: https://blog.csdn.net/xietansheng/article/details/115763279
打开系统 APP 的资源选择器选取资源(图片/文件),通常可以使用以下 3 个 Action:
一般 Android 系统内置的相关 APP 中均有实现了这 3 个 Action(如: 相册、文件管理),三的均能打开系统 APP 的资源选择器选择资源(图片、视频、文件、通讯录等)并返回,但三者的使用并不完全相同。有些第三方 APP 实现了这 3 个 Action 的,也可以用于选取相应的资源。
一般使用 ACTION_PICK
选择图片,使用 ACTION_GET_CONTENT
或 ACTION_OPEN_DOCUMENT
选择文件。
从数据中选择一个项目(不支持多选),并返回选择的内容,从返回的 intent.getData()
中获取资源,资源类型为 "content://"
开头的 Uri 资源,可通过 context.getContentResolver()
获取资源的内容和相关信息。
Intent.ACTION_PICK 的值为: "android.intent.action.PICK"
启动的 Intent:
val intent = Intent(Intent.ACTION_PICK)
intent.type = "image/*"
activity.startActivityForResult(intent, REQUEST_CODE_PICK)
// 选择视频: intent.type = "video/*";
// 选择所有类型的资源: intent.type = "*/*"
在 Activity.onActivityResult(...)
中接收选取返回的图片资源:
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (resultCode != RESULT_OK && requestCode == REQUEST_CODE_PICK) {
// 获取选取返回的图片资源, 结果为 "content://" 开头的 Uri 格式的资源,
// Uri 格式参考: content://media/external/images/media/123
val uri = data?.data ?: return
// 获取图片的数据, 可以使用 ContentResolver 直接打开输入流
var imageInputStream = contentResolver.openInputStream(uri)
// 查询图片的详细信息
val cursor = contentResolver.query(uri, null, null, null, null)
...
}
}
获取到的
uri
资源一般只能在当前 Activity 实例没有被销毁前被访问,如果当前 Activity 实例已onDestroy()
,访问该uri
可能会报无权限访问 Uri 资源的错误。
(1)先在 AndroidManifest.xml
中添加读取外部存储器的权限:
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<!--
Android 6.0+ 需要动态获取存储权限。一般系统 APP 的内容提供者可以通过 content.getContentResolver()
不需要权限直接读取选择的 Uri 内容。有些则需要权限才能读取,保险起见,读取内容前建议先申请相关权限。
-->
(2)布局文件: res/layout/activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<Button
android:id="@+id/btn_choose_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Choose Image"/>
<ImageView
android:id="@+id/image_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
(3)Activity 代码: MainActivity.kt
package com.xiets.demo
import android.content.Intent
import android.database.Cursor
import android.graphics.BitmapFactory
import android.net.Uri
import android.os.Bundle
import android.provider.MediaStore
import android.util.Log
import android.view.View
import android.widget.ImageView
import androidx.appcompat.app.AppCompatActivity
import java.io.Closeable
import java.io.InputStream
import java.util.*
class MainActivity : AppCompatActivity() {
companion object {
private const val TAG = "MainActivity"
private const val REQUEST_CODE_PICK = 1000
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
findViewById<View>(R.id.btn_choose_image).setOnClickListener {
// 点击按钮: 使用 ACTION_PICK 选择图片,启动 Activity Intent
openSystemImageChooser(REQUEST_CODE_PICK)
}
}
/**
* 使用 ACTION_PICK 选择图片,启动 Activity Intent
*/
private fun openSystemImageChooser(requestCode: Int) {
val intent = Intent(Intent.ACTION_PICK)
intent.type = "image/*"
startActivityForResult(intent, requestCode)
// 选择视频: intent.type = "video/*";
// 选择所有类型的资源: intent.type = "*/*"
}
/**
* 在返回的 onActivityResult 中接收选取返回的图片资源
*/
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (resultCode != RESULT_OK) {
Log.d(TAG, "onActivityResult not ok.")
return
}
if (requestCode == REQUEST_CODE_PICK) {
// 获取选取返回的图片资源, Uri 格式
val uri = data?.data ?: return
// URI 格式参考: content://media/external/images/media/123
Log.d(TAG, "选取的图片: $uri")
// 如果需要使用图片的数据(如解析为 Bitmap 或 上传至服务端),
// 可以使用 ContentResolver 直接打开输入流
var imageInputStream: InputStream? = null
try {
// 打开 Uri 的输入流
imageInputStream = contentResolver.openInputStream(uri)
// 把输入流解析为 Bitmap
val bitmap = BitmapFactory.decodeStream(imageInputStream)
// 显示 Bitmap 到 ImageView
findViewById<ImageView>(R.id.image_view).setImageBitmap(bitmap)
} catch (e: Exception) {
e.printStackTrace()
} finally {
closeStream(imageInputStream)
}
// 查询图片的详细信息
queryUriDetail(uri)
}
}
private fun queryUriDetail(uri: Uri) {
// 如果需要选取的图片的详细信息(图片大小、路径、所在相册名称、修改时间、MIME、宽高、文件名等),
// 则需要通过 content.getContentResolver().query(uri, ...) 查询(直接查询所有字段)
val cursor = contentResolver.query(uri, null, null, null, null)
// 一般查询出来的只有一条记录
if (cursor?.moveToFirst() == true) {
// 查看查询结果数据的的所有列, 不同系统版本列名数量和类型可能不相同, 参考:
// [_id, _data, _size, _display_name, mime_type, title, date_added, date_modified,
// description, picasa_id, isprivate, latitude, longitude, datetaken, orientation,
// mini_thumb_magic, bucket_id, bucket_display_name, width, height]
Log.d(TAG, "columnNames: " + Arrays.toString(cursor.columnNames))
// 获取图片的 大小、文件名、路径
// val size = cursor.getLong(cursor.getColumnIndex(MediaStore.Images.ImageColumns.SIZE))
// val filename = cursor.getString(cursor.getColumnIndex(MediaStore.Images.ImageColumns.DISPLAY_NAME))
// val path = cursor.getString(cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATA))
// 输出所有列对应的值
for (column in cursor.columnNames) {
val index = cursor.getColumnIndex(column)
val valueDesc = when (cursor.getType(index)) {
Cursor.FIELD_TYPE_NULL -> "$column: NULL"
Cursor.FIELD_TYPE_INTEGER -> "$column: " + cursor.getInt(index)
Cursor.FIELD_TYPE_FLOAT -> "$column: " + cursor.getFloat(index)
Cursor.FIELD_TYPE_STRING -> "$column: " + cursor.getString(index)
Cursor.FIELD_TYPE_BLOB -> "$column: BLOB"
else -> "$column: Unknown"
}
Log.d(TAG, valueDesc)
}
}
cursor?.close()
}
private fun closeStream(c: Closeable?) {
try {
c?.close()
} catch (e: Exception) {
e.printStackTrace()
}
}
}
ACTION_GET_CONTENT
允许用户选择一种特定类型的数据并返回(支持多选)。这与 ACTION_PICK
不同,因为这里我们只说需要哪种数据,而不是用户可以选择的现有数据的 URI。ACTION_GET_CONTENT
可以允许用户在数据运行时创建数据(例如拍照或录制声音),让他们浏览Web 并下载所需的数据,等等。获取的资源类型为 "content://"
开头的 Uri 资源,可通过 context.getContentResolver()
获取资源的内容和相关信息。
ACTION_GET_CONTENT
不单只支持文件/图片,还支持选择通讯录、录音、音频、视频等内容。
Intent.ACTION_GET_CONTENT 的值为: "android.intent.action.GET_CONTENT"
启动的 Intent:
val intent = Intent(Intent.ACTION_GET_CONTENT)
intent.type = "*/*"
// 只选择图片: intent.type = "image/*"
// 只选择视频: intent.type = "video/*"
// 支持多选(长按多选)
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)
// 用于表示 Intent 仅希望查询能使用 ContentResolver.openFileDescriptor(Uri, String) 打开的 Uri
intent.addCategory(Intent.CATEGORY_OPENABLE)
activity.startActivityForResult(intent, REQUEST_CODE_GET_CONTENT)
// 可以包装 Intent
// activity.startActivityForResult(Intent.createChooser(intent, "选择文件"), REQUEST_CODE_GET_CONTENT)
在 Activity.onActivityResult(...)
中接收选取返回的文件资源:
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (resultCode != RESULT_OK && requestCode == REQUEST_CODE_GET_CONTENT) {
// 多选的情况
val clipData = data?.clipData
if (clipData != null && clipData.itemCount > 0) {
for (i in 0 until clipData.itemCount) {
val item = clipData.getItemAt(i)
val uri = item.uri ?: continue
handleSelectedFile(uri)
}
}
// 单选的情况
val uri = data?.data ?: return
handleSelectedFile(uri)
}
}
private fun handleSelectedFile(uri: Uri) {
// 获取选取返回的文件资源, 结果为 "content://" 开头的 Uri 格式的资源,
// Uri 格式参考: content://com.android.providers.media.documents/document/document%3A145
val uri = data?.data ?: return
// 获取文件的数据, 可以使用 ContentResolver 直接打开输入流
var fileInputStream = contentResolver.openInputStream(uri)
// 查询文件的详细信息
val cursor = contentResolver.query(uri, null, null, null, null)
...
}
(1)先在 AndroidManifest.xml
中添加读取外部存储器的权限:
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
(2)布局文件: res/layout/activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<Button
android:id="@+id/btn_get_content"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Choose Files"/>
</LinearLayout>
(3)Activity 代码: MainActivity.kt
package com.xiets.demo
import android.content.Intent
import android.database.Cursor
import android.net.Uri
import android.os.Bundle
import android.provider.DocumentsContract
import android.util.Log
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import java.io.Closeable
import java.io.InputStream
import java.util.*
class MainActivity : AppCompatActivity() {
companion object {
private const val TAG = "MainActivity"
private const val REQUEST_CODE_GET_CONTENT = 1001
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
findViewById<View>(R.id.btn_get_content).setOnClickListener {
// 点击按钮: 使用 ACTION_GET_CONTENT 选择文件,启动 Activity Intent
openSystemFilesChooser(REQUEST_CODE_GET_CONTENT)
}
}
/**
* 使用 ACTION_GET_CONTENT 选择文件,启动 Activity Intent
*/
private fun openSystemFilesChooser(requestCode: Int) {
val intent = Intent(Intent.ACTION_GET_CONTENT)
intent.type = "*/*"
// 只选择图片: intent.type = "image/*"
// 只选择视频: intent.type = "video/*"
// 支持多选(长按多选)
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)
// 用于表示 Intent 仅希望查询能使用 ContentResolver.openFileDescriptor(Uri, String) 打开的 Uri
intent.addCategory(Intent.CATEGORY_OPENABLE)
startActivityForResult(intent, requestCode)
// 可以包装 Intent
// startActivityForResult(Intent.createChooser(intent, "选择文件"), requestCode)
}
/**
* 在返回的 onActivityResult 中接收选取返回的文件资源
*/
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (resultCode != RESULT_OK) {
Log.d(TAG, "onActivityResult not ok.")
return
}
if (requestCode == REQUEST_CODE_GET_CONTENT) {
// 多选的情况
val clipData = data?.clipData
if (clipData != null && clipData.itemCount > 0) {
for (i in 0 until clipData.itemCount) {
val item = clipData.getItemAt(i)
val uri = item.uri ?: continue
handleSelectedFile(uri)
}
}
// 单选的情况
val uri = data?.data ?: return
handleSelectedFile(uri)
}
}
private fun handleSelectedFile(uri: Uri) {
// URI 格式参考: content://com.android.providers.media.documents/document/document%3A145
Log.d(TAG, "选取的文件: $uri")
// 如果需要使用文件的数据, 可以使用 ContentResolver 直接打开输入流
var fileInputStream: InputStream? = null
try {
// 打开 Uri 的输入流
fileInputStream = contentResolver.openInputStream(uri)
// ...
} catch (e: Exception) {
e.printStackTrace()
} finally {
closeStream(fileInputStream)
}
// 查询文件的详细信息
queryUriDetail(uri)
}
private fun queryUriDetail(uri: Uri) {
// 如果需要选取的文件的详细信息(MIME、文件名、修改时间、大小等),
// 则需要通过 content.getContentResolver().query(uri, ...) 查询(直接查询所有字段)
val cursor = contentResolver.query(uri, null, null, null, null)
// 一般查询出来的只有一条记录
if (cursor?.moveToFirst() == true) {
// 查看查询结果数据的的所有列, 不同系统版本列名数量可能不相同, 参考:
// [document_id, mime_type, _display_name, last_modified, flags, _size], 这里没有路径字段
Log.d(TAG, "columnNames: " + Arrays.toString(cursor.columnNames))
// 获取文件的 大小、文件名, 列名常量值参考: DocumentsContract.Document.COLUMN_XXX
// val size = cursor.getLong(cursor.getColumnIndex(DocumentsContract.Document.COLUMN_SIZE))
// val filename = cursor.getString(cursor.getColumnIndex(DocumentsContract.Document.COLUMN_DISPLAY_NAME))
// 输出所有列对应的值
for (column in cursor.columnNames) {
val index = cursor.getColumnIndex(column)
val valueDesc = when (cursor.getType(index)) {
Cursor.FIELD_TYPE_NULL -> "$column: NULL"
Cursor.FIELD_TYPE_INTEGER -> "$column: " + cursor.getInt(index)
Cursor.FIELD_TYPE_FLOAT -> "$column: " + cursor.getFloat(index)
Cursor.FIELD_TYPE_STRING -> "$column: " + cursor.getString(index)
Cursor.FIELD_TYPE_BLOB -> "$column: BLOB"
else -> "$column: Unknown"
}
Log.d(TAG, valueDesc)
}
}
cursor?.close()
}
private fun closeStream(c: Closeable?) {
try {
c?.close()
} catch (e: Exception) {
e.printStackTrace()
}
}
}
ACTION_OPEN_DOCUMENT
允许用户选择并返回一个或多个现有文档。调用时,系统显示实现了 DocumentsProvider 实例的文件选取器(APP),让用户以交互方式浏览他们。支持的文档包括本地媒体(例如照片和视频)以及已安装的云存储提供商提供的文档。
选取的每个文档都表示为由 DocumentsProvider
提供的 "content://"
开头的 Uri 资源,可以通过 ContentResolver.openInputStream(Uri)
将该 Uri 作为流打开,也可以通过 ContentResolver.openFileDescriptor(Uri, String)
查询 DocumentsContract.Document
元数据。
ACTION_OPEN_DOCUMENT
的用法与 ACTION_GET_CONTENT
基本相似,参考之。
与 ACTION_GET_CONTENT 不同的是,ACTION_OPEN_DOCUMENT 只支持选择文档(即图片、音视频、文件 等)。
Intent.ACTION_OPEN_DOCUMENT 的值为: "android.intent.action.OPEN_DOCUMENT"
简单实例:
启动的 Intent:
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT)
intent.type = "*/*"
// 只选择图片: intent.type = "image/*"
// 只选择视频: intent.type = "video/*"
// 支持多选(长按多选)
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)
activity.startActivityForResult(intent, REQUEST_CODE_OPEN_DOCUMENT)
在 Activity.onActivityResult(...)
中接收选取返回的文件资源:
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (resultCode != RESULT_OK && requestCode == REQUEST_CODE_OPEN_DOCUMENT) {
// 多选的情况
val clipData = data?.clipData
if (clipData != null && clipData.itemCount > 0) {
for (i in 0 until clipData.itemCount) {
val item = clipData.getItemAt(i)
val uri = item.uri ?: continue
handleSelectedFile(uri)
}
}
// 单选的情况
val uri = data?.data ?: return
handleSelectedFile(uri)
}
}
private fun handleSelectedFile(uri: Uri) {
// 获取选取返回的文件资源, 结果为 "content://" 开头的 Uri 格式的资源,
// Uri 格式参考: content://com.android.providers.media.documents/document/document%3A145
val uri = data?.data ?: return
// 获取文件的数据, 可以使用 ContentResolver 直接打开输入流
var fileInputStream = contentResolver.openInputStream(uri)
// 查询文件的详细信息
val cursor = contentResolver.query(uri, null, null, null, null)
...
}
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。