Android: obtendo um URI de arquivo a partir de um URI de conteúdo?

133

No meu aplicativo, o usuário deve selecionar um arquivo de áudio que o aplicativo manipula. O problema é que, para que o aplicativo faça o que eu quero com os arquivos de áudio, preciso que o URI esteja no formato de arquivo. Quando uso o reprodutor de música nativo do Android para procurar o arquivo de áudio no aplicativo, o URI é um URI de conteúdo, com a seguinte aparência:

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

No entanto, usando o popular aplicativo gerenciador de arquivos Astro, recebo o seguinte:

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

O último é muito mais acessível para eu trabalhar, mas é claro que eu quero que o aplicativo tenha funcionalidade com o arquivo de áudio que o usuário escolhe, independentemente do programa usado para navegar em sua coleção. Então, minha pergunta é: existe uma maneira de converter o content://estilo URI em um file://URI? Caso contrário, o que você recomendaria para eu resolver esse problema? Aqui está o código que chama o seletor, para referência:

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

Eu faço o seguinte com o URI do conteúdo:

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

Em seguida, faça algumas coisas no FileInputStream com o referido arquivo.

JMRboosties
fonte
1
Quais chamadas você usa que não gostam de URIs de conteúdo?
Phil Lello
1
Existem muitos Uris de conteúdo nos quais você não pode obter o caminho do arquivo, porque nem todos os Uris de conteúdo têm caminhos de arquivo. Não use caminhos de arquivo.
achou do

Respostas:

153

Basta usar getContentResolver().openInputStream(uri)para obter um InputStreamde um URI.

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

Jason LeBrun
fonte
12
Verifique o esquema do URI retornado da atividade do seletor. Se for uri.getScheme.equals ("content"), abra-o com um resolvedor de conteúdo. Se o uri.Scheme.equals ("arquivo"), abra-o usando métodos normais de arquivo. De qualquer forma, você terminará com um InputStream que pode processar usando código comum.
Jason LeBrun
16
Na verdade, eu apenas reli os documentos para getContentResolver (). OpenInputStream () e funciona automaticamente para esquemas de "conteúdo" ou "arquivo", para que você não precise verificar o esquema ... se puder com segurança suponha que sempre haverá conteúdo: // ou file: //, então openInputStream () sempre funcionará.
Jason LeBrun
57
Existe uma maneira de obter o arquivo em vez do InputStream (do conteúdo: ...)?
AlikElzin-Kilaka
4
@kilaka Você pode obter o caminho do arquivo, mas é doloroso. Veja stackoverflow.com/a/20559418/294855
Danyal Aytekin
7
Esta resposta é insuficiente para alguém que está usando uma API de código fechado que depende de arquivos em vez de FileStreams, mas ainda deseja usar o sistema operacional para permitir que o usuário selecione o arquivo. A resposta que @DanyalAytekin referenciou foi exatamente o que eu precisava (e, de fato, consegui cortar muita gordura porque sei exatamente com que tipos de arquivos estou trabalhando).
monkey0506
45

Esta é uma resposta antiga, com uma maneira obsoleta e hacky de superar alguns pontos problemáticos específicos do resolvedor de conteúdo. Leve-o com grandes grãos de sal e use a API openInputStream adequada, se possível.

Você pode usar o resolvedor de conteúdo para obter um file://caminho a partir do 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);
Rafael Nobre
fonte
1
Obrigado, isso funcionou perfeitamente. Não pude usar um InputStream como a resposta aceita sugere.
Ldam
4
Isso funciona apenas para arquivos locais, por exemplo, não funciona no Google Drive
bluewhile
1
Às vezes funciona, às vezes retorna o arquivo: /// storage / emulated / 0 / ... que não existe.
Reza Mohammadi
1
É android.provider.MediaStore.Images.ImageColumns.DATAgarantido que a coluna "_data" ( ) sempre existe se o esquema for content://?
Edward Falk
2
Este é um grande anti-padrão. Alguns ContentProviders fornecem essa coluna, mas não é garantido que você tenha acesso de leitura / gravação ao Filetentar ignorar o ContentResolver. Use os métodos ContentResolver para operar com content://uris, esta é a abordagem oficial, incentivada pelos engenheiros do Google.
precisa saber é o seguinte
9

Se você tiver um conteúdo Uri, content://com.externalstorage...poderá usar esse método para obter o caminho absoluto de uma pasta ou arquivo no Android 19 ou superior .

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

Você pode verificar se cada parte do Uri está usando println. Os valores retornados para o cartão SD e a memória principal do dispositivo estão listados abaixo. Você pode acessar e excluir se o arquivo estiver na memória, mas não foi possível excluir o arquivo do cartão SD usando esse método, apenas ler ou abrir uma imagem usando esse caminho absoluto. Se você encontrar uma solução para excluir usando esse método, compartilhe. CARTÃO 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

MEMÓRIA PRINCIPAL

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

Se você deseja obter Uri file:///depois de obter o caminho, use

DocumentFile documentFile = DocumentFile.fromFile(new File(path));
documentFile.getUri() // will return a Uri with file Uri
Trácia
fonte
não acho que seja o caminho certo para fazer isso em um aplicativo. Infelizmente eu estou usando-o em um projeto quicky
Bondax
@Bondax Sim, você deve trabalhar com o Content Uris em vez de caminhos de arquivo ou File Uris. É assim que a estrutura de acesso ao armazenamento é introduzida. Mas, se você deseja obter o arquivo uri, esta é a maneira mais correta de outras respostas, pois você usa a classe DocumentsContract. Se você verificar exemplos do Google no Github, verá que eles também usam essa classe para obter subpastas de uma pasta.
Thracian
E a classe DocumentFile também é uma nova adição na API 19 e como você usa os uris do SAF. A maneira correta é usar um caminho padrão para você aplicativo e pedir usuário para dar permissão para uma pasta através da SAF ui, save corda Uri às preferências compartilhados, e quando é necessário acesso à pasta com DocumentFile objetos
Thracian
7

Tentar manipular o URI com o esquema content: // chamando ContentResolver.query()não é uma boa solução. No HTC Desire executando o 4.2.2, você pode obter NULL como resultado da consulta.

Por que não usar o ContentResolver? https://stackoverflow.com/a/29141800/3205334

Darth Raven
fonte
Mas às vezes precisamos apenas do caminho. Nós realmente não precisamos carregar o arquivo na memória.
Kimi Chiu
2
"O caminho" é inútil se você não tiver direitos de acesso. Por exemplo, se um aplicativo content://fornecer uma uri, correspondente ao arquivo em seu diretório interno privado, você não poderá usá-la com FileAPIs em novas versões do Android. O ContentResolver foi projetado para superar esse tipo de limitações de segurança. Se você recebeu a uri do ContentResolver, pode esperar que funcione.
precisa saber é o seguinte
5

Respostas inspiradas são Jason LaBrun e Darth Raven . Tentar abordagens já respondidas me levou à solução abaixo, que pode cobrir principalmente casos nulos do cursor e conversão de conteúdo: // para file: //

Para converter arquivos, leia e escreva o arquivo a partir de uri ganho

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

Para obter o uso do nome do arquivo, ele cobrirá o caso nulo do cursor

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

Há uma boa opção para converter do tipo MIME para extensão de arquivo

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

Cursor para obter o nome do arquivo

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;
}
Muhammed Yalçın Kuru
fonte
Obrigado, olha isso há uma semana. Não gosta de copiar um arquivo para isso, mas funciona.
justdan0227
3

Bem, estou um pouco atrasado para responder, mas meu código é testado

esquema de verificação da 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);
        }

Na resposta acima, converti a URL do vídeo para a matriz de bytes, mas isso não está relacionado à pergunta, apenas copiei meu código completo para mostrar o uso de FileInputStreameInputStream como ambos estão funcionando da mesma forma no meu código.

Eu usei o contexto variável que é getActivity () no meu fragmento e em Activity simplesmente é ActivityName.this

context=getActivity(); // no fragmento

context=ActivityName.this;// em atividade

Umar Ata
fonte
Eu sei que não está relacionado à pergunta, mas como você usaria o byte[] videoBytes;? A maioria das respostas mostra apenas como usar InputStreamcom uma imagem.
KRK