如何在从Web下载图像后对其进行缓存?
现在来到关键的一步:使用系统缓存。
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
顶部的注释以获取详细信息:
(我想在评论中发布此内容,但显然我的SO karma不够。)
HttpResponseCache
时,您可能会发现HttpResponseCache.getHitCount()
返回0。我不确定,但我认为这是因为您请求的Web服务器在这种情况下没有使用缓存标头。为了使缓存仍然起作用,请使用connection.addRequestProperty("Cache-Control", "max-stale=" + MAX_STALE_CACHE);
。 - Almer.getContent()
方法时挂起HUC。 - TheRealChx101将它们转换为位图,然后将它们存储在 Collection(HashMap、List 等) 中,或者可以将它们写入 SD 卡。
当使用第一种方法在应用程序空间中存储它们时,如果它们的数量很大,您可能希望将它们包装在 java.lang.ref.SoftReference 中(以便在危机期间进行垃圾回收)。这可能会导致重新加载。
HashMap<String,SoftReference<Bitmap>> imageCache =
new HashMap<String,SoftReference<Bitmap>>();
将它们写入SD卡不需要重新加载,只需要用户权限。
Uri
路径引用,您可以将其传递给 ImageView
和其他自定义视图。因为每次压缩时,您都会失去质量。当然,这仅适用于有损算法。此方法还允许您甚至存储文件的哈希,并在下次通过 If-None-Match
和 ETag
标头从服务器请求文件时使用它。 - TheRealChx101使用LruCache
有效地缓存图像。您可以从Android Developer网站了解有关LruCache
的详细信息。
我在Android中使用以下解决方案进行图像下载和缓存。 您可以按照以下步骤操作:
第1步:创建名为ImagesCache
的类。 我使用了Singleton对象
来实现此类。
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();
}
}
}
步骤2:
创建另一个名为 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:从您的Activity
或Adapter
中使用
注意:如果您想从Activity
类加载URL的图像,请使用DownloadImageTask
的第二个构造函数,但如果您想从Adapter
显示图像,请使用DownloadImageTask
的第一个构造函数(例如,您在ListView
中有一个图片,并且您正在从“Adapter”设置图像)
从Activity中的用法:
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);
}
注意:
cache.initializeCache()
可以在应用程序中的第一个Activity中使用此语句。一旦你初始化了缓存,如果你正在使用 ImagesCache
实例,你将永远不需要再次初始化缓存。
我从来不擅长解释事物,但希望这对于那些想要使用 LruCache
进行缓存和使用的初学者有所帮助 :)
编辑:
现在有很多著名的库,如 Picasso
和 Glide
,可以在 Android 应用程序中高效地加载图像。尝试这个非常简单和有用的库Picasso for android 和 Glide For Android。你不需要担心缓存图片。
Picasso 允许在应用程序中无忧无虑地加载图像 - 往往只需一行代码!
Glide 就像 Picasso 一样,可以从许多来源加载和显示图像 ,同时还负责缓存和在进行图像操作时保持低内存影响。它已被 Google 官方应用程序(如 Google I/O 2015 应用程序)使用,并且与 Picasso 一样受欢迎。在这个系列中,我们将探索 Glide 相对于 Picasso 的差异和优势。
你还可以访问Glide 和 Picasso 之间的区别 博客了解更多信息。
LruCache
有键值对,每当您获取图像 URL 时,getImage()
将从 URL 下载图像。图像的 URL 将是 LruCache
的 key
,而位图将是 value
。如果您仔细查看 DownloadImageTask
,您可以设置 desiredWidth
和 desiredHeight
值,您设置的宽度和高度越小,您将看到的图像质量越低。 - Zubair Ahmedif(cache == null)
,它解决了我的问题! :) - Koorosh要下载一张图片并保存到存储卡中,你可以像这样做。
//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));
别忘了在你的清单文件中添加互联网权限:
<uses-permission android:name="android.permission.INTERNET" />
在Android的官方培训章节中有一个特别的条目与此相关:http://developer.android.com/training/displaying-bitmaps/cache-bitmap.html
这个章节是很新的,当提问时它还不存在。
建议的解决方法是使用LruCache。这个类在Honeycomb上被引入,但也包括在兼容库中。
你可以通过设置最大条目数来初始化LruCache,当超过限制时,它会自动为您排序并清除不常用的条目。除此之外,它像普通的Map一样使用。
下面是官方网页的示例代码:
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 Level 9)开始,垃圾收集器对于收集软引用/弱引用更加积极,使它们相当无效。此外,在Android 3.0之前(API Level 11),位图的后备数据存储在本地内存中,该内存不以可预测的方式释放,可能导致应用程序短暂超出其内存限制并崩溃。
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
我仍然认为这是一种可行的解决方案,但你仍然需要编写一个缓存。听起来很有趣,但我更愿意编写功能。
connection.getContent()
总是返回一个 InputStream,我做错了什么? - Tyler CollierBitmap response = BitmapFactory.decodeStream((InputStream)connection.getContent());
- Stephen Fuhryconnection.getContent()
没有返回位图,因为URLConnection中的默认内容处理程序都不处理图像类型。您需要设置自定义ContentHandlerFactory,就像这个代码片段中所示。 - sschuberth