如果你的目标是针对Android 11 API,你不能直接访问文件路径,因为在API 30(Android R)中有许多限制。由于作用域存储API在Android 10(API 29)中引入,因此存储现在被分为作用域存储(私有存储)和共享存储(公共存储)。作用域存储是一种只能访问在你的scoped storage
目录中创建的文件的存储方式(即/Android/data/或/Android/media/ your-package-name)。你无法从共享存储(即内部存储/外部SD卡存储等)访问文件。
共享存储再次分为Media和Download集合。Media集合存储图像、音频和视频文件。Download集合将处理非媒体文件。
要了解关于作用域存储和共享存储的更多细节,请参阅Android 10与11中的Scoped Storage链接。
如果你正在处理媒体文件(即图像、视频、音频),你可以使用支持API 30(Android 11)的Media Store API获取文件路径。如果你正在处理非媒体文件(即文档和其他文件),则可以使用file Uri获取文件路径。
注意:如果你正在使用文件或Uri util类(如RealPathUtil、FilePathUtils等)来获取文件路径,则可以获取所需的文件路径,但你无法读取该文件。因为在Android 11中,它会抛出读访问异常(如权限被拒绝),因为你无法读取由另一个应用程序创建的文件。
因此,在Android 11(API 30)中实现获取文件路径的情况,建议使用File Uri将文件复制到应用程序的缓存目录中,并从缓存目录中获取文件访问路径。
在我的场景中,我同时使用了这两个API来获取Android 11中的文件访问。为了获取媒体文件(即图像、视频、音频)的文件路径,我使用了Media Store API(请参阅Media Store API示例 - 从共享存储访问媒体文件链接),而对于非媒体文件(即文档和其他文件)的文件路径,则使用了fileDescriptor。
文件描述符示例:
我已经创建了系统对话框文件选择器来选择文件。
private fun openDocumentAction() {
val mimetypes = arrayOf(
"application/*",
"font/*",
"message/*",
"model/*",
"multipart/*",
"text/*"
)
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = "*/*"
putExtra(Intent.EXTRA_MIME_TYPES, mimetypes)
addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
}
startActivityForResult(intent, RC_SAF_NON_MEDIA)
}
在活动的onActivityResult方法中处理文件选择器的结果。在此处获取文件URI。
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
when (requestCode) {
RC_SAF_NON_MEDIA -> {
if (resultCode == RESULT_OK) {
data?.data?.also { uri ->
contentResolver.takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION)
val path = makeFileCopyInCacheDir(uri)
Log.e(localClassName, "onActivityResult: path ${path.toString()} ")
}
}
}
}
}
将文件URI传递给以下方法以获取文件路径。此方法将在应用程序的缓存目录中创建一个文件对象,并从该位置您可以轻松地获取对该文件的读取访问权限。
private fun makeFileCopyInCacheDir(contentUri :Uri) : String? {
try {
val filePathColumn = arrayOf(
MediaStore.Files.FileColumns._ID,
MediaStore.Files.FileColumns.TITLE,
MediaStore.Files.FileColumns.DATA,
MediaStore.Files.FileColumns.SIZE,
MediaStore.Files.FileColumns.DATE_ADDED,
MediaStore.Files.FileColumns.DISPLAY_NAME,
MediaStore.MediaColumns.DATA,
MediaStore.MediaColumns.MIME_TYPE,
MediaStore.MediaColumns.DISPLAY_NAME
)
val returnCursor = contentUri.let { contentResolver.query(it, filePathColumn, null, null, null) }
if (returnCursor!=null) {
returnCursor.moveToFirst()
val nameIndex = returnCursor.getColumnIndexOrThrow(OpenableColumns.DISPLAY_NAME)
val name = returnCursor.getString(nameIndex)
val file = File(cacheDir, name)
val inputStream = contentResolver.openInputStream(contentUri)
val outputStream = FileOutputStream(file)
var read = 0
val maxBufferSize = 1 * 1024 * 1024
val bytesAvailable = inputStream!!.available()
val bufferSize = Math.min(bytesAvailable, maxBufferSize)
val buffers = ByteArray(bufferSize)
while (inputStream.read(buffers).also { read = it } != -1) {
outputStream.write(buffers, 0, read)
}
inputStream.close()
outputStream.close()
Log.e("File Path", "Path " + file.path)
Log.e("File Size", "Size " + file.length())
return file.absolutePath
}
} catch (ex: Exception) {
Log.e("Exception", ex.message!!)
}
return contentUri.let { UriPathUtils().getRealPathFromURI(this, it).toString() }
}
注意:您可以使用此方法获取媒体文件(图像、视频、音频)和非媒体文件(文档和其他文件)的文件路径。只需要传递一个文件Uri即可。
Note: 您可以使用此方法获取媒体文件(图像、视频、音频)和非媒体文件(文档和其他文件)的文件路径。只需要传递一个文件Uri即可。
pickiT.getPath
,路径将在PickiTonCompleteListener
中返回)。如果您仍然无法使其正常工作,请在库上打开一个问题并填写问题模板(包括日志和您实现库的方式)- https://github.com/HBiSoft/PickiT#implementation - HB.