Android 图像缓存

如何缓存从网上下载的图像?

115352 次浏览

将它们转换成位图,然后将它们存储在一个集合中(HashMap、 List 等) ,或者你可以将它们写在 SDcard 上。

当使用第一种方法将它们存储在应用程序空间中时,您可能希望将它们包装在 Lang.ref. SoftReference中,特别是当它们的数量很大时(以便在危机期间对它们进行垃圾收集)。不过,这可能会导致重新加载。

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

在 SDcard 上写它们不需要重新加载,只需要用户许可。

要下载一个图像并保存到内存卡,你可以这样做。

//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));

不要忘记将 Internet 权限添加到您的清单中:

<uses-permission android:name="android.permission.INTERNET" />

我会考虑使用 Droidfu 的图像缓存。它实现了内存和基于磁盘的映像缓存。您还将获得一个利用 ImageCache 库的 WebImageView。

下面是对 droidfu 和 WebImageView 的完整描述: Http://brainflush.wordpress.com/2009/11/23/droid-fu-part-2-webimageview-and-webgalleryadapter/

现在的关键是: 使用系统缓存。

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

提供与浏览器共享的内存和 flash-rom 缓存。

真希望在我编写自己的缓存管理器之前有人告诉我这些。

关于上面的优雅的 connection.setUseCaches解决方案: 遗憾的是,如果没有一些额外的努力,它将无法工作。您需要使用 ResponseCache.setDefault安装 ResponseCache。否则,HttpURLConnection将无声地忽略 setUseCaches(true)位。

有关详细信息,请参阅 FileResponseCache.java顶部的注释:

Http://libs-for-android.googlecode.com/svn/reference/com/google/android/filecache/fileresponsecache.html

(我本想发表评论,但显然我没有足够的因果报应。)

我已经试过 SoftReferences 了,它们在 android 中回收得太积极了,我觉得没有必要使用它们

乔接得好。上面的代码示例有两个问题——第一个——响应对象不是 Bitmap 的实例(当我的 URL 引用一个 jpg,比如 http: website.com image.jpg 时,它是一个

Http://www.Protocol.http://HttpURLConnectionImpl $LimitedInputStream).

其次,正如 Joe 指出的,如果没有配置响应缓存,就不会发生缓存。Android 开发人员只能自己滚动缓存。这里有一个这样做的例子,但是它只缓存在内存中,这并不是完整的解决方案。

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

这里描述 URLConnection 缓存 API:

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

我仍然认为这是一个可行的解决方案,但是您仍然需要编写一个缓存。听起来很有趣,但我更想写特写。

回答晚了,但我想我应该添加一个链接到我的网站,因为我已经写了一个教程,如何使一个安卓图像缓存: 返回文章页面安卓图像缓存 http://squarewolf.nl/2010/11/android-image-cache/ : 更新:该页面已经脱机,因为源代码已经过时。我和@elenasys 一样,建议使用 点火

所以,对于那些偶然发现这个问题却没有找到解决办法的人们,我要说: 希望你们喜欢

我已经纠结这个问题有一段时间了; 使用 SoftReferences 得到的答案会很快丢失数据。建议实例化 RequestCache 的答案太混乱了,而且我从未找到一个完整的示例。

但是 Java对我来说非常有用。它使用 HashMap,直到达到容量或者清除超时发生,然后将事情移动到 SoftReference,从而使用两者的最佳方式。

Google 的 libs-for-android 有一个很好的管理图像和文件缓存的库。

Http://code.google.com/p/libs-for-android/

正如雷兔所建议的那样,ImageDownloader 是这项工作的最佳选择。我还发现这门课程有一些细微的变化:

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

两者之间的主要区别在于 ImageDownloader 使用 Android 缓存系统,而修改后的 ImageDownloader 使用内部和外部存储作为缓存,无限期地保持缓存的映像,或者直到用户手动删除它。作者还提到了 Android 2.1的兼容性。

在 Android 的官方培训部分有一个关于这个的特殊条目: http://developer.android.com/training/displaying-bitmaps/cache-bitmap.html

这个部分是相当新的,当问题被提出的时候它不在那里。

建议的解决方案是使用 LruCache。这个类是在 Honeycomb 上引入的,但它也包含在兼容性库中。

您可以通过设置最大数量或条目来初始化 LruCache,当您超过限制时,它会自动对它们进行排序,并清除较少使用的条目。除此之外,它被用作一个普通的映射。

来自官方页面的示例代码:

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);
}

以前 SoftReferences 是一个不错的选择,但现在不再是了,官方网页引用:

注意: 在过去,流行的内存缓存实现是 SoftReference 或 WeakReference 位图缓存,但这不是 从 Android 2.3(API 级别9)开始,垃圾 收集器在收集软/弱引用方面更积极 这使得它们相当无效。此外,在 Android 3.0之前 (API 级别11) ,位图的备份数据以原生方式存储 不以可预测的方式释放的存储器,可能 导致应用程序短暂超出内存限制并崩溃。

考虑使用 Sergey Tarasevich通用图像加载程序库。它附带:

  • 多线程映像加载。它允许您定义线程池的大小
  • 图像缓存在内存,在设备的文件系统和 SD 卡。
  • 监听加载进度和加载事件的可能性

通用图像加载器允许 详细的缓存管理下载图像,具有以下缓存配置:

  • UsingFreqLimitedMemoryCache: 当超过缓存大小限制时,删除 最不常见的使用的位图。
  • LRULimitedMemoryCache: 当超过缓存大小限制时,将删除 至少最近是使用的位图。
  • FIFOLimitedMemoryCache: 当超过缓存大小限制时,FIFO 规则用于删除。
  • LargestLimitedMemoryCache: 当超过缓存大小限制时,将删除 最大的位图。
  • 当缓存对象的 年龄超过规定值被删除时。
  • WeakMemoryCache: 只有对位图弱引用的内存缓存。

一个简单的使用例子:

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);

此示例使用默认的 UsingFreqLimitedMemoryCache

甚至后来的答案,但我写了一个 Android 图像管理器,处理缓存透明(内存和磁盘)。密码在 Github https://github.com/felipecsl/Android-ImageManager

真正对我有用的是在我的 Main 类上设置 ResponseCache:

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

还有

connection.setUseCaches(true);

下载位图时。

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

使用 LruCache有效地缓存图像。您可以从 Android 开发者网站读取有关 LruCache的信息

我已经在 android 中使用了下面的图片下载和缓存解决方案。你可以按照下面的步骤:

第一步: 使类命名为 ImagesCache。我已经使用了 Singleton 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();
}
}


}

第二步:

创建另一个名为 DownloadImageTask 的类,如果位图在缓存中不可用,它将从这里下载:

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;
}

步骤3: 从你的 ActivityAdapter使用

注意: 如果您想从 Activity类的 url 加载图像。使用 DownloadImageTask的第二个构造函数,但是如果你想显示来自 Adapter的图像,使用 DownloadImageTask的第一个构造函数(例如,你在 ListView中有一个图像,你正在从“ Adapter”设置图像)

活动用法:

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);
}

来自适配器的用法:

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);
}

注:

您可以在应用程序的第一个活动中使用这个语句。一旦初始化了缓存,如果使用的是 ImagesCache实例,就不需要每次都初始化它。

我从来不擅长解释东西,但希望这将帮助初学者如何缓存使用 LruCache及其用法:)

编辑:

现在有一个非常著名的库称为 PicassoGlide,可以用来加载图像非常有效的安卓应用程序。尝试这个非常简单和有用的库 毕加索的机器人滑翔机器人。您不需要担心缓存映像。

Picasso 允许在你的应用程序中轻松加载图像ーー通常是这样 在一行代码中!

像毕加索一样,Glide 可以加载和显示许多图像 同时还负责缓存和保持低内存 在进行图像处理时产生的影响。它已被官方使用 谷歌应用程序(就像2015年谷歌 I/O 的应用程序一样)也同样受欢迎 在这个系列中,我们将探索其中的差异 滑动优于毕加索。

你也可以访问 格莱德和毕加索的区别的博客