通过uri获取文件路径手机适配

青铜版本

  return contentResolver.query(this, arrayOf(MediaStore.MediaColumns.DATA), null, null).let {
        if (it?.moveToFirst() == true) {
            val columnIndex = it.getColumnIndex(MediaStore.MediaColumns.DATA)
            val path = it.getString(columnIndex)
            it.close()
            return path
        }
        ""
    }

在firebase上发现很多异常奔溃日志,部分手机获取到的 path 是空的,也就是说没有_data字段

其实有的手机通过选择图片或者文件后返回的uri并不一定是媒体uri,也可能是document uri,造成这个时候直接通过 uri查询,找不到_data字段,需要将 document uri中分离出类型和id,在拼凑成新的uri,在通过contentprovider查询,才可以查出 _data字段中真正的路径

//如果是Document类型的URI,需要进行转换
private fun getPathFromDocumentUri(uri: Uri): String? {
    val isDocumentUri = DocumentsContract.isDocumentUri(BaseApplication.instance, uri)
    if (isDocumentUri) {
        val docId = DocumentsContract.getDocumentId(uri)
        val split = docId.split(":".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
        val type = split[0] // "image"
        val id = split[1] // "1044024"
        var quaryUri: Uri? = null
        if ("image".equals(type)) {
            quaryUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
        } else if ("video".equals(type)) {
            quaryUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
        } else if ("audio".equals(type)) {
            quaryUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
        }

        LogUtils.d("linlian quaryUri=$quaryUri")
        quaryUri?.let {
            val projection = arrayOf(MediaStore.Images.Media.DATA,MediaStore.Images.Media._ID)
            BaseApplication.instance.contentResolver.query(
                quaryUri,
                projection,
                MediaStore.Images.Media._ID + "=?",
                arrayOf<String>(id),
                null
            )?.use {cursor ->

                val columnNames = cursor.columnNames
                LogUtils.d("linlian ", "URI: $uri")
                LogUtils.d("linlian ", "Total columns: ${columnNames.size}")

                // 遍历每一行
                while (cursor.moveToNext()) {
//                    val rowData = StringBuilder()
                    // 遍历每一列
//                    for (columnName in columnNames) {
//                        val columnIndex = cursor.getColumnIndex(columnName)
//                        if (columnIndex == -1) {
//                            rowData.append("$columnName: [COLUMN_NOT_FOUND]\n")
//                            continue
//                        }
//
//                        val value = when (cursor.getType(columnIndex)) {
//                            Cursor.FIELD_TYPE_NULL -> "NULL"
//                            Cursor.FIELD_TYPE_INTEGER -> cursor.getLong(columnIndex)
//                            Cursor.FIELD_TYPE_FLOAT -> cursor.getDouble(columnIndex)
//                            Cursor.FIELD_TYPE_STRING -> cursor.getString(columnIndex)
//                            Cursor.FIELD_TYPE_BLOB -> "BLOB (${cursor.getBlob(columnIndex)?.size ?: 0} bytes)"
//                            else -> "UNKNOWN_TYPE"
//                        }
//                        rowData.append("$columnName: $value\n")
//                    }
//                    LogUtils.d("linlian", "Row ${cursor.position}:\n$rowData")

                    val index= cursor.getColumnIndex(MediaStore.Images.Media.DATA)
                    if(index!=-1){
                        val path = cursor.getString(index)
                        LogUtils.d("linlian", "!!!!!!!!!!$path")
                        return path
                    }
                    LogUtils.d("linlian", "!!!!!!!!!!$index")
                }
                return null
            }

        }

    }
    return null
}

但是根据文档其实Android 10 之后是有新的字段,但是手机厂商众多,实现方式不一,还是找不到path怎么办,官方是说可以从 RELATIVE_PATH获取路径

contentResolver.query(uri, projection, null, null, null).use { cursor ->
                if (cursor != null && cursor.moveToFirst()) {
                    // 获取文件名
                    val nameIndex: Int = cursor.getColumnIndex(
                        if (isImage) MediaStore.Images.Media.DISPLAY_NAME else MediaStore.Video.Media.DISPLAY_NAME
                    )
                    val displayName: String? =
                        if ((nameIndex != -1)) cursor.getString(nameIndex) else null

                    // 获取相对路径(可能为 null)
                    val pathIndex: Int = cursor.getColumnIndex(
                        if (isImage) MediaStore.Images.Media.RELATIVE_PATH else MediaStore.Video.Media.RELATIVE_PATH
                    )
                    val relativePath: String? =
                        if ((pathIndex != -1)) cursor.getString(pathIndex) else null
                    LogUtils.d("linlian getPathFromRelativeColumn displayName=$displayName,relativePath=$relativePath")
                    // 生成最终路径
                    return buildPathForAndroidQ(displayName, relativePath)
                }
            }

如果以上都找不到path 怎么办呢

那最后的方式是,通过uri拷贝一份文件到应用目录,不过用完记得删除

fun copyFileFromUri(context: Context, uri: Uri, destFileName: String): File? {
    return try {
        // 打开输入流
        val inputStream: InputStream? = context.contentResolver.openInputStream(uri)
        if (inputStream == null) {
            return null
        }

        // 目标文件:保存在 app 的 filesDir 目录
        val destFile = File(context.filesDir, destFileName)
        val outputStream: OutputStream = FileOutputStream(destFile)

        // 拷贝数据
        val buffer = ByteArray(4096)
        var bytesRead: Int
        while (inputStream.read(buffer).also { bytesRead = it } != -1) {
            outputStream.write(buffer, 0, bytesRead)
        }

        // 关闭流
        inputStream.close()
        outputStream.close()

        destFile
    } catch (e: Exception) {
        e.printStackTrace()
        null
    }
}





Android 中,从 `Uri` 获取实际文件路径是一个常见的需求,尤其是在处理多媒体文件(如图片、音频、视频)时。然而,随着 Android 版本的演进,特别是在 Android 6.0(Marshmallow)及更高版本中,由于系统对文件访问权限的限制,传统的通过 `MediaStore` 查询文件路径的方法可能不再适用。 ### 从 Uri 获取文件路径的通用方法 以下是一个适用于 Android 6.0 叐版本的通用方法,能够从 `Uri` 获取文件的实际路径: ```java public String getRealPathFromUri(Uri uri, Context context) { String filePath = null; if (uri.getScheme().equals("content")) { try (Cursor cursor = context.getContentResolver().query(uri, new String[]{MediaStore.MediaColumns.DATA}, null, null, null)) { if (cursor != null && cursor.moveToFirst()) { int columnIndex = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATA); filePath = cursor.getString(columnIndex); } } catch (Exception e) { e.printStackTrace(); } } else if (uri.getScheme().equals("file")) { filePath = uri.getPath(); } return filePath; } ``` ### 适配 Android 10 及更高版本 从 Android 10(API 级别 29)开始,系统引入了 **Scoped Storage**,进一步限制了直接访问文件路径的能力。在这种情况下,推荐使用 `ContentResolver` 或 `MediaStore` 来访问文件,而不是依赖于文件路径。例如: ```java public InputStream getInputStreamFromUri(Uri uri, Context context) throws IOException { return context.getContentResolver().openInputStream(uri); } ``` 通过 `InputStream`,可以直接读取文件内容,而不必获取其实际路径。如果确实需要文件路径,可以将文件内容复制到应用的私有目录中,并在该目录下操作文件。 ### 替代方案:使用 FileProvider 在某些场景下,例如分享文件给其他应用时,可以使用 `FileProvider` 来生成一个 `content://` 类型的 `Uri`,该 `Uri` 可以安全地传递给其他应用,而无需暴露实际文件路径: ```xml <provider android:name="androidx.core.content.FileProvider" android:authorities="${applicationId}.fileprovider" android:exported="false" android:grantUriPermissions="true"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths" /> </provider> ``` 在 `res/xml/file_paths.xml` 中定义可访问的文件路径: ```xml <?xml version="1.0" encoding="utf-8"?> <paths xmlns:android="http://schemas.android.com/apk/res/android"> <external-path name="external_files" path="." /> </paths> ``` 然后通过 `FileProvider.getUriForFile()` 获取 `Uri`: ```java File file = new File(context.getExternalFilesDir(null), "example.txt"); Uri uri = FileProvider.getUriForFile(context, context.getPackageName() + ".fileprovider", file); ``` ### 总结 - 在 Android 6.0 及以上版本中,通过 `MediaStore` 查询文件路径可能不再适用,需使用 `ContentResolver` 查询文件路径。 - Android 10 及更高版本引入了 Scoped Storage,建议使用 `InputStream` 或 `FileProvider` 来处理文件访问。 - 对于跨应用文件共享,推荐使用 `FileProvider` 生成安全的 `content://` 类型 `Uri` [^1]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值