Wie kann man ein Bild von einer bestimmten URL in Android herunterladen und speichern?
quelle
Wie kann man ein Bild von einer bestimmten URL in Android herunterladen und speichern?
Letztes großes Update: 31. März 2016
TL; DR aka hör auf zu reden, gib mir einfach den Code !!
Gehen Sie zum Ende dieses Beitrags, kopieren Sie die
BasicImageDownloader
(Javadoc-Version hier ) in Ihr Projekt, implementieren Sie dieOnImageLoaderListener
Schnittstelle und fertig.Hinweis : Obwohl die
BasicImageDownloader
Behandlung mögliche Fehler behandelt und verhindert, dass Ihre App abstürzt, falls etwas schief geht, führt sie keine Nachbearbeitung (z. B. Verkleinerung) des heruntergeladenen Geräts durchBitmaps
.
Da dieser Beitrag viel Aufmerksamkeit erhalten hat, habe ich mich entschlossen, ihn komplett zu überarbeiten, um zu verhindern, dass die Leute veraltete Technologien, schlechte Programmierpraktiken oder einfach nur alberne Dinge verwenden - wie die Suche nach "Hacks", um das Netzwerk im Hauptthread oder auszuführen Akzeptieren Sie alle SSL-Zertifikate.
Ich habe ein Demo-Projekt namens "Image Downloader" erstellt, das zeigt, wie ein Bild mit meiner eigenen Downloader-Implementierung, dem integrierten Android-Gerät DownloadManager
sowie einigen beliebten Open-Source-Bibliotheken heruntergeladen (und gespeichert) wird . Sie können den vollständigen Quellcode anzeigen oder das Projekt auf GitHub herunterladen .
Hinweis : Ich habe die Berechtigungsverwaltung für SDK 23+ (Marshmallow) noch nicht angepasst, daher zielt das Projekt auf SDK 22 (Lollipop) ab.
In meiner Schlussfolgerung am Ende dieses Beitrags werde ich meine bescheidene Meinung über den richtigen Anwendungsfall für jede bestimmte Art des Herunterladens von Bildern, die ich erwähnt habe, teilen .
Beginnen wir mit einer eigenen Implementierung (den Code finden Sie am Ende des Beitrags). Zunächst einmal ist dies ein Basic ImageDownloader und das wars. Es wird lediglich eine Verbindung zu der angegebenen URL hergestellt, die Daten gelesen und versucht, sie als zu dekodieren. Bei Bedarf werden die Rückrufe Bitmap
der OnImageLoaderListener
Schnittstelle ausgelöst . Der Vorteil dieses Ansatzes - es ist einfach und Sie haben einen klaren Überblick darüber, was los ist. Ein guter Weg, wenn Sie nur einige Bilder herunterladen / anzeigen und speichern müssen, während Sie sich nicht darum kümmern, einen Speicher- / Festplatten-Cache zu verwalten.
Hinweis: Bei großen Bildern müssen Sie diese möglicherweise verkleinern .
- -
Mit Android DownloadManager kann das System den Download für Sie erledigen. Es ist tatsächlich in der Lage, jede Art von Dateien herunterzuladen, nicht nur Bilder. Sie können Ihren Download unbeaufsichtigt und für den Benutzer unsichtbar machen oder dem Benutzer ermöglichen, den Download im Benachrichtigungsbereich anzuzeigen. Sie können sich auch registrieren BroadcastReceiver
, um benachrichtigt zu werden, nachdem der Download abgeschlossen ist. Das Setup ist ziemlich einfach. Beispielcode finden Sie im verknüpften Projekt.
Die Verwendung von DownloadManager
ist im Allgemeinen keine gute Idee, wenn Sie das Bild auch anzeigen möchten, da Sie die gespeicherte Datei lesen und dekodieren müssen, anstatt nur die heruntergeladene Datei Bitmap
in eine zu setzen ImageView
. Das DownloadManager
bietet auch keine API für Ihre App, um den Download-Fortschritt zu verfolgen.
- -
Nun die Einführung der großartigen Dinge - der Bibliotheken. Sie können viel mehr als nur Bilder herunterladen und anzeigen, einschließlich: Erstellen und Verwalten des Speicher- / Festplatten-Caches, Ändern der Größe von Bildern, Transformieren und mehr.
Ich beginne mit Volley , einer leistungsstarken Bibliothek, die von Google erstellt und in der offiziellen Dokumentation behandelt wird. Volley ist zwar eine universelle Netzwerkbibliothek, die sich nicht auf Bilder spezialisiert hat, bietet jedoch eine leistungsstarke API zum Verwalten von Bildern.
Sie müssen eine Singleton- Klasse zum Verwalten von Volley-Anforderungen implementieren und können loslegen.
Vielleicht möchten Sie Ihre ImageView
durch Volleys ersetzen NetworkImageView
, damit der Download im Grunde genommen zu einem Einzeiler wird:
((NetworkImageView) findViewById(R.id.myNIV)).setImageUrl(url, MySingleton.getInstance(this).getImageLoader());
Wenn Sie mehr Kontrolle benötigen, sieht es so aus, als würden Sie ImageRequest
mit Volley eine erstellen :
ImageRequest imgRequest = new ImageRequest(url, new Response.Listener<Bitmap>() {
@Override
public void onResponse(Bitmap response) {
//do stuff
}
}, 0, 0, ImageView.ScaleType.CENTER_CROP, Bitmap.Config.ARGB_8888,
new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
//do stuff
}
});
Es ist erwähnenswert, dass Volley über einen hervorragenden Mechanismus zur Fehlerbehandlung verfügt, indem es die VolleyError
Klasse bereitstellt , mit der Sie die genaue Fehlerursache ermitteln können. Wenn Ihre App viel vernetzt und Bilder verwaltet, ist dies nicht der Hauptzweck von Volley. Dann passt Volley perfekt zu Ihnen.
- -
Square's Picasso ist eine bekannte Bibliothek, die das gesamte Laden von Bildern für Sie erledigt. Das Anzeigen eines Bildes mit Picasso ist so einfach wie:
Picasso.with(myContext)
.load(url)
.into(myImageView);
Standardmäßig verwaltet Picasso den Festplatten- / Speicher-Cache, sodass Sie sich darüber keine Gedanken machen müssen. Für mehr Kontrolle können Sie die Target
Schnittstelle implementieren und zum Laden Ihres Bildes verwenden - dies liefert Rückrufe ähnlich dem Volley-Beispiel. Überprüfen Sie das Demo-Projekt für Beispiele.
Mit Picasso können Sie auch Transformationen auf das heruntergeladene Bild anwenden, und es gibt sogar andere Bibliotheken , die diese API erweitern. Funktioniert auch sehr gut in einem RecyclerView
/ ListView
/ GridView
.
- -
Universal Image Loader ist eine weitere sehr beliebte Bibliothek, die der Bildverwaltung dient. Es verwendet eine eigene ImageLoader
Instanz, die (nach der Initialisierung) über eine globale Instanz verfügt, mit der Bilder in einer einzigen Codezeile heruntergeladen werden können:
ImageLoader.getInstance().displayImage(url, myImageView);
Wenn Sie den Download-Fortschritt verfolgen oder auf die heruntergeladenen Dateien zugreifen möchten Bitmap
:
ImageLoader.getInstance().displayImage(url, myImageView, opts,
new ImageLoadingListener() {
@Override
public void onLoadingStarted(String imageUri, View view) {
//do stuff
}
@Override
public void onLoadingFailed(String imageUri, View view, FailReason failReason) {
//do stuff
}
@Override
public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {
//do stuff
}
@Override
public void onLoadingCancelled(String imageUri, View view) {
//do stuff
}
}, new ImageLoadingProgressListener() {
@Override
public void onProgressUpdate(String imageUri, View view, int current, int total) {
//do stuff
}
});
Das opts
Argument in diesem Beispiel ist ein DisplayImageOptions
Objekt. Weitere Informationen finden Sie im Demo-Projekt.
Ähnlich wie bei Volley bietet UIL die FailReason
Klasse, mit der Sie überprüfen können, was bei einem Downloadfehler fehlgeschlagen ist. Standardmäßig verwaltet UIL einen Speicher- / Festplatten-Cache, wenn Sie nicht ausdrücklich anweisen, dies nicht zu tun.
Hinweis : Der Autor hat erwähnt, dass er das Projekt ab dem 27. November 2015 nicht mehr verwaltet. Da es jedoch viele Mitwirkende gibt, können wir hoffen, dass der Universal Image Loader weiterleben wird.
- -
Facebooks Fresco ist die neueste und (IMO) fortschrittlichste Bibliothek, die die Bildverwaltung auf ein neues Niveau Bitmaps
hebt : vom Halten des Java-Heaps (vor Lollipop) bis zur Unterstützung von animierten Formaten und progressivem JPEG-Streaming .
Weitere Informationen zu Ideen und Techniken hinter Fresco finden Sie in diesem Beitrag .
Die grundlegende Verwendung ist recht einfach. Beachten Sie, dass Sie Fresco.initialize(Context);
nur einmal anrufen müssen , vorzugsweise in der Application
Klasse. Das mehrmalige Initialisieren von Fresco kann zu unvorhersehbarem Verhalten und OOM-Fehlern führen.
Fresco verwendet Drawee
s, um Bilder anzuzeigen. Sie können sich diese als ImageView
s vorstellen:
<com.facebook.drawee.view.SimpleDraweeView
android:id="@+id/drawee"
android:layout_width="match_parent"
android:layout_height="match_parent"
fresco:fadeDuration="500"
fresco:actualImageScaleType="centerCrop"
fresco:placeholderImage="@drawable/placeholder_grey"
fresco:failureImage="@drawable/error_orange"
fresco:placeholderImageScaleType="fitCenter"
fresco:failureImageScaleType="centerInside"
fresco:retryImageScaleType="centerCrop"
fresco:progressBarImageScaleType="centerInside"
fresco:progressBarAutoRotateInterval="1000"
fresco:roundAsCircle="false" />
Wie Sie sehen können, sind viele Dinge (einschließlich Transformationsoptionen) bereits in XML definiert. Alles, was Sie tun müssen, um ein Bild anzuzeigen, ist ein Einzeiler:
mDrawee.setImageURI(Uri.parse(url));
Fresco bietet eine erweiterte Anpassungs-API, die unter Umständen sehr komplex sein kann und erfordert, dass der Benutzer die Dokumente sorgfältig liest (ja, manchmal müssen Sie RTFM verwenden).
Ich habe Beispiele für progressive JPEGs und animierte Bilder in das Beispielprojekt aufgenommen.
Beachten Sie, dass der folgende Text meine persönliche Meinung widerspiegelt und nicht als Postulat verstanden werden sollte.
Recycler-/Grid-/ListView
und nicht eine ganze Reihe von Bildern benötigen, um für die Anzeige bereit zu sein, sollte der BasicImageDownloader Ihren Anforderungen entsprechen.JSON
Daten sendet / empfängt , mit Bildern arbeitet, diese aber nicht der Hauptzweck der App sind, entscheiden Sie sich für Volley .Falls Sie das verpasst haben, klicken Sie auf den Github-Link für das Demo-Projekt.
Und hier ist die BasicImageDownloader.java
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.AsyncTask;
import android.support.annotation.NonNull;
import android.util.Log;
import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import java.util.HashSet;
import java.util.Set;
public class BasicImageDownloader {
private OnImageLoaderListener mImageLoaderListener;
private Set<String> mUrlsInProgress = new HashSet<>();
private final String TAG = this.getClass().getSimpleName();
public BasicImageDownloader(@NonNull OnImageLoaderListener listener) {
this.mImageLoaderListener = listener;
}
public interface OnImageLoaderListener {
void onError(ImageError error);
void onProgressChange(int percent);
void onComplete(Bitmap result);
}
public void download(@NonNull final String imageUrl, final boolean displayProgress) {
if (mUrlsInProgress.contains(imageUrl)) {
Log.w(TAG, "a download for this url is already running, " +
"no further download will be started");
return;
}
new AsyncTask<Void, Integer, Bitmap>() {
private ImageError error;
@Override
protected void onPreExecute() {
mUrlsInProgress.add(imageUrl);
Log.d(TAG, "starting download");
}
@Override
protected void onCancelled() {
mUrlsInProgress.remove(imageUrl);
mImageLoaderListener.onError(error);
}
@Override
protected void onProgressUpdate(Integer... values) {
mImageLoaderListener.onProgressChange(values[0]);
}
@Override
protected Bitmap doInBackground(Void... params) {
Bitmap bitmap = null;
HttpURLConnection connection = null;
InputStream is = null;
ByteArrayOutputStream out = null;
try {
connection = (HttpURLConnection) new URL(imageUrl).openConnection();
if (displayProgress) {
connection.connect();
final int length = connection.getContentLength();
if (length <= 0) {
error = new ImageError("Invalid content length. The URL is probably not pointing to a file")
.setErrorCode(ImageError.ERROR_INVALID_FILE);
this.cancel(true);
}
is = new BufferedInputStream(connection.getInputStream(), 8192);
out = new ByteArrayOutputStream();
byte bytes[] = new byte[8192];
int count;
long read = 0;
while ((count = is.read(bytes)) != -1) {
read += count;
out.write(bytes, 0, count);
publishProgress((int) ((read * 100) / length));
}
bitmap = BitmapFactory.decodeByteArray(out.toByteArray(), 0, out.size());
} else {
is = connection.getInputStream();
bitmap = BitmapFactory.decodeStream(is);
}
} catch (Throwable e) {
if (!this.isCancelled()) {
error = new ImageError(e).setErrorCode(ImageError.ERROR_GENERAL_EXCEPTION);
this.cancel(true);
}
} finally {
try {
if (connection != null)
connection.disconnect();
if (out != null) {
out.flush();
out.close();
}
if (is != null)
is.close();
} catch (Exception e) {
e.printStackTrace();
}
}
return bitmap;
}
@Override
protected void onPostExecute(Bitmap result) {
if (result == null) {
Log.e(TAG, "factory returned a null result");
mImageLoaderListener.onError(new ImageError("downloaded file could not be decoded as bitmap")
.setErrorCode(ImageError.ERROR_DECODE_FAILED));
} else {
Log.d(TAG, "download complete, " + result.getByteCount() +
" bytes transferred");
mImageLoaderListener.onComplete(result);
}
mUrlsInProgress.remove(imageUrl);
System.gc();
}
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
public interface OnBitmapSaveListener {
void onBitmapSaved();
void onBitmapSaveError(ImageError error);
}
public static void writeToDisk(@NonNull final File imageFile, @NonNull final Bitmap image,
@NonNull final OnBitmapSaveListener listener,
@NonNull final Bitmap.CompressFormat format, boolean shouldOverwrite) {
if (imageFile.isDirectory()) {
listener.onBitmapSaveError(new ImageError("the specified path points to a directory, " +
"should be a file").setErrorCode(ImageError.ERROR_IS_DIRECTORY));
return;
}
if (imageFile.exists()) {
if (!shouldOverwrite) {
listener.onBitmapSaveError(new ImageError("file already exists, " +
"write operation cancelled").setErrorCode(ImageError.ERROR_FILE_EXISTS));
return;
} else if (!imageFile.delete()) {
listener.onBitmapSaveError(new ImageError("could not delete existing file, " +
"most likely the write permission was denied")
.setErrorCode(ImageError.ERROR_PERMISSION_DENIED));
return;
}
}
File parent = imageFile.getParentFile();
if (!parent.exists() && !parent.mkdirs()) {
listener.onBitmapSaveError(new ImageError("could not create parent directory")
.setErrorCode(ImageError.ERROR_PERMISSION_DENIED));
return;
}
try {
if (!imageFile.createNewFile()) {
listener.onBitmapSaveError(new ImageError("could not create file")
.setErrorCode(ImageError.ERROR_PERMISSION_DENIED));
return;
}
} catch (IOException e) {
listener.onBitmapSaveError(new ImageError(e).setErrorCode(ImageError.ERROR_GENERAL_EXCEPTION));
return;
}
new AsyncTask<Void, Void, Void>() {
private ImageError error;
@Override
protected Void doInBackground(Void... params) {
FileOutputStream fos = null;
try {
fos = new FileOutputStream(imageFile);
image.compress(format, 100, fos);
} catch (IOException e) {
error = new ImageError(e).setErrorCode(ImageError.ERROR_GENERAL_EXCEPTION);
this.cancel(true);
} finally {
if (fos != null) {
try {
fos.flush();
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return null;
}
@Override
protected void onCancelled() {
listener.onBitmapSaveError(error);
}
@Override
protected void onPostExecute(Void result) {
listener.onBitmapSaved();
}
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
public static Bitmap readFromDisk(@NonNull File imageFile) {
if (!imageFile.exists() || imageFile.isDirectory()) return null;
return BitmapFactory.decodeFile(imageFile.getAbsolutePath());
}
public interface OnImageReadListener {
void onImageRead(Bitmap bitmap);
void onReadFailed();
}
public static void readFromDiskAsync(@NonNull File imageFile, @NonNull final OnImageReadListener listener) {
new AsyncTask<String, Void, Bitmap>() {
@Override
protected Bitmap doInBackground(String... params) {
return BitmapFactory.decodeFile(params[0]);
}
@Override
protected void onPostExecute(Bitmap bitmap) {
if (bitmap != null)
listener.onImageRead(bitmap);
else
listener.onReadFailed();
}
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, imageFile.getAbsolutePath());
}
public static final class ImageError extends Throwable {
private int errorCode;
public static final int ERROR_GENERAL_EXCEPTION = -1;
public static final int ERROR_INVALID_FILE = 0;
public static final int ERROR_DECODE_FAILED = 1;
public static final int ERROR_FILE_EXISTS = 2;
public static final int ERROR_PERMISSION_DENIED = 3;
public static final int ERROR_IS_DIRECTORY = 4;
public ImageError(@NonNull String message) {
super(message);
}
public ImageError(@NonNull Throwable error) {
super(error.getMessage(), error.getCause());
this.setStackTrace(error.getStackTrace());
}
public ImageError setErrorCode(int code) {
this.errorCode = code;
return this;
}
public int getErrorCode() {
return errorCode;
}
}
}
Cursor
(in deronActivityResult()
Methode) abzurufen und dann einenBitmap
Pfad zu erstellen , der diesen Pfad verwendet. Und ja, Sie werden nicht davon abkommen, aFileOutputStream
und a zu verwenden,ByteArrayOutputStream
wenn Sie dieses Image auf SD speichern möchten.Ich bin gerade von der Lösung dieses Problems gekommen und möchte den vollständigen Code, der heruntergeladen werden kann, auf der SD-Karte speichern (und den Dateinamen ausblenden), die Bilder abrufen und schließlich prüfen, ob das Bild bereits vorhanden ist. Die URL stammt aus der Datenbank, sodass der Dateiname mit id eindeutig eindeutig verwendet werden kann.
Bilder zuerst herunterladen
Erstellen Sie dann eine Klasse zum Speichern und Abrufen der Dateien
Um auf die Bilder zuzugreifen, überprüfen Sie zuerst, ob sie bereits vorhanden sind, wenn nicht, und laden Sie sie dann herunter
quelle
AsyncTask
Vorteile (ordnungsgemäße Verwendung der Parametrisierung, parallele Ausführung ..). Einzelheiten entnehmen Sie bitte meinen Beispielen unten. PS . Für diejenigen, die vielleicht denken, ich hätte dies geschrieben, um meinen eigenen Code aus irgendeinem Grund zu bewerben: Nein, ich weise nur auf die Probleme hin, die ich im gegebenen Beispiel sehen kann.download
Methode genauer an , insbesondere diedoInBackground
Methode der von mir verwendeten Aufgabe. EinIOException
würde imcatch (Throwable e)
Block landen , was dazu führen würde , dass ein RückrufImageError
zurückgegeben und deronError()
Rückruf ausgelöst wird. DasImageError
Objekt enthält die ursprüngliche Stapelverfolgung und die Ursache des aufgetretenenException
Warum brauchen Sie wirklich Ihren eigenen Code, um ihn herunterzuladen? Wie wäre es, wenn Sie Ihre URI einfach an den Download-Manager übergeben?
quelle
es könnte dir helfen ..
quelle
Ich habe eine einfache Lösung, die perfekt funktioniert. Der Code gehört nicht mir, ich habe ihn auf diesem Link gefunden . Hier sind die folgenden Schritte:
1. Bevor Sie das Bild herunterladen, schreiben Sie eine Methode zum Speichern der Bitmap in eine Bilddatei im internen Speicher von Android. Es benötigt einen Kontext, besser ist es, den Pass im Anwendungskontext von getApplicationContext () zu verwenden. Diese Methode kann in Ihre Activity-Klasse oder andere Util-Klassen kopiert werden.
2. Jetzt haben wir eine Methode zum Speichern der Bitmap in einer Bilddatei in andorid. Schreiben wir die AsyncTask zum Herunterladen von Bildern per URL. Diese private Klasse muss als Unterklasse in Ihre Aktivitätsklasse eingefügt werden. Nachdem das Bild heruntergeladen wurde, ruft es in der onPostExecute-Methode die oben definierte saveImage-Methode auf, um das Bild zu speichern. Beachten Sie, dass der Bildname als "my_image.png" fest codiert ist.
3. Die AsyncTask zum Herunterladen des Bildes ist definiert, aber wir müssen sie ausführen, um diese AsyncTask auszuführen. Schreiben Sie dazu diese Zeile in Ihre onCreate-Methode in Ihrer Activity-Klasse oder in eine onClick-Methode einer Schaltfläche oder an andere Stellen, die Sie für richtig halten.
Das Bild sollte in /data/data/your.app.packagename/files/my_image.jpeg gespeichert werden. Überprüfen Sie diesen Beitrag, um von Ihrem Gerät aus auf dieses Verzeichnis zuzugreifen.
IMO das löst das Problem! Wenn Sie weitere Schritte wie das Laden des Bildes wünschen, können Sie die folgenden zusätzlichen Schritte ausführen:
4. Nachdem das Image heruntergeladen wurde, benötigen wir eine Möglichkeit, die Image-Bitmap aus dem internen Speicher zu laden, damit wir sie verwenden können. Schreiben wir die Methode zum Laden der Bild-Bitmap. Diese Methode verwendet zwei Parameter, einen Kontext und einen Bilddateinamen. Ohne den vollständigen Pfad sucht context.openFileInput (imageName) die Datei im Speicherverzeichnis, wenn dieser Dateiname in der obigen saveImage-Methode gespeichert wurde.
5. Jetzt haben wir alles, was wir zum Einstellen des Bildes einer ImageView oder anderer Ansichten benötigen, für die Sie das Bild verwenden möchten. Wenn wir das Bild speichern, haben wir den Bildnamen als "my_image.jpeg" fest codiert. Jetzt können wir diesen Bildnamen an die obige loadImageBitmap-Methode übergeben, um die Bitmap abzurufen und auf eine ImageView festzulegen.
6. Um den vollständigen Pfad des Bildes anhand des Bildnamens zu erhalten.
7. Überprüfen Sie, ob die Bilddatei vorhanden ist.
Datei Datei =
So löschen Sie die Bilddatei.
Datei file = getApplicationContext (). GetFileStreamPath ("my_image.jpeg"); if (file.delete ()) Log.d ("file", "my_image.jpeg gelöscht!");
quelle
Dieser Code läuft perfekt in meinem Projekt
quelle
Versuche dies
quelle
AUSGABE
Stellen Sie sicher, dass Sie die Berechtigung zum Schreiben von Daten in den Speicher hinzugefügt haben
quelle
Vergessen Sie nicht, wie Google mitteilt, im Manifest auch lesbare externe Speicher hinzuzufügen:
Quelle: http://developer.android.com/training/basics/data-storage/files.html#GetWritePermission
quelle