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

青铜版本

Kotlin 复制代码
  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字段中真正的路径

Kotlin 复制代码
//如果是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获取路径

Kotlin 复制代码
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拷贝一份文件到应用目录,不过用完记得删除

Kotlin 复制代码
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
    }
}
相关推荐
JMchen1232 分钟前
Android网络安全实战:从HTTPS到双向认证
android·经验分享·网络协议·安全·web安全·https·kotlin
CS创新实验室4 分钟前
Pandas 3 的新功能
android·ide·pandas
ujainu15 分钟前
护眼又美观:Flutter + OpenHarmony 鸿蒙记事本一键切换夜间模式(四)
android·flutter·harmonyos
三少爷的鞋27 分钟前
为什么我不在 Android ViewModel 中直接处理异常?
android
草莓熊Lotso2 小时前
Linux 文件描述符与重定向实战:从原理到 minishell 实现
android·linux·运维·服务器·数据库·c++·人工智能
恋猫de小郭2 小时前
Flutter Zero 是什么?它的出现有什么意义?为什么你需要了解下?
android·前端·flutter
工程师老罗8 小时前
如何在Android工程中配置NDK版本
android
Libraeking11 小时前
破壁行动:在旧项目中丝滑嵌入 Compose(混合开发实战)
android·经验分享·android jetpack
市场部需要一个软件开发岗位12 小时前
JAVA开发常见安全问题:Cookie 中明文存储用户名、密码
android·java·安全
JMchen12314 小时前
Android后台服务与网络保活:WorkManager的实战应用
android·java·网络·kotlin·php·android-studio