在安卓系统中将文件的Uri转换为File

510

如何在Android中最简单地将保存file:类型的android.net.Uri对象转换为java.io.File对象?

我尝试了以下方法,但它并不起作用:

File file = new File(Environment.getExternalStorageDirectory(), "read.me");
Uri uri = Uri.fromFile(file);
File auxFile = new File(uri.toString());
assertEquals(file.getAbsolutePath(), auxFile.getAbsolutePath());

这是我的解决方案!它运行良好!https://dev59.com/A3E85IYBdhLWcg3waCxT#9989900 - cesards
1
请参见https://dev59.com/9W035IYBdhLWcg3wC7cR。 - zafirk
assertEquals是做什么用的? - Huzaifa Asif
Uri.fromFile上下文对我不起作用 :( - Bay
29个回答

947

你想要的是...

new File(uri.getPath());

...而不是...

new File(uri.toString());
注意事项
  1. 对于一个名为uriandroid.net.Uri对象,其创建方式与问题中一致,uri.toString()方法返回一个格式为"file:///mnt/sdcard/myPicture.jpg"的字符串,而uri.getPath()方法返回一个格式为"/mnt/sdcard/myPicture.jpg"的字符串。
  2. 我知道Android中有关文件存储的细节。但我在这个答案中的意图是精确回答提问者所问的问题,而不涉及这些细节。

5
这两者之间有什么区别?toString是做什么用的? - Merlyn Morgan-Graham
96
url.toString() 返回的字符串格式为:"file:///mnt/sdcard/myPicture.jpg",而 url.getPath() 返回的字符串格式为:"/mnt/sdcard/myPicture.jpg",即没有前缀的方案类型。 - Adil Hussain
48
如果URI是Images.Media.EXTERNAL_CONTENT_URI(例如从Gallery的Intent.ACTION_PICK中获取),则您需要像https://dev59.com/8Gw05IYBdhLWcg3w72M0中的查找它。 - Nuthatch
2
@Chlebta 你可以尝试使用一个叫做 Picasso 的库,它可以非常轻松地将文件(甚至是URL)加载到ImageView中。 - Virat Singh
36
大多数时候,当我尝试打开给定的文件时,会收到“open failed: ENOENT(没有此文件或目录)”的错误提示。如果 Uri 是图像的内容 Uri,例如,则一定不起作用。请注意,此处仅为翻译,不包含任何解释或其他内容。 - shoke
显示剩余19条评论

112

4
这个应该得到更多的赞。ContentResolver 是默认查找 Android.Net.Uri 路径解析的对象。 - Mike Makarov
我想再加一个赞,但结果发现我只能点一次! - Xenolion
@M.UsmanKhan 这取决于你的需求。如果你想在以后使用该文件,请复制它。否则,只需将其用作输入流即可。你不应该依赖其他应用程序为你保留文件。 - VaiTon
1
@VaiTon86。我们想要从URI获取一个文件对象,而不复制文件。 - M. Usman Khan
同意这应该是最佳答案。File to URI 不起作用,因为权限问题,特别是在 Android 11 及以上版本中。必须使用 contentResolver。https://developer.android.com/guide/topics/providers/content-provider-basics#SimpleQuery - MG Developer
显示剩余4条评论

70

经过长时间的搜索,以下是对我有效的方法:

File file = new File(getPath(uri));


public String getPath(Uri uri) 
    {
        String[] projection = { MediaStore.Images.Media.DATA };
        Cursor cursor = getContentResolver().query(uri, projection, null, null, null);
        if (cursor == null) return null;
        int column_index =             cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
        cursor.moveToFirst();
        String s=cursor.getString(column_index);
        cursor.close();
        return s;
    }

8
managedQuery已被弃用,请使用getContentResolver().query(...)代替,该方法适用于API 11及以上版本。对于API 11以下的设备,添加一个条件语句。 - Kyle Falconer
5
因某些原因,这个给我的返回值是 null。而我的安卓 uri 返回的内容类似于:{content://com.android.providers.media.documents/document/image%3A2741} Android.Net.UriInvoker。 - erkinyldz
这不是一个好的解决方案,因为它不能在所有平台上工作。有些设备甚至不会填充此列,我建议像@CommonWare在之前的答案中建议的那样,让内容提供者处理此问题。 - Liran Cohen
3
仅适用于图像文件,其他文件类型怎么办? - Pratibha sarve
4
请确保你从Intent.ACTION_PICK中获取你的URI,而不是Intent.ACTION_GET_CONTENT,因为后者没有MediaStore.Images.Media.DATA。 - Denys Lobur
显示剩余3条评论

40

Android + Kotlin

  1. 添加Kotlin Android扩展的依赖:

    implementation 'androidx.core:core-ktx:{latestVersion}'

  2. 从URI获取文件:

    uri.toFile()


1
我尝试了这个:androidx.core:core-ktx:1.5.0-alpha01。但是没有看到“toFile”函数。 - M. Usman Khan
2
我将ktx移动到我的库构建文件中,然后就看到它了。谢谢! - M. Usman Khan
87
错误:java.lang.IllegalArgumentException: Uri缺少'file'协议:content://media/external/images/media/54545。翻译:这是一个错误消息,提示缺少'file'协议。出错的URI是“content://media/external/images/media/54545”。 - Kishan Solanki
“Just move to top” 在 Android + Java 中是什么意思?谢谢。 - Syl OR
Java的答案已经由Adil给出:https://dev59.com/VnA85IYBdhLWcg3wBO7h#8370299 - Hemant Kaushik
显示剩余3条评论

38

最佳方案

创建一个简单的FileUtil类,用于创建、复制和重命名文件

我曾尝试使用uri.toString()uri.getPath(),但对我没有起作用。我最终找到了这个解决方案。

import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.provider.OpenableColumns;
import android.util.Log;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

public class FileUtil {
    private static final int EOF = -1;
    private static final int DEFAULT_BUFFER_SIZE = 1024 * 4;

    private FileUtil() {

    }

    public static File from(Context context, Uri uri) throws IOException {
        InputStream inputStream = context.getContentResolver().openInputStream(uri);
        String fileName = getFileName(context, uri);
        String[] splitName = splitFileName(fileName);
        File tempFile = File.createTempFile(splitName[0], splitName[1]);
        tempFile = rename(tempFile, fileName);
        tempFile.deleteOnExit();
        FileOutputStream out = null;
        try {
            out = new FileOutputStream(tempFile);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
        if (inputStream != null) {
            copy(inputStream, out);
            inputStream.close();
        }

        if (out != null) {
            out.close();
        }
        return tempFile;
    }

    private static String[] splitFileName(String fileName) {
        String name = fileName;
        String extension = "";
        int i = fileName.lastIndexOf(".");
        if (i != -1) {
            name = fileName.substring(0, i);
            extension = fileName.substring(i);
        }

        return new String[]{name, extension};
    }

    private static String getFileName(Context context, Uri uri) {
        String result = null;
        if (uri.getScheme().equals("content")) {
            Cursor cursor = context.getContentResolver().query(uri, null, null, null, null);
            try {
                if (cursor != null && cursor.moveToFirst()) {
                    result = cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME));
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                if (cursor != null) {
                    cursor.close();
                }
            }
        }
        if (result == null) {
            result = uri.getPath();
            int cut = result.lastIndexOf(File.separator);
            if (cut != -1) {
                result = result.substring(cut + 1);
            }
        }
        return result;
    }

    private static File rename(File file, String newName) {
        File newFile = new File(file.getParent(), newName);
        if (!newFile.equals(file)) {
            if (newFile.exists() && newFile.delete()) {
                Log.d("FileUtil", "Delete old " + newName + " file");
            }
            if (file.renameTo(newFile)) {
                Log.d("FileUtil", "Rename file to " + newName);
            }
        }
        return newFile;
    }

    private static long copy(InputStream input, OutputStream output) throws IOException {
        long count = 0;
        int n;
        byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
        while (EOF != (n = input.read(buffer))) {
            output.write(buffer, 0, n);
            count += n;
        }
        return count;
    }
}

在您的代码中使用FileUtil类

try {
         File file = FileUtil.from(MainActivity.this,fileUri);
         Log.d("file", "File...:::: uti - "+file .getPath()+" file -" + file + " : " + file .exists());

  } catch (IOException e) {
          e.printStackTrace();
  }

这段代码是可以工作的,但当我从图库中选择图片时,在以下这行代码会抛出错误:InputStream inputStream = context.getContentResolver().openInputStream(uri); - Vahid Akbari
它的表现非常好,但在一些安卓设备上会抛出FileNotFoundException异常。 - vishwajit76
使用此代码从相机中选择照片 https://stackoverflow.com/a/51095098/7008132 - vishwajit76

25

编辑:抱歉,我应该在之前进行更好的测试。这个应该可以工作:

new File(new URI(androidURI.toString()));

URI 是 java.net.URI。


5
啊,但问题是“Uri”,而不是“URI”。 人们必须注意这一点 :) - Muz
1
@Muz,我相信答案是正确的。androidURI是一个android.net.Uri。java.net.URI(在Android上确实存在)仅用作转换过程的一部分。 - Matthew Flaschen
2
我们能不能这样做:new File(uri.getPath()); - Bart Burg
15
这段话的意思是:出现了错误,抛出了IllegalArgumentException异常,异常信息为:“在URI中预期文件方案:content://media/external/images/media/80038”。需要注意的是,“IllegalArgumentException”是Java编程语言中表示参数不正确的异常类名。 - Jacek Kwiecień
9
在使用此方法后,我遇到了java.lang.IllegalArgumentException: Expected file scheme in URI: content://com.shajeel.daily_monitoring.localstorage.documents.localstorage.documents/document/%2Fstorage%2Femulated%2F0%2FBook1.xlsx 异常。 - Shajeel Afzal
显示剩余5条评论

15

这些方法都对我不起作用。我发现下面这个是可行的解决方案。但我的情况特别适用于图片

String[] filePathColumn = { MediaStore.Images.Media.DATA };
Cursor cursor = getActivity().getContentResolver().query(uri, filePathColumn, null, null, null);
cursor.moveToFirst();
int columnIndex = cursor.getColumnIndex(filePathColumn[0]);
String filePath = cursor.getString(columnIndex);
cursor.close();

这应该是一个答案! :) 我在API 25上进行了测试。 - iroiroys
8
请注意,这种技术在Android Q上被禁止了。你再也不能访问DATA列(而且一开始这种方法就不可靠)。 - CommonsWare

13

Kotlin使这更加简单:

val file = File(uri.path)

或者如果您正在使用Android的Kotlin扩展

val file = uri.toFile()

更新: 对于图片,它返回“Uri缺少'file'方案:content://”

感谢评论


51
错误答案,特别是对于图像,它返回“Uri缺少'file'方案:content://” - Sever
好答案,我遇到了一个问题“Uri缺少'file'方案:content://”,这帮助我支持我的假设。 - moralejaSinCuentoNiProverbio
@Sever 这不是一个坏答案,只是因为它只适用于具有“file”方案的Uri。方法本身已经描述了这一点 - 从给定的Uri创建一个文件。请注意,当在缺乏文件方案的Uri上调用时,这将抛出IllegalArgumentException异常。如果你在谈论MediaStore Uri或DocumentProvider Uri,那么你无论如何都不应该将其转换为File - undefined

12

另一种达到这个目的的方法是创建一个临时文件,具体步骤如下:

fun createTmpFileFromUri(context: Context, uri: Uri, fileName: String): File? {
    return try {
        val stream = context.contentResolver.openInputStream(uri)
        val file = File.createTempFile(fileName, "", context.cacheDir)
        org.apache.commons.io.FileUtils.copyInputStreamToFile(stream,file)
        file
    } catch (e: Exception) {
        e.printStackTrace()
        null
    }
}

我们使用Apache Commons库中的FileUtils类。将其添加到您的项目中:
implementation "commons-io:commons-io:2.7"

请确保在使用后调用file.delete()进行删除。 有关更多信息,请查看文档

4
已测试并可在API 31上运行。 - Franz Andel

8
如果您拥有符合DocumentContractUri,那么您可能不想使用File。如果您使用kotlin,则可以使用DocumentFile来执行以前使用File执行的操作,并使用ContentResolver获取流。其他所有操作几乎都会出现问题。请注意保留HTML标记。

请展示如何使用它? - CoolMind

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接