Android 通过Uri获取真实路径以及文件名


2021年9月 建议使用 ?? https://github.com/javakam/FileOperator 

2021年9月 建议使用 ?? https://github.com/javakam/FileOperator 

2021年9月 建议使用 ?? https://github.com/javakam/FileOperator 

------------------------------------------------------------------------------------------------------------------------------

在Android 开发中经常会用到 Uri 获取文件路径/文件名的操作。

使用 https://github.com/javakam/FileOperator  中的 FileUri.kt (https://github.com/javakam/FileOperator/blob/master/library_core/src/main/java/ando/file/core/FileUri.kt) 提供的方法:

//从File路径中获取Uri
fun getUriByPath(path: String?): Uri? = if (path.isNullOrBlank()) null else getUriByFile(File(path))

fun getUriByFile(file: File?): Uri? {
    if (file == null) return null
    return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
        val authority = FileOperator.getContext().packageName + PATH_SUFFIX
        FileProvider.getUriForFile(FileOperator.getContext(), authority, file)
    } else {
        Uri.fromFile(file)
    }
}

//获取Uri对应的文件路径,兼容API 26
fun getFilePathByUri(context: Context?, uri: Uri?): String? {
    if (context == null || uri == null) return null
    val scheme = uri.scheme
    // 以 file:// 开头的
    if (ContentResolver.SCHEME_FILE.equals(scheme, ignoreCase = true)) {//使用第三方应用打开
        uri.path
    }
    return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { //4.4以后
        getPath(context, uri)
    } else { //4.4以下
        getPathKitkat(context, uri)
    }
}

 FileUri.kt

package ando.file.core

import ando.file.FileOperator
import android.annotation.TargetApi
import android.content.ContentResolver
import android.content.ContentUris
import android.content.Context
import android.net.Uri
import android.os.Build
import android.os.Environment
import android.provider.DocumentsContract
import android.provider.MediaStore
import android.text.TextUtils
import androidx.core.content.FileProvider
import java.io.File

/**
 * Title: FileUri
 * 

* Description: Uri 工具类 *

*
@author javakam * @date 2020/8/24 11:24 */ object FileUri { //从 File Path 中获取 Uri //---------------------------------------------------------------- fun getUriByPath(path: String?): Uri? = if (path.isNullOrBlank()) null else getUriByFile(File(path)) /** * Return a content URI for a given file. * * @param file The file. * @return a content URI for a given file */ fun getUriByFile(file: File?): Uri? { if (file == null) return null return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { val authority = FileOperator.getContext().packageName + PATH_SUFFIX FileProvider.getUriForFile(FileOperator.getContext(), authority, file) } else { Uri.fromFile(file) } } //获取Uri对应的文件路径 兼容API 26 //---------------------------------------------------------------- /** * 根据uri获取文件的绝对路径,解决Android 4.4以上 根据uri获取路径的方法 * *

* RequiresPermission(permission.READ_EXTERNAL_STORAGE) * @param uri * @return */ fun getFilePathByUri(uri: Uri?): String? = getFilePathByUri(FileOperator.getContext(), uri) /** * 根据uri获取文件的绝对路径,解决Android 4.4以上 根据uri获取路径的方法 * *

* RequiresPermission(permission.READ_EXTERNAL_STORAGE) * @param context * @param uri * @return */ fun getFilePathByUri(context: Context?, uri: Uri?): String? { if (context == null || uri == null) return null val scheme = uri.scheme // 以 file:// 开头的 if (ContentResolver.SCHEME_FILE.equals(scheme, ignoreCase = true)) {//使用第三方应用打开 uri.path } return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { //4.4以后 getPath(context, uri) } else { //4.4以下 getPathKitkat(context, uri) } } private fun getPathKitkat(context: Context, contentUri: Uri): String? = context.contentResolver.query(contentUri, null, null, null, null).use { c -> @Suppress("DEPRECATION") val column = MediaStore.Files.FileColumns.DATA return if (null != c && c.moveToFirst()) c.getString(c.getColumnIndex(column)) else null } /** * Get a file path from a Uri. This will get the the path for Storage Access * Framework Documents, as well as the _data field for the MediaStore and * other file-based ContentProviders.

*

* Callers should check whether the path is local before assuming it * represents a local file. * *

     * 错误的方式 -> 采用复制文件的方式重新写入再获取路径
     * https://stackoverflow.com/questions/42508383/illegalargumentexception-column-data-does-not-exist
     * @param context The context.
     * @param uri     The Uri to query.
     * @see #isLocal
     * @see #getFile
     */
    @TargetApi(Build.VERSION_CODES.KITKAT)
    private fun getPath(context: Context, uri: Uri?): String? {
        if (uri != null) {
            FileLogger.i(
                "FileUri getPath -> File : ",
                "Authority: " + uri.authority +
                        ", Fragment: " + uri.fragment +
                        ", Port: " + uri.port +
                        ", Query: " + uri.query +
                        ", Scheme: " + uri.scheme +
                        ", Host: " + uri.host +
                        ", Segments: " + uri.pathSegments.toString()
            )
        }
        val isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT

        // DocumentProvider
        if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) {
            // ExternalStorageProvider
            if (isExternalStorageDocument(uri)) {
                val docId = DocumentsContract.getDocumentId(uri)
                val split = docId.split(":").toTypedArray()
                val type = split[0]
                if ("primary".equals(type, ignoreCase = true)) {
                    @Suppress("DEPRECATION")
                    return Environment.getExternalStorageDirectory()
                        .toString() + File.separator + split[1]
                } else if ("home".equals(type, ignoreCase = true)) {
                    @Suppress("DEPRECATION")
                    return Environment.getExternalStorageDirectory()
                        .toString() + File.separator + "documents" + File.separator + split[1]
                }
            } else if (isDownloadsDocument(uri)) {
                val id = DocumentsContract.getDocumentId(uri)
                if (id != null && id.startsWith("raw:")) {
                    return id.substring(4)
                }
                // Android Q 待验证
                val contentUriPrefixesToTry = arrayOf(
                    "content://downloads/public_downloads",
                    "content://downloads/my_downloads",
                    "content://downloads/all_downloads"
                )
                for (contentUriPrefix in contentUriPrefixesToTry) {
                    val contentUri = ContentUris.withAppendedId(
                        Uri.parse(contentUriPrefix),
                        id.toLong()
                    )
                    try {
                        val path = getDataColumn(context, contentUri, null, null)
                        if (!TextUtils.isEmpty(path)) {
                            return path
                        }
                    } catch (e: Exception) {
                    }
                }
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                    return uri?.path
                }
            } else if (isMediaDocument(uri)) {
                val docId = DocumentsContract.getDocumentId(uri)
                val split = docId.split(":").toTypedArray()
                val type = split[0]
                var contentUri: Uri? = null
                when (type) {
                    "image" -> contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI
                    "video" -> contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI
                    "audio" -> contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
                }
                val selection = "_id=?"
                val selectionArgs = arrayOf(split[1])
                return getDataColumn(context, contentUri, selection, selectionArgs)
            }
        } else if ("content".equals(uri?.scheme, ignoreCase = true)) {
            // Return the remote address
            if (isGooglePhotosUri(uri)) {
                return uri?.lastPathSegment
            } else if (isHuaWeiUri(uri)) {
                val uriPath = uri?.path
                //content://com.huawei.hidisk.fileprovider/root/storage/emulated/0/Android/data/com.xxx.xxx/
                if (uriPath != null && uriPath.startsWith("/root")) {
                    return uriPath.replace("/root".toRegex(), "")
                }
            }
            return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) uri?.path
            else getDataColumn(context, uri, null, null)
        } else if ("file".equals(uri?.scheme, ignoreCase = true)) {
            uri?.path
        }
        return uri?.path
    }

    /**
     * 
     * 两个BUG :
     * 1.Android 10 闪退问题
     * 2.部分机型进入"文件管理器" 执行到  cursor.getColumnIndexOrThrow(column);出现
     * Caused by: java.lang.IllegalArgumentException: column '_data' does not exist. Available columns: []
     * Fixed :
     * FileChooserPathUtils 注释
     * 和
     * https://stackoverflow.com/questions/42508383/illegalargumentexception-column-data-does-not-exist
     * 
*/ private fun getDataColumn( context: Context, uri: Uri?, selection: String?, selectionArgs: Array?, ): String? { @Suppress("DEPRECATION") val column = MediaStore.Files.FileColumns.DATA val projection = arrayOf(column) context.contentResolver.query( uri ?: return null, projection, selection, selectionArgs, null )?.use { try { if (it.moveToFirst()) { val columnIndex = it.getColumnIndex(column) return it.getString(columnIndex) } else uri.path } catch (e: Throwable) { FileLogger.e("getDataColumn -> ${e.message}") } } return uri.path } //The Uri to check //---------------------------------------------------------------- /** * @param uri The Uri to check. * @return Whether the Uri authority is Google Photos. */ fun isGooglePhotosUri(uri: Uri?): Boolean { return "com.google.android.apps.photos.content" == uri?.authority } /** * @param uri The Uri to check. * @return Whether the Uri authority is ExternalStorageProvider. */ private fun isExternalStorageDocument(uri: Uri?): Boolean { return "com.android.externalstorage.documents" == uri?.authority } /** * @param uri The Uri to check. * @return Whether the Uri authority is DownloadsProvider. */ private fun isDownloadsDocument(uri: Uri?): Boolean { return "com.android.providers.downloads.documents" == uri?.authority } /** * @param uri The Uri to check. * @return Whether the Uri authority is MediaProvider. */ private fun isMediaDocument(uri: Uri?): Boolean { return "com.android.providers.media.documents" == uri?.authority } /** * content://com.huawei.hidisk.fileprovider/root/storage/emulated/0/Android/data/com.xxx.xxx/ * * @param uri * @return */ private fun isHuaWeiUri(uri: Uri?): Boolean { return "com.huawei.hidisk.fileprovider" == uri?.authority } }