Android:从内容URI获取文件URI?


131

在我的应用程序中,用户将选择一个由应用程序处理的音频文件。问题是,为了使该应用程序执行我希望它对音频文件执行的操作,我需要将URI设置为文件格式。当我使用Android的本地音乐播放器浏览应用中的音频文件时,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的操作。


1
您正在使用哪些不喜欢内容URI的电话?
Phil Lello

1
许多Uris内容无法获取文件路径,因为并非所有Uris内容都具有文件路径。不要使用文件路径。
Mooing Duck 2016年

Answers:


151

只需使用getContentResolver().openInputStream(uri)得到一个InputStream从URI。

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


12
检查从选择器活动返回给您的URI的方案。如果为uri.getScheme.equals(“ content”),请使用内容解析器将其打开。如果是uri.Scheme.equals(“ file”),请使用常规文件方法将其打开。无论哪种方式,您最终都会得到一个InputStream,可以使用通用代码对其进行处理。
詹森·勒布朗

16
其实,我只是重读getContentResolver()的文档。openInputStream(),和它的“内容”或“文件”方案自动工作,所以你并不需要检查方案......如果你可以安全地假设它将始终为content://或file://,则openInputStream()将始终有效。
詹森·勒布朗

57
有没有一种方法来获取File而不是InputStream(从content:...)?
AlikElzin-kilaka 2012年

4
@kilaka您可以获取文件路径,但很痛苦。请参阅stackoverflow.com/a/20559418/294855
Danyal Aytekin

7
对于使用依赖于文件而不是FileStreams的封闭源API的用户来说,这个答案是不够的,但是他们想要使用OS来允许用户选择文件。@DanyalAytekin引用的答案正是我所需要的(实际上,我能够减少很多麻烦,因为我确切地知道我正在使用哪种文件)。
monkey0506

45

这是一个过时的,老旧的方法,可以克服某些特定的内容解析器的痛点。将其与一些大颗粒的盐一起使用,并尽可能使用适当的openInputStream API。

您可以使用Content Resolver file://content://URI 获取路径:

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);

1
谢谢,这很好。我无法像接受的答案所示使用InputStream。
拉丹

4
这仅适用于本地文件,例如,不适用于Google云端硬盘
蓝色,2015年

1
有时可行,有时返回file:/// storage / emulated / 0 / ...不存在。
Reza Mohammadi

1
android.provider.MediaStore.Images.ImageColumns.DATA如果方案是,是否总是保证“ _data”()列存在content://
爱德华·福尔克

2
这是一个主要的反模式。某些ContentProvider确实提供了该列,但是File当您尝试绕过ContentResolver时,不能保证您具有读/写访问权限。使用ContentResolver方法在content://uri 上运行,这是Google工程师鼓励的官方方法。
user1643723

9

如果您拥有内容Uri,则content://com.externalstorage...可以使用此方法获取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;
}

您可以检查Uri的每个部分是否正在使用println。以下列出了我的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

@Bondax是的,您应该使用Content Uris,而不是文件路径或File Uris。这样就引入了存储访问框架。但是,如果您希望获取文件uri,则这是其他答案的最正确方法,因为您使用的是DocumentsContract类。如果您在Github中查看Google样本,您会发现它们也用于此类以获取文件夹的子文件夹。
Thracian

而且DocumentFile类也是api 19中的新增功能,以及您如何使用SAF中的uris。正确的方法是为您的应用程序使用标准路径,并要求用户通过SAF ui授予文件夹权限,将Uri字符串保存为共享首选项,以及何时需要访问带有DocumentFile对象的文件夹
Thracian

7

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

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


但是有时候我们只需要路径。我们实际上不必将文件加载到内存中。
Kimi Chiu

2
如果您没有访问权限,则“路径”是无用的。例如,如果应用程序给您提供了一个content://uri(对应于其私有内部目录中的文件),则您将无法File在新的Android版本中将该uri与API 一起使用。ContentResolver旨在克服这种安全限制。如果您从ContentResolver获得了uri,则可以期望它能够正常工作。
user1643723

5

启发性的答案是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;
}

谢谢,已经看了一个星期了。不需要为此复制文件,但是确实可以。
justdan0227

3

好吧,我来晚了,但是我的代码已经过测试

从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为字节数组,但是这不相关的问题,我只是复制我的全部代码,显示的使用情况FileInputStream,并InputStream为两者都在我的代码工作一样。

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

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

context=ActivityName.this;//活动中


我知道这与问题无关,但是您将如何使用byte[] videoBytes;?大多数答案仅显示如何InputStream与图像一起使用。
KRK
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.