Saltar al contenido

Cómo descargar y guardar una imagen en Android

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 el OnImageLoaderListener 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 descargado Bitmaps.


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 Drawees para mostrar imágenes, puede pensar en ellas como ImageViews:

    

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 
    private String requestUrl, imagename_;
    private ImageView view;
    private Bitmap bitmap ; 
      private FileOutputStream fos;
    private GetImages(String requestUrl, ImageView view, String _imagename_) 
        this.requestUrl = requestUrl;
        this.view = view;
        this.imagename_ = _imagename_ ;
    

    @Override
    protected Object doInBackground(Object... objects) 
        try 
            URL url = new URL(requestUrl);
            URLConnection conn = url.openConnection();
            bitmap = BitmapFactory.decodeStream(conn.getInputStream());
         catch (Exception ex) 
        
        return null;
    

    @Override
    protected void onPostExecute(Object o)  
        if(!ImageStorage.checkifImageExists(imagename_))
         
                view.setImageBitmap(bitmap);
                ImageStorage.saveToSdCard(bitmap, imagename_); 
              
          
   

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.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *