Android开发指南:全面解析各类文件选择器的实现方法
1、概述
在Android应用中集成文件选择功能是现代APP的基础需求之一。本文将详细介绍如何实现多种类型的文件选择器,包括系统原生方案和第三方库解决方案。
2、照片/媒体选择器
2.1、使用Intent调用系统相册
这是最简单直接的图片选择方式:
fun openGallery(isMulti: Boolean = false): Intent {
return Intent(Intent.ACTION_PICK).apply {
type = "image/*"
putExtra(Intent.EXTRA_MIME_TYPES, arrayOf("image/jpeg", "image/png"))
putExtra(Intent.EXTRA_ALLOW_MULTIPLE, isMulti) //是否多选
}
}
launcher = registerForActivityResult(register, object : ActivityResultCallback<ActivityResult> {
override fun onActivityResult(result: ActivityResult) {
Log.i(TAG, "onActivityResult ${result.resultCode}")
if (result.resultCode == Activity.RESULT_OK) {
result.data?.let {
val cnt = it.clipData?.itemCount ?: 0
//单个文件在it.data,多个文件在it.clipData
for (i in 0 until cnt) {
val item = it.clipData?.getItemAt(i)
Log.i(TAG, "clipData 文件 uri:${item?.uri}")
binding.text.append("已选择文件:${item?.uri}\n")
fileUri = item?.uri
}
}
}
}
})
2.2、使用PhotoPicker API
Android 13引入了专门的PhotoPicker API(Android 13+新特性):
fun launchModernPhotoPicker() {
val pickMedia = PickVisualMediaRequest.Builder()
.setMediaType(PickVisualMedia.ImageOnly)
.build()
registerForActivityResult(PickVisualMedia()) { uri ->
// 处理返回的URI
}.launch(pickMedia)
}
// 必须在Activity/Fragment的init阶段注册回调
class MainActivity : AppCompatActivity() {
private val photoPickerLauncher = registerForActivityResult(PickVisualMedia()) { uri ->
uri?.let { handleImage(it) }
}
visualPickLauncher = registerForActivityResult(ActivityResultContracts.PickVisualMedia()) { uri ->
Log.i(TAG, "photoLauncher onActivityResult 已选择 照片 uri:$uri")
}
/**
* PhotoPicker选择照片(单选)
*/
binding.selectPhotoPicker.setThrottleClickListener {
Log.i(TAG, "onClick: 选择图片")
val pickMedia = PickVisualMediaRequest.Builder()
.setMediaType(ActivityResultContracts.PickVisualMedia.ImageOnly)
.build()
visualPickLauncher?.launch(pickMedia)
}
pickMultipleMediaLauncher = registerForActivityResult(ActivityResultContracts.PickMultipleVisualMedia(5)) { uris ->
// Callback is invoked after the user selects media items or closes the
// photo picker.
if (uris.isNotEmpty()) {
Log.d(TAG, "Number of items selected: ${uris.size}")
uris.forEach {
Log.d(TAG, "Selected: $it")
}
} else {
Log.d(TAG, "No media selected")
}
}
/**
* PhotoPicker选择照片(多选)
*/
binding.multiSelectPhotoPicker.setThrottleClickListener {
Log.i(TAG, "onClick: 选择多图片")
val pickMedia = PickVisualMediaRequest.Builder()
.setMediaType(ActivityResultContracts.PickVisualMedia.ImageOnly)
.setMaxItems(5)
.build()
pickMultipleMediaLauncher?.launch(pickMedia)
}
3、通用文件选择器
3.1、基本文件选择实现
fun selectFile() {
val intent = Intent(Intent.ACTION_GET_CONTENT).apply {
type = "*/*" // 所有文件类型
addCategory(Intent.CATEGORY_OPENABLE)
}
startActivityForResult(intent, REQUEST_FILE_SELECT)
}
3.2、设置特定文件类型筛选
要限定只能选择Word文档(.doc/.docx):
fun selectWordDocument() {
val intent = Intent(Intent.ACTION_GET_CONTENT).apply {
type = "application/msword"
putExtra(Intent.EXTRA_MIME_TYPES, arrayOf(
"application/msword",
"application/vnd.openxmlformats-officedocument.wordprocessingml.document"
))
}
startActivityForResult(intent, REQUEST_WORD_DOCUMENT)
}
4、目录选择器
4.1、使用Storage Access Framework
适用于API 21及以上版本:
fun selectDirectory() {
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).apply {
flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or
Intent.FLAG_GRANT_WRITE_URI_PERMISSION
}
startActivityForResult(intent, REQUEST_DIRECTORY_SELECT)
}
4.2、高级用法
多文件选择,允许用户一次选择多个文件:
fun selectMultipleFiles() {
val intent = Intent(Intent.ACTION_GET_CONTENT).apply {
type = "/"
putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)
}
startActivityForResult(intent, REQUEST_MULTIPLE_FILES)
}
// Registers a photo picker activity launcher in multi-select mode.
// In this example, the app lets the user select up to 5 media files.
val pickMultipleMedia =
registerForActivityResult(PickMultipleVisualMedia(5)) { uris ->
// Callback is invoked after the user selects media items or closes the
// photo picker.
if (uris.isNotEmpty()) {
Log.d("PhotoPicker", "Number of items selected: ${uris.size}")
} else {
Log.d("PhotoPicker", "No media selected")
}
}
// For this example, launch the photo picker and let the user choose images
// and videos. If you want the user to select a specific type of media file,
// use the overloaded versions of launch(), as shown in the section about how
// to select a single media item.
pickMultipleMedia.launch(PickVisualMediaRequest(PickVisualMedia.ImageAndVideo)) 自定义文件过滤器,结合DocumentsProvider实现更复杂的过滤:
val filter = arrayOf(
"application/pdf", // PDF文件
"text/plain", // 文本文件
"application/vnd.ms-excel" // Excel文件
)
intent.putExtra(Intent.EXTRA_MIME_TYPES, filter)
5、权限处理要点
运行时权限:确保已申请READ_EXTERNAL_STORAGE权限
URI权限持久化:使用takePersistableUriPermission保存长期访问权
ContentResolver查询:正确处理返回的URI内容
6、兼容性考虑
对于旧版Android,考虑使用第三方库如:
Matisse(图片选择)
FilePicker(通用文件选择)
Android-FilePicker
处理不同厂商ROM的可能差异
以上实现方案覆盖了大多数常见的文件选择场景,开发者可根据具体需求选择合适的实现方式。