Android 图片异步加载的体会,SoftReference已经不再适用
在网络上搜索Android图片异步加载的相关文章,目前大部分提到的解决方案,都是采用Map<String, SoftReference<Drawable>> 这样软引用的方式缓存网络图片。具体的例子见这篇文章。
核心代码如下:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 public class AsyncImageLoader { 2 3 private HashMap<String, SoftReference<Drawable>> imageCache; 4 5 public AsyncImageLoader() { 6 imageCache = new HashMap<String, SoftReference<Drawable>>(); 7 } 8 9 public Drawable loadDrawable(final String imageUrl, final ImageCallback imageCallback) { 10 if (imageCache.containsKey(imageUrl)) { 11 SoftReference<Drawable> softReference = imageCache.get(imageUrl); 12 Drawable drawable = softReference.get(); 13 if (drawable != null) { 14 return drawable; 15 } 16 } 17 final Handler handler = new Handler() { 18 public void handleMessage(Message message) { 19 imageCallback.imageLoaded((Drawable) message.obj, imageUrl); 20 } 21 }; 22 new Thread() { 23 @Override 24 public void run() { 25 Drawable drawable = loadImageFromUrl(imageUrl); 26 imageCache.put(imageUrl, new SoftReference<Drawable>(drawable)); 27 Message message = handler.obtainMessage(0, drawable); 28 handler.sendMessage(message); 29 } 30 }.start(); 31 return null; 32 } 33 34 public static Drawable loadImageFromUrl(String url) { 35 URL m; 36 InputStream i = null; 37 try { 38 m = new URL(url); 39 i = (InputStream) m.getContent(); 40 } catch (MalformedURLException e1) { 41 e1.printStackTrace(); 42 } catch (IOException e) { 43 e.printStackTrace(); 44 } 45 Drawable d = Drawable.createFromStream(i, "src"); 46 return d; 47 } 48 49 public interface ImageCallback { 50 public void imageLoaded(Drawable imageDrawable, String imageUrl); 51 } 52 53 }
最开始时我在自己的项目中也采取这样的代码段,从界面上来说没有发现什么问题,也还算流畅。可后来某次抓包分析数据的时候,在ListView异步加载图片已经完全,我在上下滑动listview时,依旧会有网络请求发出不断加载图片,非常奇怪。从以上代码里可以看到,异步加载下来的图片全都存在了imageCache中,应该可以直接调用缓存中的图片来显示就可以了。
查找资料以后发现了其中的玄机,具体可见Android官方的这篇文章,Caching Bitmaps。里面提到用 SoftReference 或者 WeakReference做图片缓存的方法在Android 2.3版本以后已经不被推荐了。因为内存对SoftReference和WeakReference的回收更加频繁,从而也导致了我在上面说的反复从网络上拉取图片的操作。
Android官方推荐最新的解决办法,是采用LruCache。如果图片数量巨大,也可以进一步考虑将图片缓存到sd卡中,可以采用DiskLruCache。具体的逻辑和上文其实差不多。相关代码如下:
1 private LruCache<String, Bitmap> mMemoryCache; 2 3 @Override 4 protected void onCreate(Bundle savedInstanceState) { 5 ... 6 // Get max available VM memory, exceeding this amount will throw an 7 // OutOfMemory exception. Stored in kilobytes as LruCache takes an 8 // int in its constructor. 9 final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024); 10 11 // Use 1/8th of the available memory for this memory cache. 12 final int cacheSize = maxMemory / 8; 13 14 mMemoryCache = new LruCache<String, Bitmap>(cacheSize) { 15 @Override 16 protected int sizeOf(String key, Bitmap bitmap) { 17 // The cache size will be measured in kilobytes rather than 18 // number of items. 19 return bitmap.getByteCount() / 1024; 20 } 21 }; 22 ... 23 } 24 25 public void addBitmapToMemoryCache(String key, Bitmap bitmap) { 26 if (getBitmapFromMemCache(key) == null) { 27 mMemoryCache.put(key, bitmap); 28 } 29 } 30 31 public Bitmap getBitmapFromMemCache(String key) { 32 return mMemoryCache.get(key); 33 }