
Michael Greifeneder
Vor KitKat (oder vor der neuen Galerie) die Intent.ACTION_GET_CONTENT
gab einen URI wie diesen zurück
content://media/external/images/media/3951.
Verwendung der ContentResolver
und frage nach
MediaStore.Images.Media.DATA
hat die Datei-URL zurückgegeben.
In KitKat gibt die Galerie jedoch einen URI (über “Last”) wie folgt zurück:
content://com.android.providers.media.documents/document/image:3951
Wie gehe ich damit um?

Paul Burke
Dies erfordert keine besonderen Berechtigungen und funktioniert mit dem Storage Access Framework sowie dem inoffiziellen ContentProvider
Muster (Dateipfad in _data
Bereich).
/**
* 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() + "https://stackoverflow.com/" + 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 sich eine aktuelle Version dieser Methode an Hier.
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);
}
Wahrscheinlich brauchen
@SuppressLint(“NeueApi”)
zum
takePersistableUriPermission

Voytez
Hatte das gleiche Problem, habe die obige Lösung ausprobiert, aber obwohl es im Allgemeinen funktionierte, erhielt ich aus irgendeinem Grund die Erlaubnisverweigerung für den Uri-Inhaltsanbieter für einige Bilder, obwohl ich die hatte android.permission.MANAGE_DOCUMENTS
Berechtigung richtig hinzugefügt.
Wie auch immer, ich habe eine andere Lösung gefunden, die das Öffnen der Bildergalerie anstelle der Ansicht der KITKAT-Dokumente mit erzwingen soll:
// 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);
BEARBEITEN
ACTION_OPEN_DOCUMENT
Möglicherweise müssen Sie Berechtigungsflags usw. beibehalten und führen im Allgemeinen häufig zu Sicherheitsausnahmen …
Eine andere Lösung ist die Verwendung von ACTION_GET_CONTENT
kombiniert mit c.getContentResolver().openInputStream(selectedImageURI)
die sowohl auf Pre-KK als auch auf KK funktionieren. Kitkat verwendet dann die neue Dokumentenansicht und diese Lösung funktioniert mit allen Apps wie Fotos, Galerie, Datei-Explorer, Dropbox, Google Drive usw.), aber denken Sie daran, dass Sie bei Verwendung dieser Lösung ein Bild in Ihrem erstellen müssen onActivityResult()
und speichern Sie es beispielsweise auf einer SD-Karte. Das Neuerstellen dieses Bildes aus gespeicherten URI beim nächsten App-Start würde eine Sicherheitsausnahme für den Content-Resolver auslösen, selbst wenn Sie Berechtigungs-Flags hinzufügen, wie in der Google API-Dokumentation beschrieben (das ist passiert, als ich einige Tests durchgeführt habe).
Zusätzlich empfehlen die Android Developer API Guidelines:
ACTION_OPEN_DOCUMENT ist nicht als Ersatz für ACTION_GET_CONTENT gedacht. Welche Sie verwenden sollten, hängt von den Anforderungen Ihrer App ab:
Verwenden Sie ACTION_GET_CONTENT, wenn Ihre App Daten einfach lesen/importieren soll. Bei diesem Ansatz importiert die App eine Kopie der Daten, beispielsweise eine Bilddatei.
Verwenden Sie ACTION_OPEN_DOCUMENT, wenn Sie möchten, dass Ihre App langfristigen, dauerhaften Zugriff auf Dokumente hat, die einem Dokumentanbieter gehören. Ein Beispiel wäre eine Fotobearbeitungs-App, mit der Benutzer Bilder bearbeiten können, die in einem Dokumentanbieter gespeichert sind.

Michal Klimczak
Wie von Commonsware erwähnt, sollten Sie nicht davon ausgehen, dass der Stream, den Sie erhalten, über ContentResolver
ist in eine Datei umwandelbar.
Was Sie wirklich tun sollten, ist, die zu öffnen InputStream
von dem ContentProvider
, dann erstellen Sie daraus eine Bitmap. Und es funktioniert auch auf 4.4 und früheren Versionen, keine Notwendigkeit zum Nachdenken.
//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 diese natürlich mit angemessen laden inSampleSize
: http://developer.android.com/training/displaying-bitmaps/load-bitmap.html. Aber das ist ein anderes Thema.

LÖWE
Ich glaube, die bereits geposteten Antworten sollten die Leute in die richtige Richtung lenken. Hier ist jedoch, was ich getan habe, was für den Legacy-Code, den ich aktualisiert habe, sinnvoll war. Der alte Code verwendete den URI aus der Galerie, um die Bilder zu ändern und dann zu speichern.
Vor 4.4 (und Google Drive) sahen die URIs so aus:
content://media/extern/images/media/41
Wie in der Frage angegeben, sehen sie häufiger 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 die URI aus der Galerie in den Datenordner der App kopiert. Dann entstand eine neue 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 */
}
Hinweis – copyAndClose() führt nur Datei-I/O aus, um InputStream in einen FileOutputStream zu kopieren. Der Code wird nicht gepostet.
Ich wollte nur sagen, dass diese Antwort brillant ist und ich sie seit langem ohne Probleme verwende. Aber vor einiger Zeit bin ich auf ein Problem gestoßen, dass DownloadsProvider URIs im Format zurückgibt content://com.android.providers.downloads.documents/document/raw%3A%2Fstorage%2Femulated%2F0%2FDownload%2Fdoc.pdf
und daher ist die App mit abgestürzt NumberFormatException
da es unmöglich ist, seine URI-Segmente so lange zu analysieren. Aber raw:
segment enthält direkte URI, die verwendet werden können, um eine referenzierte Datei abzurufen. Also habe ich es durch Austausch behoben isDownloadsDocument(uri)
if
Inhalt mit folgendem:
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;
}
}

Grzegorz Pawelczuk
Ich habe mehrere Antworten zu 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() + "https://stackoverflow.com/" + 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());
}
}
9632600cookie-checkAndroid Gallery auf Android 4.4 (KitKat) gibt einen anderen URI für Intent.ACTION_GET_CONTENT zurückyes
Aus dem Stegreif würde ich Wege finden, die Inhalte zu nutzen, die keinen direkten Zugriff auf die Datei erfordern. Zum Beispiel das
Uri
sollte als Stream über geöffnet werden könnenContentResolver
. Ich bin schon lange nervös bei Apps, die davon ausgehen, dass acontent://
Uri
die eine Datei darstellt, kann immer in eine konvertiert werdenFile
.– CommonsWare
7. November 2013 um 12:31 Uhr
@CommonsWare,Wenn ich einen Bildpfad in sqlite db speichern möchte, damit ich ihn später öffnen kann, soll ich den URI oder den absoluten Dateipfad speichern?
– Schlange
19. Dezember 2013 um 20:45 Uhr
@CommonsWare Ich stimme Ihrer Nervosität zu. 🙂 Allerdings muss ich in der Lage sein, einen Dateinamen (für ein Bild) an nativen Code zu übergeben. Eine Lösung besteht darin, die erhaltenen Daten mit einem zu kopieren
InputStream
auf derContentResolver
an einen vorher festgelegten Ort, damit es einen bekannten Dateinamen hat. Allerdings klingt das für mich verschwenderisch. Irgendwelche anderen Vorschläge?– darrenp
15. Januar 2014 um 17:22 Uhr
@darrenp: Ummm …, schreiben Sie den nativen Code neu, um mit einer zu arbeiten
InputStream
über JNI? Leider gibt es nicht allzu viele Möglichkeiten für Sie.– CommonsWare
15. Januar 2014 um 17:34 Uhr
Das ist nützlich zu wissen. Vielen Dank für Ihre Antwort. Ich habe seitdem herausgefunden, dass wir das Bild jetzt im Speicher und nicht über eine Datei an C++ übergeben, sodass wir es jetzt verwenden können
InputStream
anstelle einer Datei (was großartig ist). Nur das Lesen von EXIF-Tags ist etwas schwierig und erfordert Die Bibliothek von Drew Noakes. Vielen Dank für Ihre Kommentare.– darrenp
15. Januar 2014 um 20:49 Uhr