Android: obtenir un URI de fichier à partir d'un URI de contenu?

133

Dans mon application, l'utilisateur doit sélectionner un fichier audio que l'application gère ensuite. Le problème est que pour que l'application fasse ce que je veux qu'elle fasse avec les fichiers audio, j'ai besoin que l'URI soit au format de fichier. Lorsque j'utilise le lecteur de musique natif d'Android pour rechercher le fichier audio dans l'application, l'URI est un URI de contenu, qui ressemble à ceci:

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

Cependant, en utilisant la populaire application de gestion de fichiers Astro, j'obtiens ce qui suit:

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

Ce dernier est beaucoup plus accessible pour moi, mais bien sûr, je veux que l'application ait une fonctionnalité avec le fichier audio que l'utilisateur choisit, quel que soit le programme qu'il utilise pour parcourir sa collection. Ma question est donc la suivante: y a-t-il un moyen de convertir l' content://URI de style en file://URI? Sinon, que me recommanderiez-vous pour résoudre ce problème? Voici le code qui appelle le sélecteur, pour référence:

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

Je fais ce qui suit avec l'URI du contenu:

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

Ensuite, faites des trucs FileInputStream avec ledit fichier.

JMRboosties
la source
1
Quels appels utilisez-vous qui n'aiment pas les URI de contenu?
Phil Lello
1
Il existe de nombreux Uris de contenu pour lesquels vous ne pouvez pas obtenir le chemin du fichier, car tous les Uris de contenu n'ont pas de chemins de fichiers. N'utilisez pas de chemins de fichiers.
Mooing Duck

Réponses:

153

Utilisez simplement getContentResolver().openInputStream(uri)pour obtenir un à InputStreampartir d'un URI.

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

Jason LeBrun
la source
12
Vérifiez le schéma de l'URI qui vous est renvoyé par l'activité de sélection. Si si uri.getScheme.equals ("content"), ouvrez-le avec un résolveur de contenu. Si uri.Scheme.equals ("fichier"), ouvrez-le en utilisant les méthodes de fichier normales. Dans tous les cas, vous vous retrouverez avec un InputStream que vous pourrez traiter en utilisant un code commun.
Jason LeBrun
16
En fait, je viens de relire la documentation pour getContentResolver (). OpenInputStream (), et cela fonctionne automatiquement pour les schémas de "contenu" ou de "fichier", vous n'avez donc pas besoin de vérifier le schéma ... si vous pouvez en toute sécurité Supposons que ce sera toujours content: // ou file: // alors openInputStream () fonctionnera toujours.
Jason LeBrun
57
Existe-t-il un moyen d'obtenir le fichier au lieu de InputStream (à partir du contenu: ...)?
AlikElzin-kilaka
4
@kilaka Vous pouvez obtenir le chemin du fichier mais c'est douloureux. Voir stackoverflow.com/a/20559418/294855
Danyal Aytekin
7
Cette réponse est insuffisante pour quelqu'un qui utilise une API à source fermée qui s'appuie sur des fichiers plutôt que sur FileStreams, mais qui souhaite utiliser le système d'exploitation pour permettre à l'utilisateur de sélectionner le fichier. La réponse à laquelle @DanyalAytekin faisait référence était exactement ce dont j'avais besoin (et en fait, j'ai pu couper beaucoup de graisse parce que je sais exactement avec quels types de fichiers je travaille).
monkey0506
45

C'est une vieille réponse avec une manière obsolète et piratée de surmonter certains problèmes spécifiques du résolveur de contenu. Prenez-le avec d'énormes grains de sel et utilisez l'API openInputStream appropriée si possible.

Vous pouvez utiliser le résolveur de contenu pour obtenir un file://chemin à partir de l' 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
la source
1
Merci, cela a parfaitement fonctionné. Je ne pouvais pas utiliser un InputStream comme le suggère la réponse acceptée.
ldam
4
Cela ne fonctionne que pour les fichiers locaux, par exemple cela ne fonctionne pas pour Google Drive
bleu pendant le
1
Parfois fonctionne, renvoie parfois le fichier: /// stockage / émulé / 0 / ... qui n'existe pas.
Reza Mohammadi
1
La colonne "_data" ( android.provider.MediaStore.Images.ImageColumns.DATA) est-elle toujours garantie d'exister si le schéma existe content://?
Edward Falk
2
C'est un anti-pattern majeur. Certains ContentProviders fournissent cette colonne, mais vous n'êtes pas assuré d'avoir un accès en lecture / écriture à Filelorsque vous essayez de contourner ContentResolver. Utilisez les méthodes ContentResolver pour opérer sur les content://uris, c'est l'approche officielle, encouragée par les ingénieurs de Google.
user1643723
9

Si vous avez un Uri de contenu avec, content://com.externalstorage...vous pouvez utiliser cette méthode pour obtenir le chemin absolu d'un dossier ou d'un fichier sur Android 19 ou supérieur .

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

Vous pouvez vérifier que chaque partie d'Uri utilise println. Les valeurs renvoyées pour ma carte SD et la mémoire principale de mon appareil sont répertoriées ci-dessous. Vous pouvez accéder et supprimer si le fichier est en mémoire mais je n'ai pas pu supprimer le fichier de la carte SD en utilisant cette méthode, ne lisez ou ouvrez l'image qu'en utilisant ce chemin absolu. Si vous avez trouvé une solution à supprimer en utilisant cette méthode, veuillez la partager. CARTE 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

MÉMOIRE PRINCIPALE

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

Si vous souhaitez obtenir Uri file:///après avoir utilisé le chemin

DocumentFile documentFile = DocumentFile.fromFile(new File(path));
documentFile.getUri() // will return a Uri with file Uri
Thrace
la source
je ne pense pas que ce soit la bonne façon de le faire dans une application. Malheureusement, je l'utilise dans un projet
rapide
@Bondax Oui, vous devriez travailler avec Content Uris à la place des chemins de fichiers ou des fichiers Uris. C'est ainsi que le cadre d'accès au stockage est introduit. Mais, si vous souhaitez obtenir le fichier uri, c'est la manière la plus correcte d'autres réponses puisque vous utilisez la classe DocumentsContract. Si vous consultez des exemples Google dans Github, vous verrez qu'ils utilisent également cette classe pour obtenir des sous-dossiers d'un dossier.
Thracian
Et la classe DocumentFile est également un nouvel ajout dans l'API 19 et comment vous utilisez les uris de SAF. La bonne façon est d'utiliser un chemin d'accès standard pour votre application et de demander à l'utilisateur de donner l'autorisation pour un dossier via l'interface utilisateur SAF, d'enregistrer la chaîne Uri dans les préférences partagées et, lorsqu'il est nécessaire, d'accéder au dossier avec des objets DocumentFile
Thracian
7

Essayer de gérer l'URI avec le schéma content: // en appelant ContentResolver.query()n'est pas une bonne solution. Sur HTC Desire exécutant 4.2.2, vous pourriez obtenir NULL comme résultat de la requête.

Pourquoi ne pas utiliser ContentResolver à la place? https://stackoverflow.com/a/29141800/3205334

Dark Raven
la source
Mais parfois, nous n'avons besoin que du chemin. Nous n'avons pas vraiment besoin de charger le fichier en mémoire.
Kimi Chiu
2
"Le chemin" est inutile si vous ne disposez pas des droits d'accès pour celui-ci. Par exemple, si une application vous donne un content://uri, correspondant au fichier dans son répertoire interne privé, vous ne pourrez pas utiliser cet uri avec les FileAPI dans les nouvelles versions d'Android. ContentResolver est conçu pour surmonter ce type de limitations de sécurité. Si vous avez obtenu l'URI de ContentResolver, vous pouvez vous attendre à ce qu'il fonctionne.
user1643723
5

Les réponses inspirées sont Jason LaBrun et Darth Raven . Essayer des approches déjà répondues m'a conduit à la solution ci-dessous qui peut principalement couvrir les cas nuls de curseur et la conversion de contenu: // en fichier: //

Pour convertir le fichier, lisez et écrivez le fichier à partir de l'URI gagné

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

Pour obtenir l'utilisation du nom de fichier, il couvrira la casse nulle du curseur

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

Il existe une bonne option pour convertir du type mime en extension de fichier

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

Curseur pour obtenir le nom du fichier

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
la source
Merci, je regarde ça depuis une semaine. Je n'aime pas avoir à copier un fichier pour cela, mais cela fonctionne.
justdan0227
3

Eh bien je suis un peu en retard pour répondre, mais mon code est testé

vérifier le schéma de 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);
        }

Dans la réponse ci-dessus, j'ai converti l'URI de la vidéo en tableau d'octets, mais ce n'est pas lié à la question, je viens de copier mon code complet pour montrer l'utilisation de FileInputStreamet InputStreamcomme les deux fonctionnent de la même manière dans mon code.

J'ai utilisé le contexte variable qui est getActivity () dans mon Fragment et dans Activity, il s'agit simplement de ActivityName.

context=getActivity(); // en fragment

context=ActivityName.this;// en activité

Umar Ata
la source
Je sais que ce n'est pas lié à la question, mais comment utiliseriez-vous alors le byte[] videoBytes;? La plupart des réponses montrent uniquement comment utiliser InputStreamavec une image.
KRK