¿Cómo se descarga y guarda una imagen de una URL determinada en Android?
¿Cómo se descarga y guarda una imagen de una URL determinada en Android?
última actualización importante: 31 de marzo de 2016
TL; DR también conocido como deja de hablar, solo dame el código !!
Vaya al final de esta publicación, copie la
BasicImageDownloader
(versión javadoc aquí ) en su proyecto, implemente laOnImageLoaderListener
interfaz y listo.Nota : aunque
BasicImageDownloader
maneja posibles errores y evitará que su aplicación se bloquee en caso de que algo salga mal, no realizará ningún procesamiento posterior (por ejemplo, reducción de tamaño) en la descargaBitmaps
.
Dado que esta publicación ha recibido mucha atención, he decidido modificarla por completo para evitar que la gente use tecnologías obsoletas, malas prácticas de programación o simplemente hacer cosas tontas, como buscar "hacks" para ejecutar la red en el hilo principal o aceptar todos los certificados SSL.
Creé un proyecto de demostración llamado "Image Downloader" que demuestra cómo descargar (y guardar) una imagen usando mi propia implementación de descarga, la función de Android integrada y DownloadManager
algunas bibliotecas populares de código abierto. Puede ver el código fuente completo o descargar el proyecto en GitHub .
Nota : Todavía no he ajustado la gestión de permisos para SDK 23+ (Marshmallow), por lo que el proyecto se dirige al SDK 22 (Lollipop).
En mi conclusión al final de esta publicación, compartiré mi humilde opinión sobre el caso de uso adecuado para cada forma particular de descarga de imágenes que he mencionado.
Comencemos con una implementación propia (puedes encontrar el código al final de la publicación). En primer lugar, este es un ImageDownloader básico y eso es todo. Todo lo que hace es conectarse a la URL dada, leer los datos e intentar decodificarlos como un Bitmap
, activando las OnImageLoaderListener
devoluciones de llamada de la interfaz cuando sea apropiado. La ventaja de este enfoque: es simple y tiene una visión general clara de lo que está sucediendo. Un buen camino a seguir si todo lo que necesita es descargar / mostrar y guardar algunas imágenes, mientras que no le importa mantener una memoria / caché de disco.
Nota: en el caso de imágenes grandes, es posible que deba reducirlas .
-
Android DownloadManager es una forma de permitir que el sistema maneje la descarga por usted. De hecho, es capaz de descargar cualquier tipo de archivo, no solo imágenes. Puede permitir que la descarga se realice de forma silenciosa e invisible para el usuario, o puede permitir que el usuario vea la descarga en el área de notificación. También puede registrarse BroadcastReceiver
para recibir una notificación después de que se complete la descarga. La configuración es bastante sencilla, consulte el proyecto vinculado para obtener un código de muestra.
DownloadManager
Por lo general, usar el archivo no es una buena idea si también desea mostrar la imagen, ya que necesitaría leer y decodificar el archivo guardado en lugar de simplemente configurar el descargado Bitmap
en un ImageView
. El DownloadManager
también no proporciona ninguna API para que la aplicación para realizar un seguimiento del progreso de la descarga.
-
Ahora, la introducción de las grandes cosas: las bibliotecas. Pueden hacer mucho más que descargar y mostrar imágenes, incluyendo: crear y administrar la memoria / caché del disco, cambiar el tamaño de las imágenes, transformarlas y más.
Comenzaré con Volley , una poderosa biblioteca creada por Google y cubierta por la documentación oficial. Si bien es una biblioteca de redes de uso general que no se especializa en imágenes, Volley presenta una API bastante poderosa para administrar imágenes.
Deberá implementar una clase Singleton para administrar las solicitudes de Volley y estará listo para comenzar.
Es posible que desee reemplazar su ImageView
con Volley NetworkImageView
, por lo que la descarga básicamente se convierte en una sola línea:
((NetworkImageView) findViewById(R.id.myNIV)).setImageUrl(url, MySingleton.getInstance(this).getImageLoader());
Si necesita más control, así es como se ve al crear un ImageRequest
con Volley:
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
}
});
Vale la pena mencionar que Volley presenta un excelente mecanismo de manejo de errores al proporcionar la VolleyError
clase que lo ayuda a determinar la causa exacta de un error. Si su aplicación hace muchas redes y la administración de imágenes no es su propósito principal, entonces Volley es la opción perfecta para usted.
-
Picasso de Square es una biblioteca muy conocida que hará todo el proceso de carga de imágenes por usted. Mostrar una imagen con Picasso es tan simple como:
Picasso.with(myContext)
.load(url)
.into(myImageView);
De forma predeterminada, Picasso administra el caché de memoria / disco, por lo que no debe preocuparse por eso. Para obtener más control, puede implementar la Target
interfaz y usarla para cargar su imagen; esto proporcionará devoluciones de llamada similares al ejemplo de Volley. Consulte el proyecto de demostración para ver ejemplos.
Picasso también le permite aplicar transformaciones a la imagen descargada e incluso hay otras bibliotecas alrededor que amplían esas API. También funciona muy bien en RecyclerView
/ ListView
/ GridView
.
-
Universal Image Loader es otra biblioteca muy popular que sirve para la gestión de imágenes. Utiliza el suyo propio ImageLoader
que (una vez inicializado) tiene una instancia global que se puede usar para descargar imágenes en una sola línea de código:
ImageLoader.getInstance().displayImage(url, myImageView);
Si desea realizar un seguimiento del progreso de la descarga o acceder a la descargada 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
}
});
El opts
argumento en este ejemplo es un DisplayImageOptions
objeto. Consulte el proyecto de demostración para obtener más información.
Al igual que Volley, UIL proporciona la FailReason
clase que le permite verificar qué salió mal en caso de falla de descarga. De forma predeterminada, UIL mantiene una memoria / caché de disco si no le indica explícitamente que no lo haga.
Nota : el autor ha mencionado que ya no mantendrá el proyecto a partir del 27 de noviembre de 2015. Pero dado que hay muchos colaboradores, podemos esperar que Universal Image Loader continúe.
-
Fresco de Facebook es la biblioteca más nueva y (IMO) la más avanzada que lleva la administración de imágenes a un nuevo nivel: desde mantenerse Bitmaps
alejado del montón de Java (antes de Lollipop) hasta admitir formatos animados y transmisión progresiva de JPEG .
Para obtener más información sobre las ideas y técnicas detrás de Fresco, consulte esta publicación .
El uso básico es bastante simple. Tenga en cuenta que deberá llamar Fresco.initialize(Context);
solo una vez, preferiblemente en la Application
clase. Inicializar Fresco más de una vez puede provocar un comportamiento impredecible y errores OOM.
Fresco usa Drawee
s para mostrar imágenes, puede pensar en ellas como ImageView
s:
<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" />
Como puede ver, muchas cosas (incluidas las opciones de transformación) ya están definidas en XML, por lo que todo lo que necesita hacer para mostrar una imagen es una sola línea:
mDrawee.setImageURI(Uri.parse(url));
Fresco proporciona una API de personalización extendida, que, en determinadas circunstancias, puede ser bastante compleja y requiere que el usuario lea los documentos con atención (sí, a veces necesita RTFM).
He incluido ejemplos de imágenes animadas y JPEG progresivos en el proyecto de muestra.
Tenga en cuenta que el siguiente texto refleja mi opinión personal y no debe tomarse como un postulado.
Recycler-/Grid-/ListView
y no necesita un montón de imágenes para estar listas para mostrar, BasicImageDownloader debería satisfacer sus necesidades.JSON
datos, funcione con imágenes, pero ese no sea el propósito principal de la aplicación, vaya con Volley .En caso de que te lo hayas perdido, el enlace de Github para el proyecto de demostración.
Y aqui esta el 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
(en elonActivityResult()
método) y luego crear unaBitmap
usando esa ruta. Y sí, no dejará de usar ayFileOutputStream
aByteArrayOutputStream
si desea guardar esta imagen en SD.Acabo de llegar de resolver este problema y me gustaría compartir el código completo que se puede descargar, guardar en la tarjeta SD (y ocultar el nombre del archivo) y recuperar las imágenes y finalmente verifica si la imagen ya está allí. La URL proviene de la base de datos, por lo que el nombre del archivo puede ser único fácilmente usando id.
primeras imágenes de descarga
Luego crea una clase para guardar y recuperar los archivos
Luego, para acceder a las imágenes, primero verifique si ya está allí, si no, luego descargue
fuente
AsyncTask
las ventajas (uso adecuado de la parametrización, ejecución en paralelo ...). Consulte mis ejemplos a continuación para obtener más detalles. PS . para aquellos que puedan pensar que escribí esto para promover mi propio código por cualquier motivo: no, solo estoy señalando los problemas que puedo ver en el ejemplo dado.download
método, particularmente eldoInBackground
método de la tarea que utilizo. AnIOException
aterrizaría en elcatch (Throwable e)
bloque, lo que provocaría queImageError
se devolviera yonError()
se activara la devolución de llamada. ElImageError
objeto contendrá el rastro de pila original y la causa de lo ocurridoException
¿Por qué realmente necesitas tu propio código para descargarlo? ¿Qué tal simplemente pasar su URI al administrador de descargas?
fuente
podría ayudarte ...
fuente
Tengo una solución simple que está funcionando perfectamente. El código no es mío, lo encontré en este enlace . Estos son los pasos a seguir:
1. Antes de descargar la imagen, escribamos un método para guardar el mapa de bits en un archivo de imagen en el almacenamiento interno de Android. Necesita un contexto, es mejor usar el pase en el contexto de la aplicación mediante getApplicationContext (). Este método se puede volcar en su clase de actividad u otras clases de utilidad.
2. Ahora que tenemos un método para guardar el mapa de bits en un archivo de imagen en andorid, vamos a escribir AsyncTask para descargar imágenes por URL. Esta clase privada debe colocarse en su clase de actividad como una subclase. Después de descargar la imagen, en el método onPostExecute, llama al método saveImage definido anteriormente para guardar la imagen. Tenga en cuenta que el nombre de la imagen está codificado como "my_image.png".
3. La AsyncTask para descargar la imagen está definida, pero necesitamos ejecutarla para ejecutar esa AsyncTask. Para hacerlo, escriba esta línea en su método onCreate en su clase Activity, o en un método onClick de un botón u otros lugares que considere oportunos.
La imagen debe guardarse en /data/data/your.app.packagename/files/my_image.jpeg, consulte esta publicación para acceder a este directorio desde su dispositivo.
En mi opinión, esto resuelve el problema. Si desea realizar más pasos, como cargar la imagen, puede seguir estos pasos adicionales:
4. Después de descargar la imagen, necesitamos una forma de cargar el mapa de bits de la imagen desde el almacenamiento interno, para poder usarlo. Escribamos el método para cargar el mapa de bits de la imagen. Este método toma dos parámetros, un contexto y un nombre de archivo de imagen, sin la ruta completa, el context.openFileInput (imageName) buscará el archivo en el directorio de guardado cuando este nombre de archivo se guardó en el método saveImage anterior.
5. Ahora tenemos todo lo que necesitamos para configurar la imagen de un ImageView o cualquier otra Vista en la que le guste usar la imagen. Cuando guardamos la imagen, codificamos el nombre de la imagen como "my_image.jpeg", ahora podemos pasar este nombre de imagen al método anterior loadImageBitmap para obtener el mapa de bits y configurarlo como ImageView.
6. Para obtener la ruta completa de la imagen por nombre de imagen.
7. Compruebe si existe el archivo de imagen.
Archivo archivo =
Para eliminar el archivo de imagen.
Archivo archivo = getApplicationContext (). GetFileStreamPath ("my_image.jpeg"); if (file.delete ()) Log.d ("file", "my_image.jpeg eliminado!");
fuente
este código se ejecuta perfectamente en mi proyecto
fuente
Prueba esto
fuente
SALIDA
Asegúrese de haber agregado permiso para escribir datos en la memoria
fuente
Como dice Google, por ahora, no olvide agregar también legible en almacenamiento externo en el manifiesto:
Fuente: http://developer.android.com/training/basics/data-storage/files.html#GetWritePermission
fuente