Por fin después de tanto trabajar hemos dado con el arreglo de este enigma que algunos de nuestros lectores de nuestro sitio web tienen. Si quieres aportar algún detalle no dejes de compartir tu comentario.
Solución:
Editar a partir del 30 de diciembre de 2015: la guía definitiva para la descarga de imágenes
ú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 el
BasicImageDownloader
(versión javadoc aquí) en su proyecto, implemente elOnImageLoaderListener
interfaz y listo.Nota: aunque el
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 el archivo descargadoBitmaps
.
Dado que esta publicación ha recibido mucha atención, he decidido reelaborarla 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 descarga de Android DownloadManager
así como 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 este post 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, esta es una BásicoImageDownloader y eso es todo. Todo lo que hace es conectarse a la URL dada, leer los datos e intentar decodificarlos como un Bitmap
, provocando el OnImageLoaderListener
devoluciones de llamada de 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 registrar un 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.
Utilizando el DownloadManager
generalmente 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 una ImageView
. los DownloadManager
tampoco proporciona ninguna API para que su aplicación realice 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’s 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 crear un ImageRequest
con Volley:
ImageRequest imgRequest = new ImageRequest(url, new Response.Listener()
@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 le 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 conocida que hará todo el proceso de carga de imágenes por usted. Mostrar una imagen usando 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 tiene que preocuparse por eso. Para un mayor control, puede implementar el Target
interfaz y úselo para cargar su imagen en; 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. Usa su 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 descarga 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
);
los opts
argumento en este ejemplo es un DisplayImageOptions
objeto. Consulte el proyecto de demostración para obtener más información.
Similar a Volley, UIL proporciona el FailReason
clase que le permite comprobar qué salió mal en caso de error 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 (en mi opinión) la más avanzada que lleva la gestión de imágenes a un nuevo nivel: de mantener Bitmaps
fuera del montón de Java (antes de Lollipop) para admitir formatos animados y transmisión JPEG progresiva.
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, preferible en el Application
clase. Inicializar Fresco más de una vez puede provocar un comportamiento impredecible y errores OOM.
Usos frescos Drawee
s para mostrar imágenes, puede pensar en ellas como ImageView
s:
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 necesitas RTFM).
He incluido ejemplos de imágenes animadas y JPEG progresivos en el proyecto de muestra.
Conclusión: “He aprendido acerca de las grandes cosas, ¿qué debo usar ahora?”
Tenga en cuenta que el siguiente texto refleja mi opinión personal y no debe tomarse como un postulado.
- Si solo necesita descargar / guardar / mostrar algunas imágenes, no planee usarlas en un
Recycler-/Grid-/ListView
y no necesita un montón de imágenes para estar listas para mostrarse, el BasicImageDownloader debe adaptarse a sus necesidades. - Si su aplicación guarda imágenes (u otros archivos) como resultado de un usuario o una acción automatizada y no necesita que las imágenes se muestren con frecuencia, use la aplicación Android Gestor de descargas.
- En caso de que su aplicación trabaje mucho en red, transmite / recibe
JSON
datos, funciona con imágenes, pero esos no son el propósito principal de la aplicación, vaya con Voleo. - Su aplicación está enfocada en imágenes / medios, le gustaría aplicar algunas transformaciones a las imágenes y no quiere molestarse con una API compleja: use Picasso (Nota: no proporciona ninguna API para rastrear el estado de descarga intermedio) o Cargador de imágenes universal
- Si su aplicación se trata de imágenes, necesita funciones avanzadas como mostrar formatos animados y está listo para leer los documentos, vaya con Fresco.
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 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()
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()
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) 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()
@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;
Acabo de llegar de resolver este problema y me gustaría compartir el código completo que se puede descargar, guardar en la sdcard (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.
primera descarga de imágenes
private class GetImages extends AsyncTask
Luego crea una clase para guardar y recuperar los archivos.
public class ImageStorage
public static String saveToSdCard(Bitmap bitmap, String filename)
String stored = null;
File sdcard = Environment.getExternalStorageDirectory() ;
File folder = new File(sdcard.getAbsoluteFile(), ".your_specific_directory");//the dot makes this directory hidden to the user
folder.mkdir();
File file = new File(folder.getAbsoluteFile(), filename + ".jpg") ;
if (file.exists())
return stored ;
try
FileOutputStream out = new FileOutputStream(file);
bitmap.compress(Bitmap.CompressFormat.JPEG, 90, out);
out.flush();
out.close();
stored = "success";
catch (Exception e)
e.printStackTrace();
return stored;
public static File getImage(String imagename)
File mediaImage = null;
try
String root = Environment.getExternalStorageDirectory().toString();
File myDir = new File(root);
if (!myDir.exists())
return null;
mediaImage = new File(myDir.getPath() + "/.your_specific_directory/"+imagename);
catch (Exception e)
// TODO Auto-generated catch block
e.printStackTrace();
return mediaImage;
public static boolean checkifImageExists(String imagename)
Bitmap b = null ;
File file = ImageStorage.getImage("/"+imagename+".jpg");
String path = file.getAbsolutePath();
if (path != null)
b = BitmapFactory.decodeFile(path);
if(b == null
Luego, para acceder a las imágenes, primero verifique si ya está allí, si no, luego descargue
if(ImageStorage.checkifImageExists(imagename))
File file = ImageStorage.getImage("/"+imagename+".jpg");
String path = file.getAbsolutePath();
if (path != null)
b = BitmapFactory.decodeFile(path);
imageView.setImageBitmap(b);
else
new GetImages(imgurl, imageView, imagename).execute() ;
¿Por qué realmente necesitas tu propio código para descargarlo? ¿Qué tal simplemente pasar su URI al administrador de descargas?
public void downloadFile(String uRl)
File direct = new File(Environment.getExternalStorageDirectory()
+ "/AnhsirkDasarp");
if (!direct.exists())
direct.mkdirs();
DownloadManager mgr = (DownloadManager) getActivity().getSystemService(Context.DOWNLOAD_SERVICE);
Uri downloadUri = Uri.parse(uRl);
DownloadManager.Request request = new DownloadManager.Request(
downloadUri);
request.setAllowedNetworkTypes(
DownloadManager.Request.NETWORK_WIFI
Puedes proteger nuestra investigación mostrando un comentario y dejando una valoración te damos las gracias.