Anroid中图片的缓存策略之LruCache

在安卓的项目开发中必定少不了网络图片的加载,对与加载少量图片而言可能问题不大,但是当进行多图加载的时候如果不进行缓存处理的话那么你的用户肯定是比较痛苦的,无论是已经看过的图片或是没有看过的图片,都要每次请求网络进行重新获取。一方面是消耗了用户的流量,另一方面用户体验也不会好到哪去。所以我们在平常的开发中肯定要对这种问题进行处理,以提升用户体验,对于这个问题相比听得最多的就是三级缓存了吧(读起来无比的高大上),其原理就是:当我们第一次打开应用获取图片时,先去网络中去下载图片然后依次将图片存储到内存,磁盘中去,当我们下一次再需要这张图片的时候就不需要重复的到网络上面进行下载,直接可以先去内存中找,如果没有再接着到磁盘中寻找,如果有则直接应用,如果没有则再去进行网络请求,可以提升应用的性能以及优化前面所提到的糟糕的用户体验。这次我们先介绍内存中的缓存(^__^) (下面gif图能明显看出性能的优劣)

下面引出要介绍的类:LruCache(Least Recently Used)

通过缩写的字面意思可以知道 最近最少使用的,这个类大致实现的算法就是:当我们访问了item或者是说需要进行缓存的对象(比如说BitMap),这时候这个需要进行缓存的对象应该被加入到内存当中然后移动到队列的顶部,如此循环之后这个队列的顶部就应该是最近被访问的item了,而队尾就应该是很久没有访问过的item,该类中的trimToSize()方法会不断地检查当前的缓存是否超出了最大的缓存,如果超出则移除那条很久没有访问过的item。

需要由于是缓存在当前app内存中的,当我们app退出之后缓存就会被移除(所以还要配合磁盘进行缓存)

大家如果有兴趣的话可以去查看一下源码能够有一个更清晰的认识。

下面我们通过一个加载妹子图的栗子,来看一下LruCahce的使用:

使用LruCache缓存的效果

不使用LruCache缓存的效果

看到这里相信小伙伴们已经看出加载图片使用缓存的优点了吧,对于使用缓存来进行图片的加载,当图片通过网络请求获取到了之后,下次就不需要再从网络中进行获取,而是直接从内存中通过key来取出之前存放的BitMap对象(这里的缓存),由于从内存中的数据读取比较快,所以基本上看不到图片闪烁切换的现象(主要是由ListView的复用产生的,以及网络的异步耗时的网络请求,会造成图片闪烁以及错位的现象)。
 
下面看一下具体的实现过程:
 
项目结构:

Anroid中图片的缓存策略之LruCache_第1张图片

MyImageView.java(自定义能够加载网络图片,并能够缓存BitMap到内存中的ImageView):

/**
 * Created by wangke on 17-4-2.
 *
 * 自定义可以加载网络图片的ImageView,使用LruCache进行将图片缓存到内存中去
 */
@SuppressLint("AppCompatCustomView")
public class MyImageView extends ImageView{

    private LruCache mCache;

    public MyImageView(Context context) {
        super(context);
        Init();
    }

    public MyImageView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        Init();
    }

    public MyImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        Init();

    }

    private void Init() {
        //获取全局的LruCache对象
        mCache = SingleLruCache.getInstance(getContext());
    }
    /**
     * 加载图片从网络中
     * @param imageUrl
     */
    public void loadImageFromUrl(String imageUrl){

        //此处的逻辑,从网络上加载下来的图片,加载成功之后就将其进行缓存,下一次再进行获取图片的时候,现在本地的
        //缓存中进行查找,如果没有找到的话再进行去网络中请求的操作

        new myAsncTask().execute(imageUrl);
    }

    class myAsncTask extends AsyncTask{

        @Override
        protected Bitmap doInBackground(String... params) {

            String imageUrl = params[0];

            Bitmap bitmap = mCache.get(imageUrl);

            //在这里进行判断,如果缓存中存在图片则直接从里面取出来,如果没有再去进行网络的请求
            if(bitmap==null){

                bitmap = getBitmap(imageUrl);
                Log.i("wk","请求网络进行获取");

            }else {

                Log.i("wk","从缓存中获取");

            }
            return bitmap;
        }

        @Override
        protected void onPostExecute(Bitmap bitmap) {

            if(bitmap!=null){

                setImageBitmap(bitmap);
            }
        }
    }

    /**
     * 请求网络获取图片url的bitmap
     * @param imageUrl
     * @return
     */
    @Nullable
    private Bitmap getBitmap(String imageUrl) {
        try {

            HttpURLConnection conn = (HttpURLConnection) new URL(imageUrl).openConnection();

            conn.setRequestMethod("GET");
            conn.setReadTimeout(5000);
            conn.setConnectTimeout(5000);
            conn.setDoInput(true);

            int stateCode = conn.getResponseCode();

            if(stateCode == 200) {

                Log.i("wk", "服务器返回的状态码:" + stateCode);

                InputStream inputStream = conn.getInputStream();

                Bitmap bitmap = BitmapFactory.decodeStream(inputStream);

                //每一次请求网络获取的bitmap都存储在缓存中去


                if(bitmap!=null && imageUrl!=null) {

                    mCache.put(imageUrl, bitmap);

                    Log.i("wk","已缓存的大小:"+mCache.size());
                }

                return bitmap;

            }

        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }
}

 
上面代码使用AsyncTask完成网络请求的异步操作,在进行网络请求前,先判断缓存中是否存在与key相对应的value,如果有就直接取出而不再重新从网络获取,如果没有取出数据,则进行从网络加载并将获取到的Bitmap添加到缓存中去。

LruCache类中put(key,value)方法的具体实现:

 public final V put(K key, V value) {
        if (key == null || value == null) {
            throw new NullPointerException("key == null || value == null");
        }

        V previous;
        synchronized (this) {
            putCount++;
            size += safeSizeOf(key, value);
            previous = map.put(key, value); //如果有与key关联的value则返回value的值,如果没有则返回空 
            if (previous != null) {
        //执行到此处表明,添加的key在所对应的value在缓存中已经存在,所以要撤销上面增加内存的操作,所以 -
                size -= safeSizeOf(key, previous);
            }
        }


        if (previous != null) {
            entryRemoved(false, key, previous, value); //入口移除
        }

        trimToSize(maxSize);
        return previous;
    }

SingleLruCache(单例模式,确保LruCache全局唯一)


**
 * Created by wangke on 17-4-2.
 */

public class SingleLruCache {

    private SingleLruCache(){

    }

    private static LruCache mCache;

    public static LruCache getInstance(Context context){

        if(mCache == null){
            //获取ActivityManager
            ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
            //获取Android系统分配给当前app的内存大小
            int memoryClass = manager.getMemoryClass();

            mCache = new LruCache(memoryClass*1024*1024/8){

                @Override
                protected int sizeOf(String key, Bitmap value) {

                    return value.getByteCount();
                }
            };
        }
        return mCache;
    }
}

上面代码中的注意点:

  • 我们在对图片进行缓存的时候,需要创建一个全局唯一的LruCache用作多个MyImageView对象进行缓存处理,这里用到了单例模式来创建对象确保对象的全局唯一。

  • 在LruCache的构造方法中设置的最大缓存数谷歌推荐应该为系统分配给此应用内存大小的八分之一。听谷歌爸爸的话^_^

  • sizeOf()方法的默认实现是返回缓存的对象的数目,这里我们要重写该方法,返回我们所需要的值,这里我们返回bitmap的字节数。

    后面的ListView 的适配器的代码就不贴了,比较简单,哈哈最终目标要实现一个可以进行三级缓存的图片加载类O(∩_∩)O哈哈~

    清明假期敲敲代码,写写博客也还是不错滴。

你可能感兴趣的:(Android)