Android 调用 系统选择器 选择 图片 或 文件(ACTION

Android 调用 系统选择器 选择 图片 或 文件(ACTION,第1张

本文链接: https://blog.csdn.net/xietansheng/article/details/115763279

打开系统 APP 的资源选择器选取资源(图片/文件),通常可以使用以下 3 个 Action:

Intent.ACTION_PICKIntent.ACTION_GET_CONTENTIntent.ACTION_OPEN_DOCUMENT

一般 Android 系统内置的相关 APP 中均有实现了这 3 个 Action(如: 相册、文件管理),三的均能打开系统 APP 的资源选择器选择资源(图片、视频、文件、通讯录等)并返回,但三者的使用并不完全相同。有些第三方 APP 实现了这 3 个 Action 的,也可以用于选取相应的资源。

一般使用 ACTION_PICK 选择图片,使用 ACTION_GET_CONTENTACTION_OPEN_DOCUMENT 选择文件。

1. 使用 ACTION_PICK 选择图片

从数据中选择一个项目(不支持多选),并返回选择的内容,从返回的 intent.getData() 中获取资源,资源类型为 "content://" 开头的 Uri 资源,可通过 context.getContentResolver() 获取资源的内容和相关信息。

Intent.ACTION_PICK 的值为: "android.intent.action.PICK"

1.1 简单示例

启动的 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.2 详细代码示例

(1)先在 AndroidManifest.xml 中添加读取外部存储器的权限:

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>

(2)布局文件: res/layout/activity_main.xml


<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()
        }
    }

}
2. 使用 ACTION_GET_CONTENT 选择文件

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"

2.1 简单示例

启动的 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)
    ...
}
2.2 详细代码示例

(1)先在 AndroidManifest.xml 中添加读取外部存储器的权限:

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>

(2)布局文件: res/layout/activity_main.xml


<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()
        }
    }

}
3. 使用 ACTION_OPEN_DOCUMENT 选择文件

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)
    ...
}

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/web/992312.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-21
下一篇 2022-05-21

发表评论

登录后才能评论

评论列表(0条)

保存