Cache de imagem do Android

141

Como posso armazenar em cache imagens depois que elas são baixadas da Web?

d-man
fonte

Respostas:

177

E agora a dica: use o cache do sistema.

URL url = new URL(strUrl);
URLConnection connection = url.openConnection();
connection.setUseCaches(true);
Object response = connection.getContent();
if (response instanceof Bitmap) {
  Bitmap bitmap = (Bitmap)response;
} 

Fornece memória e cache flash-rom, compartilhados com o navegador.

grr. Eu gostaria que alguém tivesse me dito isso antes de escrever meu próprio gerenciador de cache.

Edrowland
fonte
1
Uau, essa foi uma maneira incrivelmente elegante de fazer isso, muito obrigado. Não é de forma alguma mais lenta que meu próprio gerenciador de cache simples e agora não preciso fazer tarefas domésticas em uma pasta de cartão SD.
Kevin Leia
11
connection.getContent()sempre retorna um InputStream para mim, o que estou fazendo de errado?
Tyler Collier
3
Se eu pudesse agora também definir uma data de expiração do conteúdo para o cache de minha vida seria muito mais fácil :)
Janusz
11
@ Scienceprodigy não faço ideia do que é esse BitmapLoader, certamente não está em nenhuma biblioteca padrão do Android que eu conheço, mas pelo menos me levou na direção certa. Bitmap response = BitmapFactory.decodeStream((InputStream)connection.getContent());
Stephen Fuhry
6
Certifique-se de ver a resposta de Joe abaixo sobre os passos extras que você precisa fazer para obter o trabalho de cache
Keith
65

Em relação à connection.setUseCachessolução elegante acima: infelizmente, ela não funcionará sem um esforço adicional. Você precisará instalar um ResponseCacheusando ResponseCache.setDefault. Caso contrário, HttpURLConnectionsilenciosamente ignorará o setUseCaches(true)bit.

Veja os comentários no topo de FileResponseCache.javapara detalhes:

http://libs-for-android.googlecode.com/svn/reference/com/google/android/filecache/FileResponseCache.html

(Eu publicaria isso em um comentário, mas aparentemente não tenho karma SO suficiente.)

Joe
fonte
Aqui está o arquivo
Telémako 22/11/2012
2
Quando você usa um HttpResponseCache, você pode encontrar o HttpResponseCache.getHitCount()0. retornado. Não tenho certeza, mas acho que é porque o servidor da web que você está solicitando não usa cabeçalhos de cache nesse caso. Para fazer o cache funcionar de qualquer maneira, use connection.addRequestProperty("Cache-Control", "max-stale=" + MAX_STALE_CACHE);.
Almer
1
O link de pesquisa de códigos do Google está morto (de novo?), Atualize o link.
Felix D.
Além disso, não tenho certeza se esse comportamento foi corrigido ou não. Por algum motivo, retornar 304 do servidor travaria o HUC ao usar o .getContent()método, porque as respostas 304 não têm um corpo de resposta associado pelo padrão RFC.
TheRealChx101 16/07/19
27

Converta-os em bitmaps e, em seguida, armazene-os em uma coleção (HashMap, lista etc.) ou escreva-os no cartão SD.

Ao armazená-los no espaço do aplicativo usando a primeira abordagem, convém agrupá-los em torno de um java.lang.ref.SoftReference especificamente se seus números forem grandes (para que eles sejam coletados durante a crise). Isso poderia resultar em uma recarga.

HashMap<String,SoftReference<Bitmap>> imageCache =
        new HashMap<String,SoftReference<Bitmap>>();

escrevê-los no cartão SD não exigirá uma recarga; apenas uma permissão do usuário.

Samuh
fonte
como podemos escrever imagem na memória sd ou telefone?
d-man
Para salvar imagens no cartão SD: Você pode comprometer a leitura do Image Streams do servidor remoto na memória usando operações normais de E / S de arquivo ou se tiver convertido suas imagens em objetos Bitmap, poderá usar o método Bitmap.compress ().
Samuh 22/12/09
@ d-man, sugiro gravar o disco primeiro e, em seguida, obter uma Urireferência de caminho para a qual você possa passar ImageViewe outras visualizações personalizadas. Porque cada vez que compressvocê perde qualidade. Claro que isso é verdade apenas para algoritmos com perdas. Esse método também permite armazenar um hash do arquivo e usá-lo na próxima vez que você solicitar o arquivo do servidor através de If-None-Matche ETagcabeçalhos.
TheRealChx101 16/07/19
@ TheRealChx101, você poderia ajudar a entender o que você quer dizer da próxima vez que solicitar o arquivo do servidor através dos cabeçalhos If-None-Match e ETag , basicamente estou procurando uma abordagem de solução na qual a imagem deve permanecer para usar o cache local do formulário para definir period OU se isso não puder ser alcançado, sempre que o conteúdo do URL for alterado, ele deve refletir no aplicativo o mais recente e armazená-lo em cache.
CoDe
@CoDe Visite este link agora, android.jlelse.eu/…
TheRealChx101
27

Use LruCachepara armazenar em cache imagens com eficiência. Você pode ler sobre LruCacheno site do desenvolvedor do Android

Eu usei abaixo solução para imagens baixar e cache no android. Você pode seguir as etapas abaixo:

PASSO 1: torne a classe nomeada ImagesCache. Eu useiSingleton object for this class

import android.graphics.Bitmap;
import android.support.v4.util.LruCache;

public class ImagesCache 
{
    private  LruCache<String, Bitmap> imagesWarehouse;

    private static ImagesCache cache;

    public static ImagesCache getInstance()
    {
        if(cache == null)
        {
            cache = new ImagesCache();
        }

        return cache;
    }

    public void initializeCache()
    {
        final int maxMemory = (int) (Runtime.getRuntime().maxMemory() /1024);

        final int cacheSize = maxMemory / 8;

        System.out.println("cache size = "+cacheSize);

        imagesWarehouse = new LruCache<String, Bitmap>(cacheSize)
                {
                    protected int sizeOf(String key, Bitmap value) 
                    {
                        // The cache size will be measured in kilobytes rather than number of items.

                        int bitmapByteCount = value.getRowBytes() * value.getHeight();

                        return bitmapByteCount / 1024;
                    }
                };
    }

    public void addImageToWarehouse(String key, Bitmap value)
    {       
        if(imagesWarehouse != null && imagesWarehouse.get(key) == null)
        {
            imagesWarehouse.put(key, value);
        }
    }

    public Bitmap getImageFromWarehouse(String key)
    {
        if(key != null)
        {
            return imagesWarehouse.get(key);
        }
        else
        {
            return null;
        }
    }

    public void removeImageFromWarehouse(String key)
    {
        imagesWarehouse.remove(key);
    }

    public void clearCache()
    {
        if(imagesWarehouse != null)
        {
            imagesWarehouse.evictAll();
        }       
    }

}

PASSO 2:

faça outra classe chamada DownloadImageTask que é usada se o bitmap não estiver disponível no cache, ele fará o download aqui:

public class DownloadImageTask extends AsyncTask<String, Void, Bitmap>
{   
    private int inSampleSize = 0;

    private String imageUrl;

    private BaseAdapter adapter;

    private ImagesCache cache;

    private int desiredWidth, desiredHeight;

    private Bitmap image = null;

    private ImageView ivImageView;

    public DownloadImageTask(BaseAdapter adapter, int desiredWidth, int desiredHeight) 
    {
        this.adapter = adapter;

        this.cache = ImagesCache.getInstance();

        this.desiredWidth = desiredWidth;

        this.desiredHeight = desiredHeight;
    }

    public DownloadImageTask(ImagesCache cache, ImageView ivImageView, int desireWidth, int desireHeight)
    {
        this.cache = cache;

        this.ivImageView = ivImageView;

        this.desiredHeight = desireHeight;

        this.desiredWidth = desireWidth;
    }

    @Override
    protected Bitmap doInBackground(String... params) 
    {
        imageUrl = params[0];

        return getImage(imageUrl);
    }

    @Override
    protected void onPostExecute(Bitmap result) 
    {
        super.onPostExecute(result);

        if(result != null)
        {
            cache.addImageToWarehouse(imageUrl, result);

            if(ivImageView != null)
            {
                ivImageView.setImageBitmap(result);
            }
            else if(adapter != null)
            {
                adapter.notifyDataSetChanged();
            }
        }
    }

    private Bitmap getImage(String imageUrl)
    {   
        if(cache.getImageFromWarehouse(imageUrl) == null)
        {
            BitmapFactory.Options options = new BitmapFactory.Options();

            options.inJustDecodeBounds = true;

            options.inSampleSize = inSampleSize;

            try
            {
                URL url = new URL(imageUrl);

                HttpURLConnection connection = (HttpURLConnection)url.openConnection();

                InputStream stream = connection.getInputStream();

                image = BitmapFactory.decodeStream(stream, null, options);

                int imageWidth = options.outWidth;

                int imageHeight = options.outHeight;

                if(imageWidth > desiredWidth || imageHeight > desiredHeight)
                {   
                    System.out.println("imageWidth:"+imageWidth+", imageHeight:"+imageHeight);

                    inSampleSize = inSampleSize + 2;

                    getImage(imageUrl);
                }
                else
                {   
                    options.inJustDecodeBounds = false;

                    connection = (HttpURLConnection)url.openConnection();

                    stream = connection.getInputStream();

                    image = BitmapFactory.decodeStream(stream, null, options);

                    return image;
                }
            }

            catch(Exception e)
            {
                Log.e("getImage", e.toString());
            }
        }

        return image;
    }

PASSO 3: Uso do seu ActivityouAdapter

Nota: Se você deseja carregar a imagem do URL da ActivityClasse. Use o segundo Construtor de DownloadImageTask, mas se você deseja exibir uma imagem, Adapteruse o primeiro Construtor de DownloadImageTask(por exemplo, você tem uma imagem ListViewe está definindo a imagem em 'Adaptador')

USO DA ATIVIDADE:

ImageView imv = (ImageView) findViewById(R.id.imageView);
ImagesCache cache = ImagesCache.getInstance();//Singleton instance handled in ImagesCache class.
cache.initializeCache();

String img = "your_image_url_here";

Bitmap bm = cache.getImageFromWarehouse(img);

if(bm != null)
{
  imv.setImageBitmap(bm);
}
else
{
  imv.setImageBitmap(null);

  DownloadImageTask imgTask = new DownloadImageTask(cache, imv, 300, 300);//Since you are using it from `Activity` call second Constructor.

  imgTask.execute(img);
}

USO DO ADAPTADOR:

ImageView imv = (ImageView) rowView.findViewById(R.id.imageView);
ImagesCache cache = ImagesCache.getInstance();
cache.initializeCache();

String img = "your_image_url_here";

Bitmap bm = cache.getImageFromWarehouse(img);

if(bm != null)
{
  imv.setImageBitmap(bm);
}
else
{
  imv.setImageBitmap(null);

  DownloadImageTask imgTask = new DownloadImageTask(this, 300, 300);//Since you are using it from `Adapter` call first Constructor.

  imgTask.execute(img);
}

Nota:

cache.initializeCache()você pode usar esta declaração na primeira atividade do seu aplicativo. Depois de inicializar o cache, você nunca precisará inicializá-lo todas as vezes se estiver usando uma ImagesCacheinstância.

Eu nunca sou bom em explicar as coisas, mas espero que isso ajude os iniciantes a como usar o cache LruCachee seu uso :)

EDITAR:

Hoje em dia, existem bibliotecas muito famosas conhecidas como Picassoe Glideque podem ser usadas para carregar imagens com muita eficiência no aplicativo Android. Experimente esta biblioteca Picasso muito simples e útil para Android e Glide For Android . Você não precisa se preocupar com imagens de cache.

O Picasso permite o carregamento de imagens sem problemas no seu aplicativo - geralmente em uma linha de código!

O Glide, assim como o Picasso, pode carregar e exibir imagens de várias fontes, além de cuidar do cache e manter um baixo impacto na memória ao manipular imagens. Ele foi usado por aplicativos oficiais do Google (como o Google I / O 2015) e é tão popular quanto o Picasso. Nesta série, exploraremos as diferenças e vantagens do Glide sobre Picasso.

Você também pode visitar o blog para saber a diferença entre Glide e Picasso

Zubair Ahmed
fonte
3
Excelente resposta e explicação! Eu acho que essa é a melhor solução, pois funciona quando está offline e usa o Android LruCache. Descobri que a solução da edrowland não funcionava no modo avião, mesmo com a adição de Joe, que exigia mais esforço para se integrar. Aliás, parece que o Android ou a rede fornecem uma quantidade significativa de cache, mesmo que você não faça nada extra. (Um pequeno detalhe: para o uso da amostra getImageFromWareHouse, o 'H' deve estar em minúsculas para corresponder.) Obrigado!
Edwin Evans
1
boa explicação :)
XtreemDeveloper 10/09
Você poderia explicar o método getImage (), em particular o que ele faz com o tamanho da imagem e como isso acontece. Eu não entendo, por exemplo, por que você chama a função dentro de si novamente e como ela funciona.
Greyshack
1
Voto positivo por aquilo if(cache == null)que resolveu meu problema! :)
MR. Garcia
1
Veja também minha resposta editada no final. Eu mencionei sobre bibliotecas famosas usadas pela maioria dos desenvolvedores hoje em dia. Tente aqueles Picasso: square.github.io/picasso e Glide: futurestud.io/blog/glide-getting-started
Zubair Ahmed
18

Para baixar uma imagem e salvar no cartão de memória, você pode fazer o seguinte.

//First create a new URL object 
URL url = new URL("http://www.google.co.uk/logos/holiday09_2.gif")

//Next create a file, the example below will save to the SDCARD using JPEG format
File file = new File("/sdcard/example.jpg");

//Next create a Bitmap object and download the image to bitmap
Bitmap bitmap = BitmapFactory.decodeStream(url.openStream());

//Finally compress the bitmap, saving to the file previously created
bitmap.compress(CompressFormat.JPEG, 100, new FileOutputStream(file));

Não se esqueça de adicionar a permissão da Internet ao seu manifesto:

<uses-permission android:name="android.permission.INTERNET" />
Ljdawson
fonte
10
Por que você está decodificando o JPEG e depois recodificando-o? É melhor você baixar o URL para uma matriz de bytes e depois usá-la para criar seu Bitmap e gravar em um arquivo. Sempre que você decodifica e recodifica um JPEG, a qualidade da imagem piora.
CommonsWare
2
Ponto justo, foi mais por velocidade do que qualquer coisa. Embora, se salvo como uma matriz de bytes e o arquivo de origem não fosse um JPEG, o arquivo não precisaria ser convertido de qualquer maneira? "decodeByteArray" do SDK Retorna "O bitmap decodificado, ou nulo, se os dados da imagem não puderem ser decodificados", então isso me faz pensar que está sempre decodificando os dados da imagem, para que isso não precise ser recodificado novamente?
22710 Ljdawson
Falando em eficiência, não seria eficiente se, em vez de passar o FileOutputStream, passássemos o BufferedOutputStream?
Samuh 23/12/09
1
Eu não sugiro armazenar imagens em cache no seu cartão SD. depois que o aplicativo é desinstalado, as imagens não são removidas, fazendo com que o cartão sd seja preenchido com lixo inútil. é preferível salvar imagens no diretório de cache do aplicativo. IMO
james
Agora, com um limite de APK de 50mb, o cache no cartão SD pode ser a única maneira para os desenvolvedores.
precisa saber é o seguinte
13

Eu consideraria usar o cache de imagem do droidfu. Ele implementa um cache de imagem na memória e baseado em disco. Você também recebe um WebImageView que tira proveito da biblioteca ImageCache.

Aqui está a descrição completa do droidfu e do WebImageView: http://brainflush.wordpress.com/2009/11/23/droid-fu-part-2-webimageview-and-webgalleryadapter/

esilver
fonte
Ele refatorou seu código desde 2010; aqui está o link root: github.com/kaeppler/droid-fu
esilver
3
Esse link ainda não funciona. Eu escrevi uma biblioteca semelhante chamado Android-ImageManager github.com/felipecsl/Android-ImageManager
Felipe Lima
9

Eu tentei o SoftReferences, eles são recuperados de forma muito agressiva no Android e eu senti que não havia sentido em usá-los

2cupsOfTech
fonte
2
Concordou - SoftReferences recuperados muito rapidamente nos dispositivos Eu testei
esilver
3
O próprio Google confirmou que o GC da Dalvik é muito agressivo na coleta de SoftReferences. Eles recomendam o uso deles LruCache.
kaka
9

Como o Thunder Rabbit sugeriu, o ImageDownloader é o melhor para o trabalho. Também encontrei uma pequena variação da classe em:

http://theandroidcoder.com/utilities/android-image-download-and-caching/

A principal diferença entre os dois é que o ImageDownloader usa o sistema de cache do Android e o modificado usa armazenamento interno e externo como cache, mantendo as imagens em cache indefinidamente ou até que o usuário as remova manualmente. O autor também menciona a compatibilidade com o Android 2.1.

EZFrag
fonte
7

Essa é uma boa notícia para Joe. O exemplo de código acima tem dois problemas - um - o objeto de resposta não é uma instância do Bitmap (quando meu URL faz referência a um jpg, como http: \ website.com \ image.jpg, é um

org.apache.harmony.luni.internal.net.www.protocol.http.HttpURLConnectionImpl $ LimitedInputStream).

Segundo, como Joe ressalta, nenhum cache ocorre sem que um cache de resposta esteja configurado. Os desenvolvedores do Android têm que rolar seu próprio cache. Aqui está um exemplo para fazer isso, mas ele apenas armazena em cache a memória, o que realmente não é a solução completa.

http://codebycoffee.com/2010/06/29/using-responsecache-in-an-android-app/

A API de armazenamento em cache do URLConnection é descrita aqui:

http://download.oracle.com/javase/6/docs/technotes/guides/net/http-cache.html

Ainda acho que essa é uma solução boa para seguir esse caminho - mas você ainda precisa escrever um cache. Parece divertido, mas eu prefiro escrever recursos.

Peter Pascale
fonte
7

Há uma entrada especial na seção de treinamento oficial do Android sobre isso: http://developer.android.com/training/displaying-bitmaps/cache-bitmap.html

A seção é bastante nova, não estava lá quando a pergunta foi feita.

A solução sugerida é usar um LruCache. Essa classe foi introduzida no Honeycomb, mas também está incluída na biblioteca de compatibilidade.

Você pode inicializar um LruCache configurando o número máximo ou entradas e ele automaticamente os classifica como você e os limpa menos usados ​​quando você ultrapassa o limite. Fora isso, ele é usado como um mapa normal.

O código de amostra da página oficial:

private LruCache mMemoryCache;

@Override
protected void onCreate(Bundle savedInstanceState) {
    ...
    // Get memory class of this device, exceeding this amount will throw an
    // OutOfMemory exception.
    final int memClass = ((ActivityManager) context.getSystemService(
            Context.ACTIVITY_SERVICE)).getMemoryClass();

    // Use 1/8th of the available memory for this memory cache.
    final int cacheSize = 1024 * 1024 * memClass / 8;

    mMemoryCache = new LruCache(cacheSize) {
        @Override
        protected int sizeOf(String key, Bitmap bitmap) {
            // The cache size will be measured in bytes rather than number of items.
            return bitmap.getByteCount();
        }
    };
    ...
}

public void addBitmapToMemoryCache(String key, Bitmap bitmap) {
    if (getBitmapFromMemCache(key) == null) {
        mMemoryCache.put(key, bitmap);
    }
}

public Bitmap getBitmapFromMemCache(String key) {
    return mMemoryCache.get(key);
}

Anteriormente, as SoftReferences eram uma boa alternativa, mas não mais, citando a partir da página oficial:

Nota: No passado, uma implementação popular de cache de memória era um cache de bitmap SoftReference ou WeakReference, no entanto, isso não é recomendado. A partir do Android 2.3 (API Nível 9), o coletor de lixo é mais agressivo ao coletar referências suaves / fracas, o que as torna bastante ineficazes. Além disso, antes do Android 3.0 (API Nível 11), os dados de backup de um bitmap eram armazenados na memória nativa, que não é liberada de maneira previsível, potencialmente fazendo com que um aplicativo excedesse brevemente seus limites de memória e falhas.

shalafi
fonte
3

Considere usar a biblioteca Universal Image Loader de Sergey Tarasevich . Vem com:

  • Carregamento de imagens multithread. Permite definir o tamanho do conjunto de encadeamentos
  • Cache de imagem na memória, no sistema de arquivos do dispositivo e no cartão SD.
  • Possibilidade de ouvir o progresso do carregamento e eventos de carregamento

O Universal Image Loader permite gerenciamento detalhado de cache para imagens baixadas, com as seguintes configurações de cache:

  • UsingFreqLimitedMemoryCache: O bitmap usado com menos frequência é excluído quando o limite de tamanho do cache é excedido.
  • LRULimitedMemoryCache: O bitmap usado menos recentemente é excluído quando o limite de tamanho do cache é excedido.
  • FIFOLimitedMemoryCache: A regra FIFO é usada para exclusão quando o limite de tamanho do cache é excedido.
  • LargestLimitedMemoryCache: O maior bitmap é excluído quando o limite de tamanho do cache é excedido.
  • LimitedAgeMemoryCache: O objeto em cache é excluído quando sua idade excede o valor definido .
  • WeakMemoryCache: Um cache de memória com apenas referências fracas a bitmaps.

Um exemplo simples de uso:

ImageView imageView = groupView.findViewById(R.id.imageView);
String imageUrl = "http://site.com/image.png"; 

ImageLoader imageLoader = ImageLoader.getInstance();
imageLoader.init(ImageLoaderConfiguration.createDefault(context));
imageLoader.displayImage(imageUrl, imageView);

Este exemplo usa o padrão UsingFreqLimitedMemoryCache.

Gunnar Karlsson
fonte
Quando usado intensivamente, o Universal Image Loader causa muitos vazamentos de memória. Eu suspeito que isso aconteça porque ele usa singletons no código (consulte 'getInstance ()' no exemplo). Depois de carregar muitas imagens e girar minha tela algumas vezes, meu aplicativo falhou o tempo todo porque OutOfMemoryErrors no UIL. É uma grande biblioteca, mas é um facto bem conhecido thet você nunca deve usar singletons, especialmente não no Android ...
Geert Bellemans
1
USE singletons quando você souber como! :)
Renetik 08/08
3

O que realmente funcionou para mim foi definir o ResponseCache na minha classe Principal:

try {
   File httpCacheDir = new File(getApplicationContext().getCacheDir(), "http");
   long httpCacheSize = 10 * 1024 * 1024; // 10 MiB
   HttpResponseCache.install(httpCacheDir, httpCacheSize);
} catch (IOException e) { } 

e

connection.setUseCaches(true);

ao baixar bitmap.

http://practicaldroid.blogspot.com/2013/01/utilizing-http-response-cache.html

ZamPrano
fonte
é possível utilizar em conjunto com LRUCache httpresponsecache
iOSAndroidWindowsMobileAppsDev
1

Eu estava lutando com isso há algum tempo; as respostas usando o SoftReferences perderiam seus dados muito rapidamente. As respostas que sugerem instanciar um RequestCache eram muito complicadas, e eu nunca consegui encontrar um exemplo completo.

Mas ImageDownloader.java funciona maravilhosamente para mim. Ele usa um HashMap até que a capacidade seja atingida ou até que o tempo limite da limpeza ocorra, depois as coisas são movidas para um SoftReference, usando assim o melhor dos dois mundos.

Coelho Trovão
fonte
0

Resposta ainda mais tarde, mas escrevi um Android Image Manager que lida com o cache de forma transparente (memória e disco). O código está no Github https://github.com/felipecsl/Android-ImageManager

Felipe Lima
fonte
1
Adicionei isso a um ListView e ele não parece lidar muito bem com isso. Existe alguma implementação especial para o ListViews?
0

Resposta tardia, mas achei que deveria adicionar um link ao meu site porque escrevi um tutorial sobre como fazer um cache de imagens para o Android: http://squarewolf.nl/2010/11/android-image-cache/ Update: the A página foi colocada offline porque a fonte estava desatualizada. Associo-me à @ elenasys nos conselhos dela para usar o Ignition .

Portanto, para todas as pessoas que se deparam com essa pergunta e não encontraram uma solução: espero que gostem! = D

Thomas Vervest
fonte
0

Resposta tardia, mas acho que esta biblioteca ajudará muito no armazenamento de imagens em cache: https://github.com/crypticminds/ColdStorage .

Basta anotar o ImageView com @LoadCache (R.id.id_of_my_image_view, "URL_to_downlaod_image_from) e ele cuidará do download da imagem e do carregamento na visualização da imagem. Também é possível especificar uma imagem de espaço reservado e carregar a animação.

A documentação detalhada da anotação está presente aqui: - https://github.com/crypticminds/ColdStorage/wiki/@LoadImage-annotation

Anurag Mandal
fonte