web-dev-qa-db-ger.com

Android Gallery auf Android 4.4 (KitKat) gibt verschiedene URIs für Intent.ACTION_GET_CONTENT zurück

Vor KitKat (oder vor der neuen Galerie) hat der Intent.ACTION_GET_CONTENT einen solchen URI zurückgegeben

inhalt: // media/external/images/media/3951.

Über die Variable ContentResolver und die Abfrage nach MediaStore.Images.Media.DATA wurde die Datei-URL zurückgegeben.

In KitKat gibt die Galerie jedoch einen URI (über "Last") wie folgt zurück:

inhalt: //com.Android.providers.media.documents/document/image: 3951

Wie gehe ich damit um?

204

Versuche dies: 

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

}

Vermutlich brauchen 

@SuppressLint ("NewApi")

zum 

takePersistableUriPermission

108
finder

Dies erfordert keine besonderen Berechtigungen und funktioniert mit dem Storage Access Framework sowie dem inoffiziellen Muster ContentProvider (Dateipfad im Feld _data).

/**
 * 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());
}

Sehen Sie eine aktuelle Version dieser Methode hier .

171
Paul Burke

Hatte das gleiche Problem, probierte die Lösung oben, aber obwohl es allgemein funktionierte, bekam ich aus irgendeinem Grund die Erlaubnis, Uri Content Provider für einige Bilder zu verweigern, obwohl ich die Android.permission.MANAGE_DOCUMENTS-Berechtigung richtig hinzugefügt hatte.

Jedenfalls fand eine andere Lösung, die das Öffnen der Bildergalerie anstelle der KitKat-Dokumente erzwingt, mit:

// KitKat

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

und dann das Bild laden:

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

EDIT

Für ACTION_OPEN_DOCUMENT müssen Sie möglicherweise die Berechtigungsflags usw. beibehalten und führen in der Regel zu Sicherheitsausnahmen ...

Eine andere Lösung ist die Verwendung von ACTION_GET_CONTENT in Kombination mit c.getContentResolver().openInputStream(selectedImageURI), das sowohl für Pre-KK als auch für KK geeignet ist. KitKat verwendet dann die neue Dokumentansicht und diese Lösung funktioniert mit allen Anwendungen wie Fotos, Galerie, Datei-Explorer, Dropbox, Google Drive usw.). Denken Sie jedoch daran, dass Sie bei der Verwendung dieser Lösung ein Bild in Ihrer onActivityResult() erstellen und speichern müssen auf SD-Karte zum Beispiel. Wenn Sie dieses Bild beim nächsten Start der App aus dem gespeicherten URI neu erstellen, wird die Sicherheitsausnahme für den Inhaltsauflöser ausgelöst, selbst wenn Sie Berechtigungsflags hinzufügen, wie in den Google-API-Dokumenten beschrieben.

Zusätzlich schlagen die Android Developer API-Richtlinien Folgendes vor:

ACTION_OPEN_DOCUMENT soll ACTION_GET_CONTENT nicht ersetzen. Welche Sie verwenden sollten, hängt von den Anforderungen von .__ ab. Ihre App:

Verwenden Sie ACTION_GET_CONTENT, wenn Ihre App einfach gelesen/importiert werden soll Daten. Bei diesem Ansatz importiert die App eine Kopie der Daten, z. B. eine Bilddatei.

Verwenden Sie ACTION_OPEN_DOCUMENT, wenn Ihre App über .__ verfügen soll. dauerhafter Zugriff auf Dokumente, die einem Dokument gehören Anbieter. Ein Beispiel wäre eine Fotobearbeitungs-App, mit der Benutzer .__ bearbeiten können. Bilder, die in einem Dokumentanbieter gespeichert sind.

67
voytez

Genau wie Commonsware erwähnt, sollten Sie nicht davon ausgehen, dass der über ContentResolver erhaltene Stream in eine Datei konvertierbar ist.

Was Sie wirklich tun sollten, ist, die InputStream von der ContentProvider zu öffnen und daraus eine Bitmap zu erstellen. Und es funktioniert auch mit 4.4 und früheren Versionen, keine Reflexion.

    //cxt -> current context

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

    }

Wenn Sie mit großen Bildern arbeiten, sollten Sie sie natürlich mit der entsprechenden inSampleSize: http://developer.Android.com/training/displaying-bitmaps/load-bitmap.html laden. Aber das ist ein anderes Thema.

38
Michał K

Ich glaube, die Antworten, die bereits veröffentlicht wurden, sollten die Leute in die richtige Richtung bringen. Hier ist jedoch das, was ich für den alten Code, den ich aktualisiert habe, sinnvoll machte. Der ältere Code verwendete die URI aus der Galerie, um die Bilder zu ändern und dann zu speichern.

Vor 4.4 (und Google-Laufwerk) würden die URIs folgendermaßen aussehen: content: // media/external/images/media/41

Wie in der Frage angegeben, sehen sie öfter so aus: content: //com.Android.providers.media.documents/document/image: 3951

Da ich die Möglichkeit brauchte, Bilder zu speichern und den bereits vorhandenen Code nicht zu stören, habe ich einfach den URI aus der Galerie in den Datenordner der App kopiert. Dann entstand ein neuer URI aus der gespeicherten Bilddatei im Datenordner.

Hier ist die Idee:

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 */
 }

Note - copyAndClose () kopiert InputStream einfach in eine FileOutputStream-Datei. Der Code wird nicht gebucht.

31
LEO

Ich wollte nur sagen, diese Antwort ist genial und ich benutze sie lange Zeit ohne Probleme. Vor einiger Zeit bin ich jedoch auf ein Problem gestoßen, dass DownloadsProvider URIs im Format content://com.Android.providers.downloads.documents/document/raw%3A%2Fstorage%2Femulated%2F0%2FDownload%2Fdoc.pdf zurückgibt und daher die App mit NumberFormatException abgestürzt ist, da es unmöglich ist, ihre Urisegmente so lange zu analysieren. raw: segment enthält jedoch einen direkten uri, mit dem eine referenzierte Datei abgerufen werden kann. Ich habe es also behoben, indem ich isDownloadsDocument(uri)if-Inhalte durch Folgendes ersetzt:

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

Ich habe mehrere Antworten in einer funktionierenden Lösung kombiniert, die mit dem Dateipfad resultiert

Der Mime-Typ ist für den Beispielzweck irrelevant.

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

Handhabungsergebnis

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

}
7

Frage

So erhalten Sie einen tatsächlichen Dateipfad von einem URI

Antworten

Meines Wissens müssen wir den Dateipfad nicht von einem URI abrufen, da wir in den meisten Fällen den URI direkt verwenden können, um unsere Arbeit zu erledigen (z. B. 1. Bitmap abrufen 2. Datei an den Server senden usw.) .)

1. Senden an den Server

Wir können die Datei direkt über den URI an den Server senden.

Über die URI können wir InputStream abrufen, das wir mit MultiPartEntity direkt an den Server senden können.

Beispiel

/**
 * 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. Abrufen einer Bitmap von einer URI

Wenn der URI auf ein Bild zeigt, erhalten wir eine Bitmap, sonst 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;
}

Bemerkungen

  1. Android bietet keine Methoden zum Abrufen des Dateipfads von einem URI. In den meisten der obigen Antworten haben wir einige Konstanten hartcodiert, die bei der Funktionsfreigabe kaputt gehen können (sorry, ich kann mich irren).
  2. Bevor Sie direkt von einer URI zu einer Lösung für den Abruf der Dateipfade gelangen, probieren Sie, ob Sie Ihren Anwendungsfall mit einer URI und Android-Standardmethoden lösen können.

Referenz

  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
7
Vasanth

Diese Android Bibliothek behandelt die Falländerungen in KitKat (einschließlich der älteren Versionen - 2.1+):
https://github.com/iPaulPro/aFileChooser

Verwenden Sie die Funktion String path = FileUtils.getPath(context, uri), um den zurückgegebenen Uri in eine Pfadzeichenfolge zu konvertieren, die in allen Betriebssystemversionen verwendet werden kann. Weitere Informationen hierzu finden Sie hier: https://stackoverflow.com/a/20559175/860488

6

Für diejenigen, die noch immer @Paul Burkes Code mit Android SDK Version 23 oder höher verwenden, ist Ihr Projekt auf die Fehlermeldung gestoßen, dass Sie EXTERNAL_PERMISSION fehlen, und Sie sind sehr sicher, dass Sie bereits Benutzerrechte in Ihrer AndroidManifest.xml-Datei hinzugefügt haben. Das liegt daran, dass Sie in Android API 23 oder höher und Google möglicherweise die Erlaubnis erneut gewähren müssen, während Sie die Aktion für den Zugriff auf die Datei in Runtime ausführen.

Das bedeutet: Wenn Ihre SDK-Version 23 oder höher ist, werden Sie während der Auswahl der Bilddatei nach der Berechtigung READ & WRITE gefragt, und Sie möchten den URI der SDK-Datei erfahren.

Und das Folgende ist mein Code, zusätzlich zur Lösung von Paul Burke. Ich füge diesen Code hinzu und mein Projekt funktioniert gut.

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

Und in Ihrer Aktivität & Fragment, wo Sie nach der URI fragen:

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

In meinem Fall definiere ich in CompatUtils.Java die verifyStoragePermissions-Methode (als statischen Typ, damit ich sie innerhalb einer anderen Aktivität aufrufen kann).

Es sollte auch sinnvoller sein, wenn Sie zuerst einen if -Status festlegen, um zu sehen, ob die aktuelle SDK-Version über 23 liegt oder nicht, bevor Sie die verifyStoragePermissions-Methode aufrufen.

3
Anthonyeef

Das ist was ich mache:

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

HINWEIS: Die managedQuery()-Methode ist veraltet, daher verwende ich sie nicht.

Diese Antwort ist von m3n0R auf Frage Android bekommt echten Pfad von Uri.getPath () und ich behaupte kein Guthaben. Ich dachte nur, dass Leute, die dieses Problem noch nicht gelöst haben, dies nutzen könnten.

2
Isaac Zais

Wenn jemand interessiert ist, habe ich eine funktionierende Kotlin-Version für ACTION_GET_CONTENT erstellt:

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)
}
1
0101100101

Versuchen Sie, die Verwendung der takePersistableUriPermission-Methode zu vermeiden, da dadurch eine Laufzeitausnahme für mich ausgelöst wurde. * Wählen Sie aus der Galerie . * /

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 für das Ergebnis zur Verarbeitung der Bilddaten:

@Override protected void onActivityResult (int requestCode, int resultCode, Intent-Daten) {

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


    }
1
saranya

Ich habe hier einige Antworten ausprobiert, und ich denke, ich habe eine Lösung, die jedes Mal funktioniert und Berechtigungen verwaltet.

Es basiert auf der cleveren Lösung von LEO. Dieser Beitrag sollte den gesamten Code enthalten, der für diese Funktion erforderlich ist. Er sollte auf allen Telefon- und Android-Versionen funktionieren.

Damit Sie eine Datei von einer SD-Karte auswählen können, benötigen Sie dies in Ihrem Manifest:

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

Konstanten:

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

Überprüfen Sie die Berechtigung und starten Sie, falls möglich, imageImagePick

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

Antwort der Erlaubnis

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

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

Berechtigungsantwort verwalten

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

Bildauswahl starten

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
}

Antwort auf Bildauswahl verwalten

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

Das war's Leute; das funktioniert bei mir auf allen Telefonen, die ich habe.

1
Quentin G.

Aufbauend auf Paul Burkes Antwort Ich hatte viele Probleme beim Lösen des URI-Pfads der externen SD-Karte, da die meisten der "eingebauten" Funktionen Pfade zurückgeben, die nicht in Dateien aufgelöst werden.

Dies ist jedoch mein -Ansatz von his// TODO, der nicht-primäre Datenträger behandelt.

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

Beachten Sie, dass es von der Hierarchie abhängt, die bei jedem Telefonhersteller unterschiedlich sein kann. Ich habe sie nicht alle getestet (bisher funktionierte das mit Xperia Z3 API 23 und Samsung Galaxy A3 API 23 gut).

Bitte bestätigen Sie, ob es an anderer Stelle nicht gut funktioniert.

0
Evusas

Das funktionierte gut für mich:

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());
    }
}
0
Rafa

Dies ist ein totaler Hack, aber hier habe ich getan ...

Beim Einrichten eines DocumentsProvider habe ich also festgestellt, dass der Beispielcode (in getDocIdForFile, um die Zeile 450) eine eindeutige ID für ein ausgewähltes Dokument generiert, basierend auf dem (eindeutigen) Pfad der Datei relativ zum angegebener Stamm, den Sie ihm geben (dh, was Sie mBaseDir in Zeile 96 setzen).

Die URI sieht also ungefähr so ​​aus:

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

Wie die Dokumente sagen, wird davon ausgegangen, dass es sich nur um ein einzelnes Stammverzeichnis handelt (in meinem Fall ist dies Environment.getExternalStorageDirectory(), aber Sie können es woanders verwenden ...) Dann nimmt es den Dateipfad, beginnend beim Stammverzeichnis, und macht ihn zur eindeutigen ID, wobei "root:" vorangestellt wird. So kann ich den Pfad ermitteln, indem ich den "/document/root: "-Part aus uri.getPath () entferne und einen tatsächlichen Dateipfad durch das Ausführen von etwas wie dem folgenden erzeuge:

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

Ich kenne. Es ist beschämend, aber es hat funktioniert. Dies setzt wiederum voraus, dass Sie Ihren own -Dokumentanbieter in Ihrer App verwenden, um die Dokument-ID zu generieren.

(Außerdem gibt es eine bessere Methode, um den Pfad zu erstellen, bei dem nicht angenommen wird, dass "/" das Pfadtrennzeichen ist usw.). Sie erhalten jedoch die Idee.

0
fattire