自己实现图片缓存

本文探讨了Android图片缓存的必要性,如防止OOM和提高加载速度,并讲解了实现图片缓存的步骤,包括使用LRU算法和LinkedHashMap。还涉及从网络获取图片、三级缓存策略以及Android网络编程基础,提供了实现简单LRU缓存的示例。
摘要由CSDN通过智能技术生成

前言 - 理解图片缓存

参考文章:
Android Universal Image Loader 源码分析
Android性能优化:图片的加载和图片缓存技术|SquirrelNote
自己动手实现Android中的三级缓存框架

上手要求实现一个图片缓存框架,我是很懵,很不知所措的。因为我连图片缓存框架都没用过- -。因为自己写的小项目不需要用。所以第一就是进一步详细了解一下什么是图片缓存框架,它要完成什么任务。


  • 图片缓存框架的作用?

简单来说就做了一件事:获取图片并显示在相应的控件上。

  • 为什么显示图片这件事情会需要一个图片缓存框架来处理呢

必然是因为不用会出问题,解决这些问题也就是图片缓存框架的作用,那么问题是什么呢?

  • 问题一:OOM,加载太多的大量数据时控件不够

Android性能优化:图片的加载和图片缓存技术|SquirrelNote中,解释道:

当需要在界面上加载大量图片的时候,比如使用ListView,GridView,或者ViewPager这样的组件,屏幕上显示的图片可以通过滑动屏幕等事件不断增加,最终导致OOM。
为了保证内存的使用始终维持在一个合理的范围,通常会把被移除屏幕的图片进行处理。这个时候,垃圾回收器也会认为你不再持有这些图片的引用,从而对这些图片进行GC操作。如果为了让程序快速地运行,在界面上迅速地加载图片,我们又需要考虑到某些图片被回收之后,用户又将它重新滑入屏幕这种情况。就需要避免又重新去加载刚刚加载过的图片,这个时候,可以使用内存缓存技术解决这个问题,它可以让组件快速地重新加载和处理图片。

这篇文章的讲解逻辑很清楚,
第一步:解释了,为什么如果加载图片过大的时候,为什么会导致OOM?因为Android系统每个进程有最大的VM限制。
第二步:怎么来解决加载图片过大的问题?我们不需要那么大的图片,所以可以压缩大图片,而压缩的方式就是 - 重采样。
第三步:即使每张图片都小,但是如果要加载的量大的话依然有问题。这就需要图片缓存技术

  • 问题二:访问数据的性能问题,从网络中拿数据速度太慢鸟~

现在android应用中不可避免的要使用图片,有些图片是可以变化的,需要每次启动时从网络拉取,这种场景在有广告位的应用以及纯图片应用(比如百度美拍)中比较多。假如每次启动的时候都从网络拉取图片的话,势必会消耗很多流量。

同时,有时候Android应用中要获取比较大的数据,比如说图片流,短视频流等,如果每次都从网络上去请求,那么响应速度很慢的,用户体验不好。

如何实现图片缓存

从思路部分可以看到图片缓存的目标:在一定的内存空间内,缓存最有可能使用的图片。
完整的三级缓存:

参考: 自己动手实现Android中的三级缓存框架

  1. 先检查内存中缓存数据如果内存中有数据并且数据不脏时直接返回内存中的数据。
  2. 如果内存中无数据并且数据不为脏时向本地数据库中请求数据,并且将请求的数据写入到内存中,再将内存中的数据返回。
  3. 如果内存和本地数据库中都没有数据返回,也就是内存中无数据并且数据为脏时,向服务器请求数据,服务器返回的数据,保存到本地数据库并且保存一份到内存,最后将内存中的数据返回。

要怎么实现呢?换句话说,学什么呢?

  1. 缓存算法
    想要在内存和数据库中交换数据,必然涉及到交换数据的办法。

  2. 从网络上下载图片
    这个,我之前还没有用过。之前真的是作为一个只写Android界面的想法。。。

  3. 线程池
    页面在第一次加载的时候,从网络上下载,如果滑动图片比较快,里面滑动了1000个图片,就会有1000个new Thread()线程,会造成oom,解决方案是,在代码中,不用new Thread()方法去开启线程,用线程池管理

缓存算法

参考文章:Android 内存缓存:手把手教你学会LrhCache算法
在这里插入图片描述
我要自己实现,就要明白这几个名词的含义:LRU算法LinkedHashMap数据结构强引用

LRU算法

这个算法其实挺有印象的,在学操作系统的时候学过页的替换。LRU算法也就是最近最少使用算法,会把使用时间最遥远的一个替换出去。

LinkedHashMap

参考文章:彻头彻尾理解 LinkedHashMap

HashMap和双向链表合二为一即是LinkedHashMap。

HashMap是无序的,也就是说,迭代HashMap所得到的元素顺序并不是它们最初放置到HashMap的顺序。HashMap的这一缺点往往会造成诸多不便,因为在有些场景中,我们确需要用到一个可以保持插入顺序的Map。庆幸的是,JDK为我们解决了这个问题,它为HashMap提供了一个子类 —— LinkedHashMap。虽然LinkedHashMap增加了时间和空间上的开销,但是它通过维护一个额外的双向链表保证了迭代顺序。特别地,该迭代顺序可以是插入顺序,也可以是访问顺序。因此,根据链表中元素的顺序可以将LinkedHashMap分为:保持插入顺序的LinkedHashMap 和 保持访问顺序的LinkedHashMap,其中LinkedHashMap的默认实现是按插入顺序排序的。

LinkedHashMapMap中,所有put进来的Entry都保存在如下面第一个图所示的哈希表中,但由于它又额外定义了一个以head为头结点的双向链表(如下面第二个图所示),因此对于每次put进来Entry,除了将其保存到哈希表中对应的位置上之外,还会将其插入到双向链表的尾部。
在这里插入图片描述
在这里插入图片描述
从上述介绍我们可以发现,我们可以用hashmap的特性来判断它是否在我们的缓存中,如果不在,我们可以用双向链表维护的访问顺序来决定我们需要将那一个图片从缓存中撤下来

### LinkedHashMap的简单例子
        LinkedHashMap<Integer, Integer> map = new LinkedHashMap<>(0, 0.75f, true);

        // 2. 插入数据
        map.put(0, 0);
        map.put(1, 1);
        map.put(2, 2);
        map.put(3, 3);
        map.put(4, 4);
        map.put(5, 5);
        map.put(6, 6);

        // 3. 访问数据
        map.get(1);
        map.get(2);

        // 遍历获取LinkedHashMap内的数据
        for (Map.Entry<Integer, Integer> entry : map.entrySet()) {
   
            Log.d(TAG, entry.getKey() + ":" + entry.getValue());
        }
06-05 09:18:18.892 1710-1710/com.example.myimageloader D/MainActivity: 0:0
06-05 09:18:18.892 1710-1710/com.example.myimageloader D/MainActivity: 3:3
06-05 09:18:18.892 1710-1710/com.example.myimageloader D/MainActivity: 4:4
06-05 09:18:18.892 1710-1710/com.example.myimageloader D/MainActivity: 5:5
06-05 09:18:18.892 1710-1710/com.example.myimageloader D/MainActivity: 6:6
06-05 09:18:18.892 1710-1710/com.example.myimageloader D/MainActivity: 1:1
06-05 09:18:18.892 1710-1710/com.example.myimageloader D/MainActivity: 2:2

强引用

理解Java的强引用、软引用、弱引用和虚引用
这其实是因为Java使用垃圾回收器的方式处理我们声明的空间。

强引用
强引用是使用最普遍的引用。如果一个对象具有强引用,那垃圾回收器绝不会回收它。

Object strongReference = new Object();

当内存空间不足时,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足的问题。 如果强引用对象不使用时,需要弱化从而使GC能够回收,如下:

strongReference = null;

显式地设置strongReference对象为null,或让其超出对象的生命周期范围,则gc认为该对象不存在引用,这时就可以回收这个对象。具体什么时候收集这要取决于GC算法。

软引用
如果一个对象只具有软引用,则内存空间充足时,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。

    // 强引用
    String strongReference = new String("abc");
    // 软引用
    String str = new String("abc");
    SoftReference<String> softReference = new SoftReference<String>(str);

弱引用
弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。

    String str = new String("abc");
    WeakReference<String> weakReference = new WeakReference<>(str);
    str = null;

简单的一个LRU缓存

在这里插入图片描述
其实不难,因为LinkedHashMap真的是适合做这个事情啊。意识到了数据结构的重要性
效果如下:3个地址,缓存2个。换页方式为 1,2,1,3,2,3。 在拿入3的时候,因为访问过1,所以是2被置换出去,而不是先放入的1.所以后来访问2的时候需要重新读取。

06-05 11:05:39.055 3013-3013/com.example.myimageloader D/myLruCache: 从SD卡读取
06-05 11:05:39.747 3013-3013/com.example.myimageloader D/myLruCache: 从SD卡读取
06-05 11:05:41.045 3013-3013/com.example.myimageloader D/myLruCache: 在Cache中
06-05 11:05:42.179 3013-3013/com.example.myimageloader D/myLruCache: 从SD卡读取
06-05 11:05:43.458 3013-3013/com.example.myimageloader D/myLruCache: 从SD卡读取
06-05 11:05:44.931 3013-3013/com.example.myimageloader D/myLruCache: 在Cache中

在这里插入图片描述

详细代码如下:(记得设置SD卡读写权限)

public class myLruCache {
   
    private st
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值