【问题标题】:Android: Getting a file URI from a content URI?Android:从内容 URI 获取文件 URI?
【发布时间】:2023-03-09 01:33:01
【问题描述】:

在我的应用程序中,用户将选择应用程序处理的音频文件。问题是,为了让应用程序执行我希望它对音频文件执行的操作,我需要 URI 为文件格式。当我使用Android的原生音乐播放器在app中浏览音频文件时,URI是一个内容URI,看起来是这样的:

content://media/external/audio/media/710

但是,使用流行的文件管理器应用程序 Astro,我得到以下信息:

file:///sdcard/media/audio/ringtones/GetupGetOut.mp3

后者对我来说更易于使用,但我当然希望该应用程序具有用户选择的音频文件的功能,而不管他们使用什么程序来浏览他们的收藏。所以我的问题是,有没有办法将 content:// 样式的 URI 转换为 file:// URI?否则,您会建议我如何解决这个问题?下面是调用选择器的代码,供参考:

Intent ringIntent = new Intent();
ringIntent.setType("audio/mp3");
ringIntent.setAction(Intent.ACTION_GET_CONTENT);
ringIntent.addCategory(Intent.CATEGORY_OPENABLE);
startActivityForResult(Intent.createChooser(ringIntent, "Select Ringtone"), SELECT_RINGTONE);

我对内容 URI 执行以下操作:

m_ringerPath = m_ringtoneUri.getPath();
File file = new File(m_ringerPath);

然后用所述文件做一些 FileInputStream 的东西。

【问题讨论】:

  • 您正在使用哪些不喜欢内容 URI 的调用?
  • 有很多内容Uris无法获取文件路径,因为并非所有内容Uris都有文件路径。不要使用文件路径。

标签: android file uri


【解决方案1】:

您可以使用此功能从新 android 和旧版本的 uri 中获取文件

fun getFileFromUri(uri: Uri): File? {
    if (uri.path == null) {
        return null
    }
    var realPath = String()
    val databaseUri: Uri
    val selection: String?
    val selectionArgs: Array<String>?
    if (uri.path!!.contains("/document/image:")) {
        databaseUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI
        selection = "_id=?"
        selectionArgs = arrayOf(DocumentsContract.getDocumentId(uri).split(":")[1])
    } else {
        databaseUri = uri
        selection = null
        selectionArgs = null
    }
    try {
        val column = "_data"
        val projection = arrayOf(column)
        val cursor = context.contentResolver.query(
            databaseUri,
            projection,
            selection,
            selectionArgs,
            null
        )
        cursor?.let {
            if (it.moveToFirst()) {
                val columnIndex = cursor.getColumnIndexOrThrow(column)
                realPath = cursor.getString(columnIndex)
            }
            cursor.close()
        }
    } catch (e: Exception) {
        Log.i("GetFileUri Exception:", e.message ?: "")
    }
    val path = if (realPath.isNotEmpty()) realPath else {
        when {
            uri.path!!.contains("/document/raw:") -> uri.path!!.replace(
                "/document/raw:",
                ""
            )
            uri.path!!.contains("/document/primary:") -> uri.path!!.replace(
                "/document/primary:",
                "/storage/emulated/0/"
            )
            else -> return null
        }
    }
    return File(path)
}

【讨论】:

  • 啊,非常感谢 :)
【解决方案2】:

如果您有一个带有content://com.externalstorage... 的内容 Uri,您可以使用此方法获取文件夹或文件的绝对路径在 Android 19 或更高版本上

public static String getPath(final Context context, final Uri uri) {
    final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;

    // DocumentProvider
    if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) {
        System.out.println("getPath() uri: " + uri.toString());
        System.out.println("getPath() uri authority: " + uri.getAuthority());
        System.out.println("getPath() uri path: " + uri.getPath());

        // ExternalStorageProvider
        if ("com.android.externalstorage.documents".equals(uri.getAuthority())) {
            final String docId = DocumentsContract.getDocumentId(uri);
            final String[] split = docId.split(":");
            final String type = split[0];
            System.out.println("getPath() docId: " + docId + ", split: " + split.length + ", type: " + type);

            // This is for checking Main Memory
            if ("primary".equalsIgnoreCase(type)) {
                if (split.length > 1) {
                    return Environment.getExternalStorageDirectory() + "/" + split[1] + "/";
                } else {
                    return Environment.getExternalStorageDirectory() + "/";
                }
                // This is for checking SD Card
            } else {
                return "storage" + "/" + docId.replace(":", "/");
            }

        }
    }
    return null;
}

您可以使用println 检查 Uri 的每个部分。下面列出了我的 SD 卡和设备主存储器的返回值。如果文件在内存中,您可以访问和删除,但我无法使用此方法从 SD 卡中删除文件,只能使用此绝对路径读取或打开图像。如果您找到使用此方法删除的解决方案,请分享。

SD 卡

getPath() uri: content://com.android.externalstorage.documents/tree/612E-B7BF%3A/document/612E-B7BF%3A
getPath() uri authority: com.android.externalstorage.documents
getPath() uri path: /tree/612E-B7BF:/document/612E-B7BF:
getPath() docId: 612E-B7BF:, split: 1, type: 612E-B7BF

主内存

getPath() uri: content://com.android.externalstorage.documents/tree/primary%3A/document/primary%3A
getPath() uri authority: com.android.externalstorage.documents
getPath() uri path: /tree/primary:/document/primary:
getPath() docId: primary:, split: 1, type: primary

如果您希望在获取路径后使用 file:/// 获取 Uri

DocumentFile documentFile = DocumentFile.fromFile(new File(path));
documentFile.getUri() // will return a Uri with file Uri

【讨论】:

  • 我认为这不是在应用程序中执行此操作的正确方法。可悲的是我在一个快速的项目中使用它
  • @Bondax 是的,您应该使用 Content Uris 而不是文件路径或文件 Uris。这就是引入存储访问框架的方式。但是,如果您希望获取文件 uri,这是其他答案的最正确方法,因为您使用 DocumentsContract 类。如果您在 Github 中查看 Google 示例,您会发现它们也使用此类来获取文件夹的子文件夹。
  • DocumentFile 类也是 api 19 中的新增内容,以及如何使用 SAF 中的 uris。正确的方法是为您的应用程序使用标准路径,并要求用户通过 SAF ui 授予文件夹权限,将 Uri 字符串保存到共享首选项,并在需要使用 DocumentFile 对象访问文件夹时
【解决方案3】:

你可以通过简单的方式通过uri获取文件名

Retrieving file information

fun get_filename_by_uri(uri : Uri) : String{
    contentResolver.query(uri, null, null, null, null).use { cursor ->
        cursor?.let {
            val nameIndex = it.getColumnIndex(OpenableColumns.DISPLAY_NAME)
            it.moveToFirst()
            return it.getString(nameIndex)
        }
    }
    return ""
}

使用起来很容易阅读

contentResolver.openInputStream(uri)

【讨论】:

    【解决方案4】:

    试试这个....

    从内容 uri 中获取文件

    fun fileFromContentUri(context: Context, contentUri: Uri): File {
        // Preparing Temp file name
        val fileExtension = getFileExtension(context, contentUri)
        val fileName = "temp_file" + if (fileExtension != null) ".$fileExtension" else ""
    
        // Creating Temp file
        val tempFile = File(context.cacheDir, fileName)
        tempFile.createNewFile()
    
        try {
            val oStream = FileOutputStream(tempFile)
            val inputStream = context.contentResolver.openInputStream(contentUri)
    
            inputStream?.let {
                copy(inputStream, oStream)
            }
    
            oStream.flush()
        } catch (e: Exception) {
            e.printStackTrace()
        }
    
        return tempFile
    }
    
    private fun getFileExtension(context: Context, uri: Uri): String? {
        val fileType: String? = context.contentResolver.getType(uri)
        return MimeTypeMap.getSingleton().getExtensionFromMimeType(fileType)
    }
    
    @Throws(IOException::class)
    private fun copy(source: InputStream, target: OutputStream) {
        val buf = ByteArray(8192)
        var length: Int
        while (source.read(buf).also { length = it } > 0) {
            target.write(buf, 0, length)
        }
    }
    

    【讨论】:

    • 救命稻草。关于pdf,唯一对我有用的东西。
    • 谢谢,老兄,你的回答再次帮助了我!我希望我能再次投票;)
    • 最佳答案在这里。
    • 感谢它的工作
    【解决方案5】:

    这是一个旧的答案,它用过时的方式来克服一些特定的内容解析器的痛点。如果可能的话,尽量使用适当的 openInputStream API。

    您可以使用内容解析器从 content:// URI 中获取 file:// 路径:

    String filePath = null;
    Uri _uri = data.getData();
    Log.d("","URI = "+ _uri);                                       
    if (_uri != null && "content".equals(_uri.getScheme())) {
        Cursor cursor = this.getContentResolver().query(_uri, new String[] { android.provider.MediaStore.Images.ImageColumns.DATA }, null, null, null);
        cursor.moveToFirst();   
        filePath = cursor.getString(0);
        cursor.close();
    } else {
        filePath = _uri.getPath();
    }
    Log.d("","Chosen path = "+ filePath);
    

    【讨论】:

    • 谢谢,这很好用。我不能像接受的答案所暗示的那样使用 InputStream。
    • 这仅适用于本地文件,例如它不适用于 Google Drive
    • 有时有效,有时返回不存在的 file:///storage/emulated/0/...。
    • 如果方案为content://,列“_data”(android.provider.MediaStore.Images.ImageColumns.DATA)是否始终保证存在?
    • 这是一个主要的反模式。一些 ContentProvider 确实提供了该列,但是当您尝试绕过 ContentResolver 时,不能保证您具有对 File 的读/写访问权限。使用 ContentResolver 方法对 content:// uris 进行操作,这是官方的做法,受到 Google 工程师的鼓励。
    【解决方案6】:

    启发答案是Jason LaBrunDarth Raven。尝试已经回答的方法导致我得到以下解决方案,其中可能主要涵盖光标空情况和从 content://file://

    的转换

    要转换文件,从获得的uri中读写文件

    public static Uri getFilePathFromUri(Uri uri) throws IOException {
        String fileName = getFileName(uri);
        File file = new File(myContext.getExternalCacheDir(), fileName);
        file.createNewFile();
        try (OutputStream outputStream = new FileOutputStream(file);
             InputStream inputStream = myContext.getContentResolver().openInputStream(uri)) {
            FileUtil.copyStream(inputStream, outputStream); //Simply reads input to output stream
            outputStream.flush();
        }
        return Uri.fromFile(file);
    }
    

    为了获取文件名使用,它会覆盖光标为空的情况

    public static String getFileName(Uri uri) {
        String fileName = getFileNameFromCursor(uri);
        if (fileName == null) {
            String fileExtension = getFileExtension(uri);
            fileName = "temp_file" + (fileExtension != null ? "." + fileExtension : "");
        } else if (!fileName.contains(".")) {
            String fileExtension = getFileExtension(uri);
            fileName = fileName + "." + fileExtension;
        }
        return fileName;
    }
    

    从 mime 类型转换为文件扩展名是个不错的选择

     public static String getFileExtension(Uri uri) {
        String fileType = myContext.getContentResolver().getType(uri);
        return MimeTypeMap.getSingleton().getExtensionFromMimeType(fileType);
    }
    

    获取文件名的光标

    public static String getFileNameFromCursor(Uri uri) {
        Cursor fileCursor = myContext.getContentResolver().query(uri, new String[]{OpenableColumns.DISPLAY_NAME}, null, null, null);
        String fileName = null;
        if (fileCursor != null && fileCursor.moveToFirst()) {
            int cIndex = fileCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
            if (cIndex != -1) {
                fileName = fileCursor.getString(cIndex);
            }
        }
        return fileName;
    }
    

    【讨论】:

    • 谢谢,关注这个一周了。不喜欢为此复制文件,但它确实有效。
    【解决方案7】:

    好吧,我回答有点晚了,但我的代码已经过测试

    从 uri 检查方案:

     byte[] videoBytes;
    
    if (uri.getScheme().equals("content")){
            InputStream iStream =   context.getContentResolver().openInputStream(uri);
                videoBytes = getBytes(iStream);
            }else{
                File file = new File(uri.getPath());
                FileInputStream fileInputStream = new FileInputStream(file);     
                videoBytes = getBytes(fileInputStream);
            }
    

    在上面的答案中,我将视频 uri 转换为字节数组,但这与问题无关, 我刚刚复制了我的完整代码以显示FileInputStreamInputStream 的用法,因为它们在我的代码中的工作方式相同。

    我在 Fragment 中使用了 getActivity() 变量上下文,而在 Activity 中它只是 ActivityName.this

    context=getActivity(); //在片段中

    context=ActivityName.this;// 活动中

    【讨论】:

    • 我知道这与问题无关,但是您将如何使用byte[] videoBytes;?大多数答案仅显示如何将InputStream 与图像一起使用。
    【解决方案8】:

    尝试通过调用 ContentResolver.query() 来处理带有 content:// 方案的 URI 不是一个好的解决方案。在运行 4.2.2 的 HTC Desire 上,您可以获得 NULL 作为查询结果。

    为什么不改用 ContentResolver? https://stackoverflow.com/a/29141800/3205334

    【讨论】:

    • 但有时我们只需要路径。我们实际上不必将文件加载到内存中。
    • “路径”如果你没有访问权限就没有用。例如,如果应用程序为您提供了一个 content:// uri,对应于它的私有内部目录中的文件,您将无法在新的 Android 版本中将该 uri 与 File API 一起使用。 ContentResolver 旨在克服这种安全限制。如果您从 ContentResolver 获得 uri,则可以预期它可以正常工作。
    【解决方案9】:

    只需使用 getContentResolver().openInputStream(uri) 从 URI 中获取 InputStream

    http://developer.android.com/reference/android/content/ContentResolver.html#openInputStream(android.net.Uri)

    【讨论】:

    • 检查从选择器活动返回给您的 URI 的方案。如果如果 uri.getScheme.equals("content"),则使用内容解析器打开它。如果 uri.Scheme.equals("file"),使用普通文件方法打开它。无论哪种方式,您最终都会得到一个可以使用通用代码处理的 InputStream。
    • 实际上,我只是重新阅读了 getContentResolver().openInputStream() 的文档,它自动适用于“内容”或“文件”的方案,所以你不要 需要检查方案...如果您可以安全地假设它始终是 content:// 或 file:// 那么 openInputStream() 将始终有效。
    • 有没有办法获取文件而不是 InputStream(来自内容:...)?
    • @kilaka 你可以得到文件路径但是很痛苦。见stackoverflow.com/a/20559418/294855
    • 对于使用依赖文件而不是 FileStreams 的闭源 API,但又希望使用操作系统允许用户选择文件的人来说,这个答案是不够的。 @DanyalAytekin 引用的答案正是我所需要的(事实上,我能够减少很多脂肪,因为我确切地知道我正在处理什么样的文件)。
    猜你喜欢
    • 2014-06-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-03-01
    • 1970-01-01
    • 2014-07-31
    • 1970-01-01
    • 2014-08-13
    相关资源
    最近更新 更多