Android Gallery su Android 4.4 (KitKat) restituisce URI diversi per Intent.ACTION_GET_CONTENT


214

Prima di KitKat (o prima della nuova Galleria) Intent.ACTION_GET_CONTENTrestituiva un URI come questo

tenore: // media / / images / supporto esterno / 3951.

L'utilizzo di ContentResolvere query per MediaStore.Images.Media.DATArestituito l'URL del file.

In KitKat, tuttavia, la Galleria restituisce un URI (tramite "Ultimo") come questo:

contenuti: //com.android.providers.media.documents/document/image: 3951

Come posso gestirlo?


21
Al di fuori del bracciale, troverei modi di usare il contenuto che non richiede l'accesso diretto al file. Ad esempio, Uridovrebbe essere apribile come flusso tramite ContentResolver. Sono stato a lungo nervoso per le app che presuppongono che content:// Uriun file che rappresenta un file possa sempre essere convertito in un file File.
CommonsWare

1
@ CommonsWare, se voglio salvare un percorso immagine in sqlite db in modo da poterlo aprire in un secondo momento, dovrei salvare l'URI o il percorso file assoluto?
Snake,

2
@CommonsWare Sono d'accordo con il tuo nervosismo. :-) Tuttavia, devo essere in grado di passare un nome file (per un'immagine) al codice nativo. Una soluzione è quella di copiare i dati ottenuti usando un InputStreamsu ContentResolverin un posto prestabilito in modo che abbia un nome file noto. Tuttavia, questo mi sembra dispendioso. Altri suggerimenti?
darrenp,

2
@darrenp: Ummm ..., riscrivi il codice nativo per lavorare con un InputStreamover JNI? Sfortunatamente non ci sono così tante opzioni per te.
Commons War

1
È utile saperlo. Grazie per la risposta. Da allora ho scoperto che ora stiamo trasmettendo l'immagine al C ++ in memoria anziché tramite un file, in modo che ora possiamo usare un InputStreamfile anziché un file (il che è fantastico). Solo la lettura dei tag EXIF ​​è leggermente complicata e richiede la libreria di Drew Noakes . Molte grazie per i tuoi commenti.
darrenp,

Risposte:


108

Prova questo:

if (Build.VERSION.SDK_INT <19){
    Intent intent = new Intent(); 
    intent.setType("image/jpeg");
    intent.setAction(Intent.ACTION_GET_CONTENT);
    startActivityForResult(Intent.createChooser(intent, getResources().getString(R.string.select_picture)),GALLERY_INTENT_CALLED);
} else {
    Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
    intent.addCategory(Intent.CATEGORY_OPENABLE);
    intent.setType("image/jpeg");
    startActivityForResult(intent, GALLERY_KITKAT_INTENT_CALLED);
}

@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if (resultCode != Activity.RESULT_OK) return;
    if (null == data) return;
    Uri originalUri = null;
    if (requestCode == GALLERY_INTENT_CALLED) {
        originalUri = data.getData();
    } else if (requestCode == GALLERY_KITKAT_INTENT_CALLED) {
        originalUri = data.getData();
        final int takeFlags = data.getFlags()
                & (Intent.FLAG_GRANT_READ_URI_PERMISSION
                | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
        // Check for the freshest data.
        getContentResolver().takePersistableUriPermission(originalUri, takeFlags);
    }

    loadSomeStreamAsynkTask(originalUri);

}

Probabilmente bisogno

@SuppressLint ( "NewApi")

per

takePersistableUriPermission


1
Ti dispiacerebbe elaborare cosa sta facendo il codice KitKat? Ciò richiede qualche nuova autorizzazione? Il codice pre KitKat funziona anche per me su KitKat. Quindi perché dovrei scegliere di utilizzare un codice diverso per KitKat? Grazie.
Michael Greifeneder,

67
sembra che non possiamo ottenere il percorso dai nuovi sdks uri. Inoltre è un peccato che Google abbia apportato questo tipo di modifica senza una corretta documentazione e annuncio.
user65721

1
Potresti spiegare come ottenere l'URL del file? Vorrei ottenere il vero percorso in sdcard. Ad esempio, se si tratta di un'immagine, vorrei ottenere questo percorso /storage/sdcard0/DCIM/Camera/IMG_20131118_153817_119.jpg invece del documento Uri.
Álvaro,

4
Sulla base dei documenti di KitKat ( developer.android.com/about/versions/… ), questo potrebbe non essere quello di cui ha bisogno l'OP a meno che non intenda utilizzare / modificare documenti di proprietà delle altre applicazioni. Se l'OP vuole una copia o fare le cose in modo coerente con le versioni precedenti, la risposta di @voytez sarebbe più appropriata.
Colin M.,

8
Questo non funziona per me. Ottengo la seguente eccezione (disponibile 4.4.2): E / AndroidRuntime (29204): Causato da: java.lang.SecurityException: Flag richiesti 0x1, ma sono consentiti solo 0x0
Russell Stewart

177

Ciò non richiede autorizzazioni speciali e funziona con Storage Access Framework, nonché con il ContentProvidermodello non ufficiale (percorso del file nel _datacampo).

/**
 * Get a file path from a Uri. This will get the the path for Storage Access
 * Framework Documents, as well as the _data field for the MediaStore and
 * other file-based ContentProviders.
 *
 * @param context The context.
 * @param uri The Uri to query.
 * @author paulburke
 */
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)) {
        // ExternalStorageProvider
        if (isExternalStorageDocument(uri)) {
            final String docId = DocumentsContract.getDocumentId(uri);
            final String[] split = docId.split(":");
            final String type = split[0];

            if ("primary".equalsIgnoreCase(type)) {
                return Environment.getExternalStorageDirectory() + "/" + split[1];
            }

            // TODO handle non-primary volumes
        }
        // DownloadsProvider
        else if (isDownloadsDocument(uri)) {

            final String id = DocumentsContract.getDocumentId(uri);
            final Uri contentUri = ContentUris.withAppendedId(
                    Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));

            return getDataColumn(context, contentUri, null, null);
        }
        // MediaProvider
        else if (isMediaDocument(uri)) {
            final String docId = DocumentsContract.getDocumentId(uri);
            final String[] split = docId.split(":");
            final String type = split[0];

            Uri contentUri = null;
            if ("image".equals(type)) {
                contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
            } else if ("video".equals(type)) {
                contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
            } else if ("audio".equals(type)) {
                contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
            }

            final String selection = "_id=?";
            final String[] selectionArgs = new String[] {
                    split[1]
            };

            return getDataColumn(context, contentUri, selection, selectionArgs);
        }
    }
    // MediaStore (and general)
    else if ("content".equalsIgnoreCase(uri.getScheme())) {

        // Return the remote address
        if (isGooglePhotosUri(uri))
            return uri.getLastPathSegment();

        return getDataColumn(context, uri, null, null);
    }
    // File
    else if ("file".equalsIgnoreCase(uri.getScheme())) {
        return uri.getPath();
    }

    return null;
}

/**
 * Get the value of the data column for this Uri. This is useful for
 * MediaStore Uris, and other file-based ContentProviders.
 *
 * @param context The context.
 * @param uri The Uri to query.
 * @param selection (Optional) Filter used in the query.
 * @param selectionArgs (Optional) Selection arguments used in the query.
 * @return The value of the _data column, which is typically a file path.
 */
public static String getDataColumn(Context context, Uri uri, String selection,
        String[] selectionArgs) {

    Cursor cursor = null;
    final String column = "_data";
    final String[] projection = {
            column
    };

    try {
        cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs,
                null);
        if (cursor != null && cursor.moveToFirst()) {
            final int index = cursor.getColumnIndexOrThrow(column);
            return cursor.getString(index);
        }
    } finally {
        if (cursor != null)
            cursor.close();
    }
    return null;
}


/**
 * @param uri The Uri to check.
 * @return Whether the Uri authority is ExternalStorageProvider.
 */
public static boolean isExternalStorageDocument(Uri uri) {
    return "com.android.externalstorage.documents".equals(uri.getAuthority());
}

/**
 * @param uri The Uri to check.
 * @return Whether the Uri authority is DownloadsProvider.
 */
public static boolean isDownloadsDocument(Uri uri) {
    return "com.android.providers.downloads.documents".equals(uri.getAuthority());
}

/**
 * @param uri The Uri to check.
 * @return Whether the Uri authority is MediaProvider.
 */
public static boolean isMediaDocument(Uri uri) {
    return "com.android.providers.media.documents".equals(uri.getAuthority());
}

/**
 * @param uri The Uri to check.
 * @return Whether the Uri authority is Google Photos.
 */
public static boolean isGooglePhotosUri(Uri uri) {
    return "com.google.android.apps.photos.content".equals(uri.getAuthority());
}

Consulta una versione aggiornata di questo metodo qui .


2
Funzionava in modo fantastico su un'interfaccia utente Nexus 5 Documents 4.4 e su alcuni altri dispositivi KitKat di pref che utilizzano le app standard della galleria, grazie Paul!
Josh,

1
Grazie per questo, mi ci sono voluti anni per arrivare così lontano con SDK 19 !! Il mio problema è che il mio dispositivo utilizza Google Drive come browser di file. Se il file si trova sul percorso dell'immagine del dispositivo va bene, ma se il file si trova sull'unità non si apre. Forse ho solo bisogno di esaminare come aprire le immagini da Google Drive. La cosa è che la mia app è stata scritta per utilizzare il percorso del file e ottenere l'immagine usando il campionamento ...
RuAware,

2
@RuAware Quando si seleziona un file Drive, viene restituito Authority: com.google.android.apps.docs.storagee Segments: [document, acc=1;doc=667]. Non sono sicuro, ma suppongo che il docvalore sia l' UriID con cui è possibile eseguire una query. Probabilmente avrai bisogno delle autorizzazioni per essere impostato come dettagliato in "Autorizza la tua app su Android" qui: developers.google.com/drive/integrate-android-ui . Si prega di aggiornare qui se lo capisci.
Paul Burke,

30
questo è assolutamente orribile! non dovresti continuare a propagare il codice che "tradisce" in questo modo. supporta solo le app di origine per le quali conosci il modello e l'intero punto del modello del provider di documenti è supportare fonti arbitrarie
j__m

2
Il _datanon avrebbe funzionato quando ContentProvider non lo supporta. Si consiglia di seguire le istruzioni di @CommonsWare e di non utilizzare più il percorso completo del file, poiché potrebbe essere un file nel cloud Dropbox anziché un file reale.
soshial,

67

Ho avuto lo stesso problema, ho provato la soluzione sopra ma sebbene funzionasse in generale, per qualche motivo stavo ottenendo il rifiuto dell'autorizzazione sul fornitore di contenuti Uri per alcune immagini sebbene avessi l' android.permission.MANAGE_DOCUMENTSaggiunta dell'autorizzazione corretta.

Comunque ho trovato un'altra soluzione che è quella di forzare l'apertura della galleria di immagini anziché la visualizzazione dei documenti KITKAT con:

// KITKAT

i = new Intent(Intent.ACTION_PICK,android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
    startActivityForResult(i, CHOOSE_IMAGE_REQUEST);

e quindi caricare l'immagine:

Uri selectedImageURI = data.getData();
input = c.getContentResolver().openInputStream(selectedImageURI);
                BitmapFactory.decodeStream(input , null, opts);

MODIFICARE

ACTION_OPEN_DOCUMENT potrebbe richiedere il persistere di flag di autorizzazioni ecc. e in genere comporta spesso eccezioni di sicurezza ...

Un'altra soluzione è quella di utilizzare il ACTION_GET_CONTENTcombinato con il c.getContentResolver().openInputStream(selectedImageURI)quale funzionerà sia su pre-KK che su KK. Kitkat utilizzerà quindi la visualizzazione di nuovi documenti e questa soluzione funzionerà con tutte le app come Foto, Galleria, Esplora file, Dropbox, Google Drive ecc ...) ma ricorda che quando usi questa soluzione devi creare un'immagine nella tua onActivityResult()e archiviarla su Scheda SD per esempio. Ricreare questa immagine dall'URI salvato al prossimo avvio dell'app genererebbe Eccezione di sicurezza sul risolutore di contenuti anche quando aggiungi flag di autorizzazione come descritto nei documenti dell'API di Google (questo è ciò che è accaduto quando ho fatto alcuni test)

Inoltre, le Linee guida per le API per sviluppatori Android suggeriscono:

ACTION_OPEN_DOCUMENT non è destinato a sostituire ACTION_GET_CONTENT. Quello che dovresti usare dipende dalle esigenze della tua app:

Utilizza ACTION_GET_CONTENT se desideri che l'app legga / importi semplicemente i dati. Con questo approccio, l'app importa una copia dei dati, ad esempio un file di immagine.

Utilizza ACTION_OPEN_DOCUMENT se desideri che la tua app abbia accesso persistente a lungo termine ai documenti di proprietà di un fornitore di documenti. Un esempio potrebbe essere un'app di fotoritocco che consente agli utenti di modificare le immagini archiviate in un fornitore di documenti.


1
Questa risposta conteneva le informazioni giuste per i miei scopi. L'utilizzo condizionale di ACTION_PICK e EXTERNAL_CONTENT_URI su KitKat ha fornito la stessa capacità di ottenere metadati sulle immagini nella galleria tramite ContentResolver come è possibile su versioni precedenti utilizzando semplicemente ACTION_GET_CONTENT.
Colin M.,

@voytez, questo URI può essere restituito tramite il tuo messaggio convertito nel percorso reale completo dell'immagine?
Snake,

Credo di sì, dovrebbe funzionare proprio come prima di KitKat poiché questo codice forza l'apertura della galleria di immagini anziché la visualizzazione dei documenti di KK. Ma se hai intenzione di usarlo per creare un'immagine, questa soluzione è migliore in quanto la conversione in percorso reale è un ulteriore passaggio inutile.
voytez,

4
Ha funzionato anche per me, invece di Intent.ACTION_GET_CONTENT. Ad ogni modo ho mantenuto il Intent.createChooser()wrapper sul nuovo Intent, per consentire all'utente di scegliere l'app per la navigazione e ha funzionato come previsto. Qualcuno può vedere gli svantaggi di questa soluzione?
lorenzo-s,

1
Per chiunque si stia chiedendo la citazione proviene da developer.android.com/guide/topics/providers/…
snark

38

Proprio come ha detto Commonsware, non dovresti presumere che il flusso che ottieni ContentResolversia convertibile in file.

Quello che dovresti davvero fare è aprire il InputStreamda ContentProvider, quindi crearne una Bitmap. E funziona anche su versioni 4.4 e precedenti, senza bisogno di riflessioni.

    //cxt -> current context

    InputStream input;
    Bitmap bmp;
    try {
        input = cxt.getContentResolver().openInputStream(fileUri);
        bmp = BitmapFactory.decodeStream(input);
    } catch (FileNotFoundException e1) {

    }

Ovviamente se gestisci immagini di grandi dimensioni, dovresti caricarle con le appropriate inSampleSize: http://developer.android.com/training/displaying-bitmaps/load-bitmap.html . Ma questo è un altro argomento.


Questo non funziona per me con un Nexus 4 con Kitkat ma con un Galaxy S3 con esecuzione 4.1.2
Dan2552

@ Dan2552 quale parte non funziona? Hai qualche eccezione?
Michał K,

Ho scoperto che stavo usando la chiamata di intenti sbagliata alla galleria. Ne stavo usando uno adatto a qualsiasi tipo di documento, ma con un filtro di estensione file.
Dan2552,

2
Che risposta meravigliosamente semplice, grazie! Per altri che seguono questa risposta "cxt" si riferisce al contesto attuale e di solito sarà "questo".
Thomas

1
Questo probabilmente significa che il file non è proprio lì. L'URI sembra OK.
Michał K,

33

Credo che le risposte già pubblicate dovrebbero far andare le persone nella giusta direzione. Tuttavia, ecco cosa ho fatto che ha senso per il codice legacy che stavo aggiornando. Il codice legacy utilizzava l'URI dalla galleria per modificare e quindi salvare le immagini.

Prima di 4.4 (e google drive), gli URI sarebbero stati così: content: // media / external / images / media / 41

Come indicato nella domanda, hanno più spesso questo aspetto: content: //com.android.providers.media.documents/document/image: 3951

Dato che avevo bisogno della possibilità di salvare le immagini e di non disturbare il codice già esistente, ho appena copiato l'URI dalla galleria nella cartella dei dati dell'app. Quindi ha originato un nuovo URI dal file di immagine salvato nella cartella dei dati.

Ecco l'idea:

Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.setType("image/*");
startActivityForResult(intent), CHOOSE_IMAGE_REQUEST);

public void onActivityResult(int requestCode, int resultCode, Intent data) {

    super.onActivityResult(requestCode, resultCode, data);

    File tempFile = new File(this.getFilesDir().getAbsolutePath(), "temp_image");

    //Copy URI contents into temporary file.
    try {
        tempFile.createNewFile();
        copyAndClose(this.getContentResolver().openInputStream(data.getData()),new FileOutputStream(tempFile));
    }
    catch (IOException e) {
        //Log Error
    }

    //Now fetch the new URI
    Uri newUri = Uri.fromFile(tempFile);

    /* Use new URI object just like you used to */
 }

Nota: copyAndClose () esegue semplicemente l'I / O dei file per copiare InputStream in FileOutputStream. Il codice non è pubblicato.


abbastanza intelligente. anch'io avevo bisogno del file reale uri
GreaterKing

sei il mio eroe, questo è esattamente quello di cui avevo bisogno! funziona benissimo anche per i file di Google Drive
mishkin

Pensa fuori dagli schemi, giusto? : D Questo codice funziona esattamente come mi aspettavo.
WeirdElfB0y

2
pubblica il codice per copyAndClose, la risposta non è completa
Bartek Pacia

24

Volevo solo dire che questa risposta è geniale e la sto usando da molto tempo senza problemi. Ma qualche tempo fa mi sono imbattuto in un problema con il quale DownloadsProvider restituisce gli URI in formato content://com.android.providers.downloads.documents/document/raw%3A%2Fstorage%2Femulated%2F0%2FDownload%2Fdoc.pdfe quindi l'app viene bloccata NumberFormatExceptionperché è impossibile analizzare i suoi segmenti uri più a lungo. Ma il raw:segmento contiene uri diretti che possono essere utilizzati per recuperare un file di riferimento. Quindi l'ho risolto sostituendo il isDownloadsDocument(uri) ifcontenuto con il seguente:

final String id = DocumentsContract.getDocumentId(uri);
if (!TextUtils.isEmpty(id)) {
if (id.startsWith("raw:")) {
    return id.replaceFirst("raw:", "");
}
try {
    final Uri contentUri = ContentUris.withAppendedId(
            Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));
    return getDataColumn(context, contentUri, null, null);
} catch (NumberFormatException e) {
    Log.e("FileUtils", "Downloads provider returned unexpected uri " + uri.toString(), e);
    return null;
}
}

2
Funziona perfettamente! Grazie
mikemike396,

8

Ho combinato più risposte in un'unica soluzione funzionante che risulta con il percorso del file

Il tipo MIME è irrilevante ai fini dell'esempio.

            Intent intent;
            if(Build.VERSION.SDK_INT >= 19){
                intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
                intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, false);
                intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
            }else{
                intent = new Intent(Intent.ACTION_GET_CONTENT);
            }
            intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
            intent.setType("application/octet-stream");
            if(isAdded()){
                startActivityForResult(intent, RESULT_CODE);
            }

Risultato della gestione

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
    if( requestCode == RESULT_CODE && resultCode == Activity.RESULT_OK) {
        Uri uri = data.getData();
        if (uri != null && !uri.toString().isEmpty()) {
            if(Build.VERSION.SDK_INT >= 19){
                final int takeFlags = data.getFlags() & Intent.FLAG_GRANT_READ_URI_PERMISSION;
                //noinspection ResourceType
                getActivity().getContentResolver()
                        .takePersistableUriPermission(uri, takeFlags);
            }
            String filePath = FilePickUtils.getSmartFilePath(getActivity(), uri);
            // do what you need with it...
        }
    }
}

FilePickUtils

import android.annotation.SuppressLint;
import android.content.ContentUris;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.provider.DocumentsContract;
import android.provider.MediaStore;

public class FilePickUtils {
    private static String getPathDeprecated(Context ctx, Uri uri) {
        if( uri == null ) {
            return null;
        }
        String[] projection = { MediaStore.Images.Media.DATA };
        Cursor cursor = ctx.getContentResolver().query(uri, projection, null, null, null);
        if( cursor != null ){
            int column_index = cursor
                    .getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
            cursor.moveToFirst();
            return cursor.getString(column_index);
        }
        return uri.getPath();
    }

    public static String getSmartFilePath(Context ctx, Uri uri){

        if (Build.VERSION.SDK_INT < 19) {
            return getPathDeprecated(ctx, uri);
        }
        return  FilePickUtils.getPath(ctx, uri);
    }

    @SuppressLint("NewApi")
    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)) {
            // ExternalStorageProvider
            if (isExternalStorageDocument(uri)) {
                final String docId = DocumentsContract.getDocumentId(uri);
                final String[] split = docId.split(":");
                final String type = split[0];

                if ("primary".equalsIgnoreCase(type)) {
                    return Environment.getExternalStorageDirectory() + "/" + split[1];
                }

                // TODO handle non-primary volumes
            }
            // DownloadsProvider
            else if (isDownloadsDocument(uri)) {
                final String id = DocumentsContract.getDocumentId(uri);
                final Uri contentUri = ContentUris.withAppendedId(
                        Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));

                return getDataColumn(context, contentUri, null, null);
            }
            // MediaProvider
            else if (isMediaDocument(uri)) {
                final String docId = DocumentsContract.getDocumentId(uri);
                final String[] split = docId.split(":");
                final String type = split[0];

                Uri contentUri = null;
                if ("image".equals(type)) {
                    contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
                } else if ("video".equals(type)) {
                    contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
                } else if ("audio".equals(type)) {
                    contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
                }

                final String selection = "_id=?";
                final String[] selectionArgs = new String[] {
                        split[1]
                };

                return getDataColumn(context, contentUri, selection, selectionArgs);
            }
        }
        // MediaStore (and general)
        else if ("content".equalsIgnoreCase(uri.getScheme())) {
            return getDataColumn(context, uri, null, null);
        }
        // File
        else if ("file".equalsIgnoreCase(uri.getScheme())) {
            return uri.getPath();
        }

        return null;
    }

    /**
     * Get the value of the data column for this Uri. This is useful for
     * MediaStore Uris, and other file-based ContentProviders.
     *
     * @param context The context.
     * @param uri The Uri to query.
     * @param selection (Optional) Filter used in the query.
     * @param selectionArgs (Optional) Selection arguments used in the query.
     * @return The value of the _data column, which is typically a file path.
     */
    public static String getDataColumn(Context context, Uri uri, String selection,
                                       String[] selectionArgs) {
        Cursor cursor = null;
        final String column = "_data";
        final String[] projection = {
                column
        };

        try {
            cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs,
                    null);
            if (cursor != null && cursor.moveToFirst()) {
                final int column_index = cursor.getColumnIndexOrThrow(column);
                return cursor.getString(column_index);
            }
        } finally {
            if (cursor != null)
                cursor.close();
        }
        return null;
    }


    /**
     * @param uri The Uri to check.
     * @return Whether the Uri authority is ExternalStorageProvider.
     */
    public static boolean isExternalStorageDocument(Uri uri) {
        return "com.android.externalstorage.documents".equals(uri.getAuthority());
    }

    /**
     * @param uri The Uri to check.
     * @return Whether the Uri authority is DownloadsProvider.
     */
    public static boolean isDownloadsDocument(Uri uri) {
        return "com.android.providers.downloads.documents".equals(uri.getAuthority());
    }

    /**
     * @param uri The Uri to check.
     * @return Whether the Uri authority is MediaProvider.
     */
    public static boolean isMediaDocument(Uri uri) {
        return "com.android.providers.media.documents".equals(uri.getAuthority());
    }

}

stavo affrontando il problema .... uri.getPath () stava restituendo uri con / esterno e non stava ritornando il percorso. ho aggiunto questo altro caso se ("content" .equalsIgnoreCase (uri.getScheme ())) si blocca e ora funziona bene .. puoi spiegare cosa fa
FaisalAhmed,

filePath sta diventando nullo. In uri: contenuto: //com.android.externalstorage.documents/document/799B-1419%3AScreenshot%2FScreenshot_20181117_162826.png
Bhavesh Moradiya

7

Domanda

Come ottenere un percorso file effettivo da un URI

Risposta

Per quanto ne so, non abbiamo bisogno di ottenere il percorso del file da un URI perché nella maggior parte dei casi possiamo usare direttamente l'URI per svolgere il nostro lavoro (come 1. ottenere bitmap 2. Invio di un file al server, ecc. .)

1. Invio al server

Possiamo inviare direttamente il file al server usando solo l'URI.

Usando l'URI possiamo ottenere InputStream, che possiamo inviare direttamente al server usando MultiPartEntity.

Esempio

/**
 * Used to form Multi Entity for a URI (URI pointing to some file, which we got from other application).
 *
 * @param uri     URI.
 * @param context Context.
 * @return Multi Part Entity.
 */
public MultipartEntity formMultiPartEntityForUri(final Uri uri, final Context context) {
    MultipartEntity multipartEntity = new MultipartEntity(HttpMultipartMode.BROWSER_COMPATIBLE, null, Charset.forName("UTF-8"));
    try {
        InputStream inputStream = mContext.getContentResolver().openInputStream(uri);
        if (inputStream != null) {
            ContentBody contentBody = new InputStreamBody(inputStream, getFileNameFromUri(uri, context));
            multipartEntity.addPart("[YOUR_KEY]", contentBody);
        }
    }
    catch (Exception exp) {
        Log.e("TAG", exp.getMessage());
    }
    return multipartEntity;
}

/**
 * Used to get a file name from a URI.
 *
 * @param uri     URI.
 * @param context Context.
 * @return File name from URI.
 */
public String getFileNameFromUri(final Uri uri, final Context context) {

    String fileName = null;
    if (uri != null) {
        // Get file name.
        // File Scheme.
        if (ContentResolver.SCHEME_FILE.equals(uri.getScheme())) {
            File file = new File(uri.getPath());
            fileName = file.getName();
        }
        // Content Scheme.
        else if (ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) {
            Cursor returnCursor =
                    context.getContentResolver().query(uri, null, null, null, null);
            if (returnCursor != null && returnCursor.moveToFirst()) {
                int nameIndex = returnCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
                fileName = returnCursor.getString(nameIndex);
                returnCursor.close();
            }
        }
    }
    return fileName;
}

2. Ottenere una BitMap da un URI

Se l'URI punta all'immagine, avremo bitmap, altrimenti null:

/**
 * Used to create bitmap for the given URI.
 * <p>
 * 1. Convert the given URI to bitmap.
 * 2. Calculate ratio (depending on bitmap size) on how much we need to subSample the original bitmap.
 * 3. Create bitmap bitmap depending on the ration from URI.
 * 4. Reference - http://stackoverflow.com/questions/3879992/how-to-get-bitmap-from-an-uri
 *
 * @param context       Context.
 * @param uri           URI to the file.
 * @param bitmapSize Bitmap size required in PX.
 * @return Bitmap bitmap created for the given URI.
 * @throws IOException
 */
public static Bitmap createBitmapFromUri(final Context context, Uri uri, final int bitmapSize) throws IOException {

    // 1. Convert the given URI to bitmap.
    InputStream input = context.getContentResolver().openInputStream(uri);
    BitmapFactory.Options onlyBoundsOptions = new BitmapFactory.Options();
    onlyBoundsOptions.inJustDecodeBounds = true;
    onlyBoundsOptions.inDither = true;//optional
    onlyBoundsOptions.inPreferredConfig = Bitmap.Config.ARGB_8888;//optional
    BitmapFactory.decodeStream(input, null, onlyBoundsOptions);
    input.close();
    if ((onlyBoundsOptions.outWidth == -1) || (onlyBoundsOptions.outHeight == -1)) {
        return null;
    }

    // 2. Calculate ratio.
    int originalSize = (onlyBoundsOptions.outHeight > onlyBoundsOptions.outWidth) ? onlyBoundsOptions.outHeight : onlyBoundsOptions.outWidth;
    double ratio = (originalSize > bitmapSize) ? (originalSize / bitmapSize) : 1.0;

    // 3. Create bitmap.
    BitmapFactory.Options bitmapOptions = new BitmapFactory.Options();
    bitmapOptions.inSampleSize = getPowerOfTwoForSampleRatio(ratio);
    bitmapOptions.inDither = true;//optional
    bitmapOptions.inPreferredConfig = Bitmap.Config.ARGB_8888;//optional
    input = context.getContentResolver().openInputStream(uri);
    Bitmap bitmap = BitmapFactory.decodeStream(input, null, bitmapOptions);
    input.close();

    return bitmap;
}

/**
 * For Bitmap option inSampleSize - We need to give value in power of two.
 *
 * @param ratio Ratio to be rounded of to power of two.
 * @return Ratio rounded of to nearest power of two.
 */
private static int getPowerOfTwoForSampleRatio(final double ratio) {
    int k = Integer.highestOneBit((int) Math.floor(ratio));
    if (k == 0) return 1;
    else return k;
}

Commenti

  1. Android non fornisce alcun metodo per ottenere il percorso del file da un URI e nella maggior parte delle risposte di cui sopra abbiamo codificato alcune costanti, il che potrebbe interrompere la funzione di rilascio (scusate, potrei sbagliarmi).
  2. Prima di andare direttamente a una soluzione per ottenere il percorso del file da un URI, prova a risolvere il tuo caso d'uso con un URI e i metodi predefiniti di Android.

Riferimento

  1. https://developer.android.com/guide/topics/providers/content-provider-basics.html
  2. https://developer.android.com/reference/android/content/ContentResolver.html
  3. https://hc.apache.org/httpcomponents-client-ga/httpmime/apidocs/org/apache/http/entity/mime/content/InputStreamBody.html

Grazie. L'uso di Uri e ContentResolver in questo modo ha semplificato notevolmente la mia applicazione quando si trattava di file.
jacksonakj,


3

Per coloro che stanno ancora utilizzando il codice di @Paul Burke con Android SDK versione 23 e successive, se il progetto ha riscontrato l'errore che dice che ti manca EXTERNAL_PERMISSION e sei sicuro di aver già aggiunto l'autorizzazione utente nel tuo file AndroidManifest.xml. Questo perché in Android API 23 o versione successiva è possibile che Google renda necessario garantire nuovamente l'autorizzazione mentre si esegue l'azione per accedere al file in fase di esecuzione.

Ciò significa: se la tua versione dell'SDK è 23 o successiva, ti viene richiesta l'autorizzazione READ & WRITE mentre stai selezionando il file di immagine e vuoi conoscerne l'URI.

E seguito è il mio codice, oltre alla soluzione di Paul Burke. Aggiungo questo codice e il mio progetto inizia a funzionare bene.

private static final int REQUEST_EXTERNAL_STORAGE = 1;
private static final String[] PERMISSINOS_STORAGE = {
    Manifest.permission.READ_EXTERNAL_STORAGE,
    Manifest.permission.WRITE_EXTERNAL_STORAGE
};

public static void verifyStoragePermissions(Activity activity) {
    int permission = ActivityCompat.checkSelfPermission(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE);

    if (permission != PackageManager.PERMISSION_GRANTED) {
        ActivityCompat.requestPermissions(
                activity,
                PERMISSINOS_STORAGE,
                REQUEST_EXTERNAL_STORAGE
        );
    }
}

E nella tua attività e frammento in cui stai chiedendo l'URI:

private void pickPhotoFromGallery() {

    CompatUtils.verifyStoragePermissions(this);
    Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
    intent.setType("image/*");
    // startActivityForResult(intent, REQUEST_PHOTO_LIBRARY);
    startActivityForResult(Intent.createChooser(intent, "选择照片"),
            REQUEST_PHOTO_LIBRARY);
}

Nel mio caso, CompatUtils.java è il punto in cui definisco il metodo confirmStoragePermissions (come tipo statico in modo da poterlo chiamare all'interno di altre attività).

Inoltre, dovrebbe avere più senso se si effettua uno stato if per verificare se la versione corrente dell'SDK è precedente a 23 o meno prima di chiamare il metodo confirmStoragePermissions.


2

Questo è ciò che faccio:

Uri selectedImageURI = data.getData();    imageFile = new File(getRealPathFromURI(selectedImageURI)); 

private String getRealPathFromURI(Uri contentURI) {
  Cursor cursor = getContentResolver().query(contentURI, null, null, null, null);
  if (cursor == null) { // Source is Dropbox or other similar local file path
      return contentURI.getPath();
      } else { 
      cursor.moveToFirst(); 
      int idx = cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATA); 
      return cursor.getString(idx); 
  }
}

NOTA: il managedQuery()metodo è obsoleto, quindi non lo sto usando.

Questa risposta è da m3n0R sulla domanda Android ottiene il vero percorso da Uri.getPath () e non rivendico credito. Ho solo pensato che le persone che non hanno ancora risolto questo problema potrebbero usarlo.


2
Questa non è una risposta alla nuova app Galleria (rigorosamente "Provider di documenti multimediali") su KitKat.
nagoya0,

L'app che l'interrogante chiama "Galleria" è probabilmente un nuovo selettore di file su kitkat. A proposito: addictivetips.com/android/…
nagoya0

Faccio simile e ho IndexOutOfBound su Nexus 5X, Android 6 su questa linea:cursor.getString(idx);
Osify,

1

Cerca di evitare di utilizzare il metodo takePersistableUriPermission perché ha sollevato un'eccezione di runtime per me. / ** * Seleziona dalla galleria. * /

public void selectFromGallery() {
    if (Build.VERSION.SDK_INT < AppConstants.KITKAT_API_VERSION) {

        Intent intent = new Intent(); 
        intent.setType("image/*");
        intent.setAction(Intent.ACTION_GET_CONTENT);
        ((Activity)mCalledContext).startActivityForResult(intent,AppConstants.GALLERY_INTENT_CALLED);

    } else {

        Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
        intent.setType("image/*");
        ((Activity)mCalledContext).startActivityForResult(intent, AppConstants.GALLERY_AFTER_KITKAT_INTENT_CALLED);
    }
}

OnActivity per il risultato per gestire i dati dell'immagine:

@Override protetto void onActivityResult (int requestCode, int resultCode, Intent data) {

    //gallery intent result handling before kit-kat version
    if(requestCode==AppConstants.GALLERY_INTENT_CALLED 
            && resultCode == RESULT_OK) {

        Uri selectedImage = data.getData();
        String[] filePathColumn = {MediaStore.Images.Media.DATA};
        Cursor cursor = getContentResolver().query(selectedImage,filePathColumn, null, null, null);
        cursor.moveToFirst();
        int columnIndex = cursor.getColumnIndex(filePathColumn[0]);
        String filePath = cursor.getString(columnIndex);
        cursor.close();
        photoFile = new File(filePath);
        mImgCropping.startCropImage(photoFile,AppConstants.REQUEST_IMAGE_CROP);

    }
    //gallery intent result handling after kit-kat version
    else if (requestCode == AppConstants.GALLERY_AFTER_KITKAT_INTENT_CALLED 
            && resultCode == RESULT_OK) {

        Uri selectedImage = data.getData();
        InputStream input = null;
        OutputStream output = null;

        try {
            //converting the input stream into file to crop the 
            //selected image from sd-card.
            input = getApplicationContext().getContentResolver().openInputStream(selectedImage);
            try {
                photoFile = mImgCropping.createImageFile();
            } catch (IOException e) {
                e.printStackTrace();
            }catch(Exception e) {
                e.printStackTrace();
            }
            output = new FileOutputStream(photoFile);

            int read = 0;
            byte[] bytes = new byte[1024];

            while ((read = input.read(bytes)) != -1) {
                try {
                    output.write(bytes, 0, read);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }


    }

Dove stai visualizzando l'immagine nel secondo caso?
Quantum_VC

scusa se non sono riuscito ad aggiungere questa riga di codice altrimenti se mImgCropping.startCropImage (photoFile, AppConstants.REQUEST_IMAGE_CROP); di nuovo ho bisogno di chiamare la funzione di immagecropping secondo il mio bisogno del progetto
saranya,

Che tipo di file è photoFile e mImgCropping?
Philip BH,

1

Se qualcuno è interessato, ho realizzato una versione Kotlin funzionante per ACTION_GET_CONTENT:

var path: String = uri.path // uri = any content Uri
val databaseUri: Uri
val selection: String?
val selectionArgs: Array<String>?
if (path.contains("/document/image:")) { // files selected from "Documents"
    databaseUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI
    selection = "_id=?"
    selectionArgs = arrayOf(DocumentsContract.getDocumentId(uri).split(":")[1])
} else { // files selected from all other sources, especially on Samsung devices
    databaseUri = uri
    selection = null
    selectionArgs = null
}
try {
    val projection = arrayOf(MediaStore.Images.Media.DATA,
        MediaStore.Images.Media._ID,
        MediaStore.Images.Media.ORIENTATION,
        MediaStore.Images.Media.DATE_TAKEN) // some example data you can query
    val cursor = context.contentResolver.query(databaseUri,
        projection, selection, selectionArgs, null)
    if (cursor.moveToFirst()) {
        // do whatever you like with the data
    }
    cursor.close()
} catch (e: Exception) {
    Log.e(TAG, e.message, e)
}

Voglio solo un codice funzionante di kotlin. Per me è un lavoro. grazie
Harvi Sirja

1

Ho provato diverse risposte qui e penso di avere una soluzione che funzionerà ogni volta e gestirà anche le autorizzazioni.

Si basa sulla soluzione intelligente di LEO. Questo post dovrebbe contenere tutto il codice necessario per farlo funzionare e dovrebbe funzionare su qualsiasi telefono e versione Android;)

Per poter scegliere un file da una scheda SD, è necessario nel manifest:

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

costanti:

private static final int PICK_IMAGE = 456; // Whatever number you like
public static final int MY_PERMISSIONS_REQUEST_READ_EXTERNAL = 28528; // Whatever number you like
public static final String FILE_TEMP_NAME = "temp_image"; // Whatever file name you like

Controlla l'autorizzazione e avviaImagePick se possibile

if (ContextCompat.checkSelfPermission(getThis(),
        Manifest.permission.READ_EXTERNAL_STORAGE)
        != PackageManager.PERMISSION_GRANTED) {

    ActivityCompat.requestPermissions(getThis(),
            new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
            MY_PERMISSIONS_REQUEST_READ_EXTERNAL);
}
else {
    launchImagePick();
}

Risposta di autorizzazione

@Override
public void onRequestPermissionsResult(int requestCode,
                                       @NonNull
                                         String permissions[],
                                       @NonNull
                                         int[] grantResults) {

    if (manageReadExternalPermissionResponse(this, requestCode, grantResults)) {
        launchImagePick();
    }
}

Gestire la risposta alle autorizzazioni

public static boolean manageReadExternalPermissionResponse(final Activity activity, int requestCode, int[] grantResults) {

    if (requestCode == MY_PERMISSIONS_REQUEST_READ_EXTERNAL) {

        // If request is cancelled, the result arrays are empty.

        if (grantResults.length > 0
                && grantResults[0] == PackageManager.PERMISSION_GRANTED) {

            // Permission was granted, yay! Do the
            // contacts-related task you need to do.
            return true;

        } else if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_DENIED) {

            boolean showRationale = ActivityCompat.shouldShowRequestPermissionRationale(activity,
                    Manifest.permission.READ_EXTERNAL_STORAGE);

            if (!showRationale) {
                // The user also CHECKED "never ask again".
                // You can either enable some fall back,
                // disable features of your app
                // or open another dialog explaining
                // again the permission and directing to
                // the app setting.

            } else {
                // The user did NOT check "never ask again".
                // This is a good place to explain the user
                // why you need the permission and ask if he/she wants
                // to accept it (the rationale).
            }
        } else {
            // Permission denied, boo! Disable the
            // functionality that depends on this permission.
        }
    }
    return false;
}

Avvia selezione immagine

private void launchImagePick() {

    Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
    intent.setType("image/*");
    intent.addCategory(Intent.CATEGORY_OPENABLE);
    startActivityForResult(intent, PICK_IMAGE);

    // see onActivityResult
}

Gestisci la risposta di prelievo dell'immagine

@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if (requestCode == PICK_IMAGE) {

        if (resultCode == Activity.RESULT_OK) {
            if (data != null && data.getData() != null) {

                try {
                     InputStream inputStream = getContentResolver().openInputStream(data.getData())
                     if (inputStream != null) {

                        // No special persmission needed to store the file like that
                        FileOutputStream fos = openFileOutput(FILE_TEMP_NAME, Context.MODE_PRIVATE);

                        final int BUFFER_SIZE = 1 << 10 << 3; // 8 KiB buffer
                        byte[] buffer = new byte[BUFFER_SIZE];
                        int bytesRead = -1;
                        while ((bytesRead = inputStream.read(buffer)) > -1) {
                            fos.write(buffer, 0, bytesRead);
                        }
                        inputStream.close();
                        fos.close();

                        File tempImageFile = new File(getFilesDir()+"/"+FILE_TEMP_NAME);

                        // Do whatever you want with the File

                        // Delete when not needed anymore
                        deleteFile(FILE_TEMP_NAME);
                    }
                }
                catch (Exception e) {
                    e.printStackTrace();
                }
            } else {
                // Error display
            }
        } else {
            // The user did not select any image
        }
    }
}

È tutto gente; questo funziona per me su tutti i telefoni che ho.


0

Questo è un trucco totale, ma ecco cosa ho fatto ...

Quindi, mentre giocavo con l'impostazione di DocumentsProvider , ho notato che il codice di esempio (in getDocIdForFile, intorno alla riga 450) genera un ID univoco per un documento selezionato in base al percorso (univoco) del file relativo alla radice specificata che gli dai (ovvero cosa hai impostato mBaseDirsulla linea 96).

Quindi l'URI finisce per assomigliare a:

content://com.example.provider/document/root:path/to/the/file

Come dicono i documenti, sta assumendo solo una singola radice (nel mio caso è Environment.getExternalStorageDirectory()ma potresti usare altrove ... quindi prende il percorso del file, a partire dalla radice, e lo rende l'ID univoco, anteponendo " root:". Quindi può determinare il percorso eliminando la "/document/root:"parte da uri.getPath (), creando un percorso file effettivo facendo qualcosa del genere:

public void onActivityResult(int requestCode, int resultCode, Intent data) {
// check resultcodes and such, then...
uri = data.getData();
if (uri.getAuthority().equals("com.example.provider"))  {
    String path = Environment.getExternalStorageDirectory(0.toString()
                 .concat("/")
                 .concat(uri.getPath().substring("/document/root:".length())));
    doSomethingWithThePath(path); }
else {
    // another provider (maybe a cloud-based service such as GDrive)
    // created this uri.  So handle it, or don't.  You can allow specific
    // local filesystem providers, filter non-filesystem path results, etc.
}

Lo so. È vergognoso, ma ha funzionato. Di nuovo, questo dipende da voi di utilizzare il proprio fornitore di documenti nella vostra applicazione per generare l'ID del documento.

(Inoltre, c'è un modo migliore per costruire il percorso che non assume che "/" sia il separatore di percorso, ecc. Ma hai l'idea.)


Per rispondere a me stesso con un pensiero ancora più folle: se la tua app sta già gestendo file://gli intenti da un selettore di file esterno, puoi anche controllare l'autorità, come nell'esempio sopra per assicurarti che provenga dal tuo provider personalizzato e, in tal caso, potresti usa anche il percorso per "forgiare" un nuovo file://intento usando il percorso che hai estratto, quindi StartActivity()lascia che la tua app lo raccolga. Lo so, terribile.
ingrasso

0

Questo ha funzionato bene per me:

else if(requestCode == GALLERY_ACTIVITY_NEW && resultCode == Activity.RESULT_OK)
{
    Uri uri = data.getData();
    Log.i(TAG, "old uri =  " + uri);
    dumpImageMetaData(uri);

    try {
        ParcelFileDescriptor parcelFileDescriptor =
                getContentResolver().openFileDescriptor(uri, "r");
        FileDescriptor fileDescriptor = parcelFileDescriptor.getFileDescriptor();
        Log.i(TAG, "File descriptor " + fileDescriptor.toString());

        final BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeFileDescriptor(fileDescriptor, null, options);

        options.inSampleSize =
           BitmapHelper.calculateInSampleSize(options,
                                              User.PICTURE_MAX_WIDTH_IN_PIXELS,
                                              User.PICTURE_MAX_HEIGHT_IN_PIXELS);
        options.inJustDecodeBounds = false;

        Bitmap bitmap = BitmapFactory.decodeFileDescriptor(fileDescriptor, null, options);
        imageViewPic.setImageBitmap(bitmap);

        ByteArrayOutputStream stream = new ByteArrayOutputStream();
        bitmap.compress(Bitmap.CompressFormat.JPEG, 100, stream);
        // get byte array here
        byte[] picData = stream.toByteArray();
        ParseFile picFile = new ParseFile(picData);
        user.setProfilePicture(picFile);
    }
    catch(FileNotFoundException exc)
    {
        Log.i(TAG, "File not found: " + exc.toString());
    }
}

dimenticare il dumpImageMetaData (uri); non è necessario
Rafa,

0

Sulla base della risposta di Paul Burke, ho riscontrato molti problemi nella risoluzione del percorso URI della scheda SD esterna poiché la maggior parte delle funzioni "incorporate" suggerite restituiscono percorsi che non vengono risolti in file.

Tuttavia, questo è il mio approccio al suo // TODO per gestire volumi non primari .

String resolvedPath = "";
File[] possibleExtSdComposites = context.getExternalFilesDirs(null);
for (File f : possibleExtSdComposites) {
    // Reset final path
    resolvedPath = "";

    // Construct list of folders
    ArrayList<String> extSdSplit = new ArrayList<>(Arrays.asList(f.getPath().split("/")));

    // Look for folder "<your_application_id>"
    int idx = extSdSplit.indexOf(BuildConfig.APPLICATION_ID);

    // ASSUMPTION: Expected to be found at depth 2 (in this case ExtSdCard's root is /storage/0000-0000/) - e.g. /storage/0000-0000/Android/data/<your_application_id>/files
    ArrayList<String> hierarchyList = new ArrayList<>(extSdSplit.subList(0, idx - 2));

    // Construct list containing full possible path to the file
    hierarchyList.add(tail);
    String possibleFilePath = TextUtils.join("/", hierarchyList);

    // If file is found --> success
    if (idx != -1 && new File(possibleFilePath).exists()) {
        resolvedPath = possibleFilePath;
        break;
    }
}

if (!resolvedPath.equals("")) {
    return resolvedPath;
} else {
    return null;
}

Nota che dipende dalla gerarchia che potrebbe essere diversa per ogni produttore di telefoni - non li ho testati tutti (finora ha funzionato bene su Xperia Z3 API 23 e Samsung Galaxy A3 API 23).

Si prega di confermare se non funziona bene altrove.


-1

La risposta di @paul burke funziona bene per le immagini della fotocamera e della galleria per il livello API 19 e versioni successive, ma non funziona se l'SDK minimo del progetto Android è impostato su 19 e alcune risposte che fanno riferimento sopra non funzionano sia per la galleria che per telecamera. Bene, ho modificato il codice di @paul burke che funziona a livello di API al di sotto di 19. Di seguito è riportato il codice.

public static String getPath(final Context context, final Uri uri) {

    final boolean isKitKat = Build.VERSION.SDK_INT >=
                             Build.VERSION_CODES.KITKAT;
    Log.i("URI",uri+"");
    String result = uri+"";

    // DocumentProvider
    // if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) {
    if (isKitKat && (result.contains("media.documents"))) {

        String[] ary = result.split("/");
        int length = ary.length;
        String imgary = ary[length-1];
        final String[] dat = imgary.split("%3A");

        final String docId = dat[1];
        final String type = dat[0];

        Uri contentUri = null;
        if ("image".equals(type)) {
            contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
        }
        else if ("video".equals(type)) {
        }
        else if ("audio".equals(type)) {
        }

        final String selection = "_id=?";
        final String[] selectionArgs = new String[] {
            dat[1]
        };

        return getDataColumn(context, contentUri, selection, selectionArgs);
    }
    else
    if ("content".equalsIgnoreCase(uri.getScheme())) {
        return getDataColumn(context, uri, null, null);
    }
    // File
    else if ("file".equalsIgnoreCase(uri.getScheme())) {
        return uri.getPath();
    }

    return null;
}

public static String getDataColumn(Context context, Uri uri, String selection,
                                   String[] selectionArgs) {
    Cursor cursor = null;
    final String column = "_data";
    final String[] projection = {
            column
    };

    try {
        cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs,
                null);
        if (cursor != null && cursor.moveToFirst()) {
            final int column_index = cursor.getColumnIndexOrThrow(column);
            return cursor.getString(column_index);
        }
    }
    finally {
        if (cursor != null)
            cursor.close();
    }
    return null;
}

Ricevo java.lang.IllegalArgumentException: nessuna delle colonne richieste può essere fornita quando si seleziona un'immagine di Google Doc
dirkoneill

@dirkoneill Ricevo la stessa eccezione. Hai trovato una soluzione?
Henry,

-4

La risposta alla tua domanda è che devi disporre delle autorizzazioni. Digita il seguente codice nel tuo file manifest.xml:

<uses-sdk  android:minSdkVersion="8"   android:targetSdkVersion="18" />
<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"></uses-permission>
<uses-permission android:name="android.permission.WRITE_OWNER_DATA"></uses-permission>
<uses-permission android:name="android.permission.READ_OWNER_DATA"></uses-permission>`

Ha funzionato per me ...

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.