Android 图片三级缓存之内存缓存(告别软引用(SoftRefrerence)和弱引用(WeakReference))

本文深入探讨了Android应用中内存管理的挑战,特别关注于从软引用(SoftReference)和弱引用(WeakReference)转向更可靠的LruCache策略,以优化图片缓存性能并预防内存溢出。分析了Android系统的垃圾回收行为,强调了API Level 9及以上版本中软引用和弱引用的不可靠性,并提出了通过合理设置LruCache大小来解决内存泄漏问题的方法。通过示例代码展示如何高效地利用LruCache缓存图片,确保应用在加载大量图片时保持流畅运行。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


    因为之前项目同事使用了图片三级缓存,今天整理项目的时候发现同事还是使用了软引用(SoftRefrerence)和弱引用(WeakReference),来管理在内存中的缓存。看到这个我就感觉不对了。脑海中有一句之前文章出现的“因为从 Android 2.3 (API Level 9)开始,垃圾回收器会更倾向于回收持有软引用或弱引用的对象,这让软引用和弱引用变得不再可靠。”

    下面这个是官方文档的截图:

    点击打开链接 这是官方链接

    翻译一下:

    在过去,我们经常会使用一种非常流行的内存缓存技术的实现,即软引用或弱引用 (SoftReference or WeakReference)。

    但是现在已经不再推荐使用这种方式了,因为从 Android 2.3 (API Level 9)开始,垃圾回收器会更倾向于回收持有软引用或弱引用的对象,

    这让软引用和弱引用变得不再可靠。另外,Android 3.0 (API Level 11)中,图片的数据会存储在本地的内存当中,因而无法用一种可预见的方式将其释放,

    这就有潜在的风险造成应用程序的内存溢出并崩溃。所以看到还有很多相关文章还在推荐用软引用或弱引用 (SoftReference or WeakReference),就有点out了。

    下面是谷歌官方给的图片缓存例子(其实就是翻译上面链接的文章):

    在你应用程序的UI界面加载一张图片是一件很简单的事情,但是当你需要在界面上加载一大堆图片的时候,情况就变得复杂起来。在很多情况下,

    (比如使用ListView, GridView 或者 ViewPager 这样的组件),屏幕上显示的图片可以通过滑动屏幕等事件不断地增加,最终导致OOM。

    为了保证内存的使用始终维持在一个合理的范围,通常会把被移除屏幕的图片进行回收处理。此时垃圾回收器也会认为你不再持有这些图片的引用,

    从而对这些图片进行GC操作。用这种思路来解决问题是非常好的,可是为了能让程序快速运行,在界面上迅速地加载图片,你又必须要考虑到某些图片被回收之后,

    用户又将它重新滑入屏幕这种情况。这时重新去加载一遍刚刚加载过的图片无疑是性能的瓶颈,你需要想办法去避免这个情况的发生。

    这个时候,使用内存缓存技术可以很好的解决这个问题,它可以让组件快速地重新加载和处理图片。下面我们就来看一看如何使用内存缓存技术来对图片进行缓存,

    从而让你的应用程序在加载很多图片的时候可以提高响应速度和流畅性。

    内存缓存技术对那些大量占用应用程序宝贵内存的图片提供了快速访问的方法。其中最核心的类是LruCache (此类在android-support-v4的包中提供) 。

    这个类非常适合用来缓存图片,它的主要算法原理是把最近使用的对象用强引用存储在 LinkedHashMap 中,并且把最近最少使用的对象在缓存值达到预设定值之前从内存中移

    除。

    在过去,我们经常会使用一种非常流行的内存缓存技术的实现,即软引用或弱引用 (SoftReference or WeakReference)。但是现在已经不再推荐使用这种方式了,

    因为从 Android 2.3 (API Level 9)开始,垃圾回收器会更倾向于回收持有软引用或弱引用的对象,这让软引用和弱引用变得不再可靠。

    另外,Android 3.0 (API Level 11)中,图片的数据会存储在本地的内存当中,因而无法用一种可预见的方式将其释放,

    这就有潜在的风险造成应用程序的内存溢出并崩溃。

    为了能够选择一个合适的缓存大小给LruCache, 有以下多个因素应该放入考虑范围内,例如:

    • 你的设备可以为每个应用程序分配多大的内存?
    • 设备屏幕上一次最多能显示多少张图片?有多少图片需要进行预加载,因为有可能很快也会显示在屏幕上?
    • 你的设备的屏幕大小和分辨率分别是多少?一个超高分辨率的设备(例如 Galaxy Nexus) 比起一个较低分辨率的设备(例如 Nexus S),在持有相同数量图片的时候,需要更大的缓存空间。
    • 图片的尺寸和大小,还有每张图片会占据多少内存空间。
    • 图片被访问的频率有多高?会不会有一些图片的访问频率比其它图片要高?如果有的话,你也许应该让一些图片常驻在内存当中,或者使用多个LruCache 对象来区分不同组的图片。
    • 你能维持好数量和质量之间的平衡吗?有些时候,存储多个低像素的图片,而在后台去开线程加载高像素的图片会更加的有效。

    并没有一个指定的缓存大小可以满足所有的应用程序,这是由你决定的。你应该去分析程序内存的使用情况,然后制定出一个合适的解决方案。一个太小的缓存空间,

    有可能造成图片频繁地被释放和重新加载,这并没有好处。而一个太大的缓存空间,则有可能还是会引起 java.lang.OutOfMemory 的异常。

    下面是一个使用 LruCache 来缓存图片的例子:

    1. private LruCache<String, Bitmap> mMemoryCache;
    2. @Override
    3. protected void onCreate(Bundle savedInstanceState) {
    4. // 获取到可用内存的最大值,使用内存超出这个值会引起OutOfMemory异常。
    5. // LruCache通过构造函数传入缓存值,以KB为单位。
    6. int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
    7. // 使用最大可用内存值的1/8作为缓存的大小。
    8. int cacheSize = maxMemory / 8;
    9. mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
    10. @Override
    11. protected int sizeOf(String key, Bitmap bitmap) {
    12. // 重写此方法来衡量每张图片的大小,默认返回图片数量。
    13. return bitmap.getByteCount() / 1024;
    14. }
    15. };
    16. }
    17. public void addBitmapToMemoryCache(String key, Bitmap bitmap) {
    18. if (getBitmapFromMemCache(key) == null) {
    19. mMemoryCache.put(key, bitmap);
    20. }
    21. }
    22. public Bitmap getBitmapFromMemCache(String key) {
    23. return mMemoryCache.get(key);
    24. }

    在这个例子当中,使用了系统分配给应用程序的八分之一内存来作为缓存大小。在中高配置的手机当中,这大概会有4兆(32/8)的缓存空间。

    一个全屏幕的 GridView 使用4张 800x480分辨率的图片来填充,则大概会占用1.5兆的空间(800*480*4)。因此,这个缓存大小可以存储2.5页的图片。

    当向 ImageView 中加载一张图片时,首先会在 LruCache 的缓存中进行检查。如果找到了相应的键值,则会立刻更新ImageView ,否则开启一个后台线程来加载这张图片。
    1. public void loadBitmap(int resId, ImageView imageView) {
    2. final String imageKey = String.valueOf(resId);
    3. final Bitmap bitmap = getBitmapFromMemCache(imageKey);
    4. if (bitmap != null) {
    5. imageView.setImageBitmap(bitmap);
    6. } else {
    7. imageView.setImageResource(R.drawable.image_placeholder);
    8. BitmapWorkerTask task = new BitmapWorkerTask(imageView);
    9. task.execute(resId);
    10. }
    11. }
    BitmapWorkerTask 还要把新加载的图片的键值对放到缓存中。
    1. class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
    2. // 在后台加载图片。
    3. @Override
    4. protected Bitmap doInBackground(Integer... params) {
    5. final Bitmap bitmap = decodeSampledBitmapFromResource(
    6. getResources(), params[0], 100, 100);
    7. addBitmapToMemoryCache(String.valueOf(params[0]), bitmap);
    8. return bitmap;
    9. }
    10. }  
    评论
    添加红包

    请填写红包祝福语或标题

    红包个数最小为10个

    红包金额最低5元

    当前余额3.43前往充值 >
    需支付:10.00
    成就一亿技术人!
    领取后你会自动成为博主和红包主的粉丝 规则
    hope_wisdom
    发出的红包
    实付
    使用余额支付
    点击重新获取
    扫码支付
    钱包余额 0

    抵扣说明:

    1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
    2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

    余额充值