一聚教程网:一个值得你收藏的教程网站

最新下载

热门教程

Android开发中内存缓存LruCache实现原理及实例应用

时间:2015-09-25 编辑:简简单单 来源:一聚教程网

先分析内存缓存是如何实现的,开始进入正题。

BitmapUtils内存缓存的核心类LruMemoryCache,LruMemoryCache代码和v4包的LruCache一样,只是加了一个存储超期的处理,这里分析LruCache源码。LRU即Least Recently Used,近期最少使用算法。也就是当内存缓存达到设定的最大值时将内存缓存中近期最少使用的对象移除,有效的避免了OOM的出现。
 
讲到LruCache不得不提一下LinkedHashMap,因为LruCache中Lru算法的实现就是通过LinkedHashMap来实现的。LinkedHashMap继承于HashMap,它使用了一个双向链表来存储Map中的Entry顺序关系,这种顺序有两种,一种是LRU顺序,一种是插入顺序,这可以由其构造函数public LinkedHashMap(int initialCapacity,float loadFactor, boolean accessOrder)指定。所以,对于get、put、remove等操作,LinkedHashMap除了要做HashMap做的事情,还做些调整Entry顺序链表的工作。LruCache中将LinkedHashMap的顺序设置为LRU顺序来实现LRU缓存,每次调用get(也就是从内存缓存中取图片),则将该对象移到链表的尾端。调用put插入新的对象也是存储在链表尾端,这样当内存缓存达到设定的最大值时,将链表头部的对象(近期最少用到的)移除。关于LinkedHashMap详解请前往http://www.111com.net/jsp/J2ME/94612.htm

下面看下LruCache的源码,我都注释的很详细了。
        

/*
 * Copyright (C) 2011 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package android.support.v4.util;
import java.util.LinkedHashMap;
import java.util.Map;
/**
 * Static library version of {@link android.util.LruCache}. Used to write apps
 * that run on API levels prior to 12. When running on API level 12 or above,
 * this implementation is still used; it does not try to switch to the
 * framework's implementation. See the framework SDK documentation for a class
 * overview.
 */
public class LruCache {
    private final LinkedHashMap map;
    /** Size of this cache in units. Not necessarily the number of elements. */
    private int size;    //当前cache的大小
    private int maxSize; //cache最大大小
    private int putCount;       //put的次数
    private int createCount;    //create的次数
    private int evictionCount;  //回收的次数
    private int hitCount;       //命中的次数
    private int missCount;      //未命中次数
    /**
     * @param maxSize for caches that do not override {@link #sizeOf}, this is
     *     the maximum number of entries in the cache. For all other caches,
     *     this is the maximum sum of the sizes of the entries in this cache.
     */
    public LruCache(int maxSize) {
        if (maxSize <= 0) {
            throw new IllegalArgumentException("maxSize <= 0");
        }
        this.maxSize = maxSize;
        //将LinkedHashMap的accessOrder设置为true来实现LRU
        this.map = new LinkedHashMap(0, 0.75f, true);  
    }
    /**
     * Returns the value for {@code key} if it exists in the cache or can be
     * created by {@code #create}. If a value was returned, it is moved to the
     * head of the queue. This returns null if a value is not cached and cannot
     * be created.
     * 通过key获取相应的item,或者创建返回相应的item。相应的item会移动到队列的尾部,
     * 如果item的value没有被cache或者不能被创建,则返回null。
     */
    public final V get(K key) {
        if (key == null) {
            throw new NullPointerException("key == null");
        }
        V mapValue;
        synchronized (this) {
            mapValue = map.get(key);
            if (mapValue != null) {
                //mapValue不为空表示命中,hitCount+1并返回mapValue对象
                hitCount++;
                return mapValue;
            }
            missCount++;  //未命中
        }
        /*
         * Attempt to create a value. This may take a long time, and the map
         * may be different when create() returns. If a conflicting value was
         * added to the map while create() was working, we leave that value in
         * the map and release the created value.
         * 如果未命中,则试图创建一个对象,这里create方法返回null,并没有实现创建对象的方法
         * 如果需要事项创建对象的方法可以重写create方法。因为图片缓存时内存缓存没有命中会去
         * 文件缓存中去取或者从网络下载,所以并不需要创建。
         */
        V createdValue = create(key);
        if (createdValue == null) {
            return null;
        }
        //假如创建了新的对象,则继续往下执行
        synchronized (this) {
            createCount++;  
            //将createdValue加入到map中,并且将原来键为key的对象保存到mapValue
            mapValue = map.put(key, createdValue);   
            if (mapValue != null) {
                // There was a conflict so undo that last put
                //如果mapValue不为空,则撤销上一步的put操作。
                map.put(key, mapValue);
            } else {
                //加入新创建的对象之后需要重新计算size大小
                size += safeSizeOf(key, createdValue);
            }
        }
        if (mapValue != null) {
            entryRemoved(false, key, createdValue, mapValue);
            return mapValue;
        } else {
            //每次新加入对象都需要调用trimToSize方法看是否需要回收
            trimToSize(maxSize);
            return createdValue;
        }
    }
    /**
     * Caches {@code value} for {@code key}. The value is moved to the head of
     * the queue.
     *
     * @return the previous value mapped by {@code key}.
     */
    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);  //size加上预put对象的大小
            previous = map.put(key, value);
            if (previous != null) {
                //如果之前存在键为key的对象,则size应该减去原来对象的大小
                size -= safeSizeOf(key, previous);
            }
        }
        if (previous != null) {
            entryRemoved(false, key, previous, value);
        }
        //每次新加入对象都需要调用trimToSize方法看是否需要回收
        trimToSize(maxSize);
        return previous;
    }
    /**
     * @param maxSize the maximum size of the cache before returning. May be -1
     *     to evict even 0-sized elements.
     * 此方法根据maxSize来调整内存cache的大小,如果maxSize传入-1,则清空缓存中的所有对象
     */
    private void trimToSize(int maxSize) {
        while (true) {
            K key;
            V value;
            synchronized (this) {
                if (size < 0 || (map.isEmpty() && size != 0)) {
                    throw new IllegalStateException(getClass().getName()
                            + ".sizeOf() is reporting inconsistent results!");
                }
                //如果当前size小于maxSize或者map没有任何对象,则结束循环
                if (size <= maxSize || map.isEmpty()) {
                    break;
                }
                //移除链表头部的元素,并进入下一次循环
                Map.Entry toEvict = map.entrySet().iterator().next();
                key = toEvict.getKey();
                value = toEvict.getValue();
                map.remove(key);
                size -= safeSizeOf(key, value);
                evictionCount++;  //回收次数+1
            }
            entryRemoved(true, key, value, null);
        }
    }
    /**
     * Removes the entry for {@code key} if it exists.
     *
     * @return the previous value mapped by {@code key}.
     * 从内存缓存中根据key值移除某个对象并返回该对象
     */
    public final V remove(K key) {
        if (key == null) {
            throw new NullPointerException("key == null");
        }
        V previous;
        synchronized (this) {
            previous = map.remove(key);
            if (previous != null) {
                size -= safeSizeOf(key, previous);
            }
        }
        if (previous != null) {
            entryRemoved(false, key, previous, null);
        }
        return previous;
    }
    /**
     * Called for entries that have been evicted or removed. This method is
     * invoked when a value is evicted to make space, removed by a call to
     * {@link #remove}, or replaced by a call to {@link #put}. The default
     * implementation does nothing.
     *
     * 

The method is called without synchronization: other threads may      * access the cache while this method is executing.      *      * @param evicted true if the entry is being removed to make space, false      *     if the removal was caused by a {@link #put} or {@link #remove}.      * @param newValue the new value for {@code key}, if it exists. If non-null,      *     this removal was caused by a {@link #put}. Otherwise it was caused by      *     an eviction or a {@link #remove}.      */     protected void entryRemoved(boolean evicted, K key, V oldValue, V newValue) {}     /**      * Called after a cache miss to compute a value for the corresponding key.      * Returns the computed value or null if no value can be computed. The      * default implementation returns null.      *      * 

The method is called without synchronization: other threads may      * access the cache while this method is executing.      *      * 

If a value for {@code key} exists in the cache when this method      * returns, the created value will be released with {@link #entryRemoved}      * and discarded. This can occur when multiple threads request the same key      * at the same time (causing multiple values to be created), or when one      * thread calls {@link #put} while another is creating a value for the same      * key.      */     protected V create(K key) {         return null;     }     private int safeSizeOf(K key, V value) {         int result = sizeOf(key, value);         if (result < 0) {             throw new IllegalStateException("Negative size: " + key + "=" + value);         }         return result;     }     /**      * Returns the size of the entry for {@code key} and {@code value} in      * user-defined units.  The default implementation returns 1 so that size      * is the number of entries and max size is the maximum number of entries.      *      * 

An entry's size must not change while it is in the cache.      * 用来计算单个对象的大小,这里默认返回1,一般需要重写该方法来计算对象的大小      * xUtils中创建LruMemoryCache时就重写了sizeOf方法来计算bitmap的大小      * mMemoryCache = new LruMemoryCache(globalConfig.getMemoryCacheSize()) {      *       @Override      *       protected int sizeOf(MemoryCacheKey key, Bitmap bitmap) {      *           if (bitmap == null) return 0;      *           return bitmap.getRowBytes() * bitmap.getHeight();      *       }      *   };      *      */     protected int sizeOf(K key, V value) {         return 1;     }     /**      * Clear the cache, calling {@link #entryRemoved} on each removed entry.      * 清空内存缓存      */     public final void evictAll() {         trimToSize(-1); // -1 will evict 0-sized elements     }     /**      * For caches that do not override {@link #sizeOf}, this returns the number      * of entries in the cache. For all other caches, this returns the sum of      * the sizes of the entries in this cache.      */     public synchronized final int size() {         return size;     }     /**      * For caches that do not override {@link #sizeOf}, this returns the maximum      * number of entries in the cache. For all other caches, this returns the      * maximum sum of the sizes of the entries in this cache.      */     public synchronized final int maxSize() {         return maxSize;     }     /**      * Returns the number of times {@link #get} returned a value.      */     public synchronized final int hitCount() {         return hitCount;     }     /**      * Returns the number of times {@link #get} returned null or required a new      * value to be created.      */     public synchronized final int missCount() {         return missCount;     }     /**      * Returns the number of times {@link #create(Object)} returned a value.      */     public synchronized final int createCount() {         return createCount;     }     /**      * Returns the number of times {@link #put} was called.      */     public synchronized final int putCount() {         return putCount;     }     /**      * Returns the number of values that have been evicted.      */     public synchronized final int evictionCount() {         return evictionCount;     }     /**      * Returns a copy of the current contents of the cache, ordered from least      * recently accessed to most recently accessed.      */     public synchronized final Map snapshot() {         return new LinkedHashMap(map);     }     @Override public synchronized final String toString() {         int accesses = hitCount + missCount;         int hitPercent = accesses != 0 ? (100 * hitCount / accesses) : 0;         return String.format("LruCache[maxSize=%d,hits=%d,misses=%d,hitRate=%d%%]",                 maxSize, hitCount, missCount, hitPercent);     } }



Android 关于使用LruCache缓存你想缓存的数据.

相信大家做开发的时候都知道请求网络数据的重要,但是有一些只用请求一次就过时性的消息比如某些新闻信息,如果我们每次进入新闻界面就从新从网络上获取势必会给用户带来不好的体验,所以我们需要缓存技术来帮我们解决这一问题。

1,LruCache介绍

核心的类是LruCache (此类在android-support-v4的包中提供) 。这个类非常适合用来缓存图片,它的主要算法原理是把最近使用的对象用强引用存储在 LinkedHashMap 中,并且把最近最少使用的对象在缓存值达到预设定值之前从内存中移除。


在过去,我们经常会使用一种非常流行的内存缓存技术的实现,即软引用或弱引用 (SoftReference or WeakReference)。但是现在已经不再推荐使用这种方式了,因为从 Android 2.3 (API Level 9)开始,垃圾回收器会更倾向于回收持有软引用或弱引用的对象,这让软引用和弱引用变得不再可靠。另外,Android 3.0 (API Level 11)中,图片的数据会存储在本地的内存当中,因而无法用一种可预见的方式将其释放,这就有潜在的风险造成应用程序的内存溢出并崩溃。

2,LruCache使用


下面我们就来写一个简单的demo来学习LruCache,效果也是每次请求一次第二次直接从缓存中提取出来不用再次请求网络

/**
 * 缓存json数据
 */
private LruCache mJsonCache;
/**
 * 缓存图片信息
 */
private LruCache mBitmapCache;
public Util() {
    mJsonCache = new LruCache(1 * 1024 * 1024);
    mBitmapCache = new LruCache(2 * 1024 * 1024);
}
/**
 * 添加进入缓存列表
 * 
 * @param key
 * @param value
 */
public void addJsonLruCache(Integer key, String value) {
    mJsonCache.put(key, value);
}
public void addBitmapLruCache(Integer key, Bitmap value) {
    mBitmapCache.put(key, value);
}
/**
 * 从缓存列表中拿出来
 * 
 * @param key
 * @return
 */
public String getJsonLruCache(Integer key) {
    return mJsonCache.get(key);
}
public Bitmap getBitmapLruCache(Integer key) {
    return mBitmapCache.get(key);
}


可以看到我们准备缓存Bitmap与String,只需要拿到信息的时候put进缓存中,需要的时候get出来,是不是非常简单,我们为我们String分配了1m为我们的Bitmap分配了2m空间,这只是我们的demo为了简单这样使用,实际上我们应该更加详细的考虑到底应该为缓存分配多大的空间

// 获取到可用内存的最大值,使用内存超出这个值会引起OutOfMemory异常。 
// LruCache通过构造函数传入缓存值,以KB为单位。 
int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);


一般来说最大值的1/8左右就可以了。

public class MainActivity extends Activity implements OnItemClickListener {
    private static final String LIST_DATA = http://api.yi18.net/top/list;
    private ListView mListView;
    private ArrayAdapter mAdapter;
    private ArrayList mListId;
    private Util util;
 
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        util = new Util();
        mListView = (ListView) findViewById(R.id.list);
        mListId = new ArrayList();
        mAdapter = new ArrayAdapter(this,
                android.R.layout.simple_list_item_1);
        mListView.setAdapter(mAdapter);
        mListView.setOnItemClickListener(this);
        new DownLoadJson().execute(LIST_DATA);
    }
    
这一段就是普通的请求数据添加到ListView中。
    
private void getJsonData(String json) {
        try {
            JSONObject jsonObject = new JSONObject(json);
            if (jsonObject.getBoolean(success)) {
                JSONArray jsonArray = jsonObject.getJSONArray(yi18);
                for (int i = 0; i < jsonArray.length(); i++) {
                    JSONObject jsonObject2 = (JSONObject) jsonArray.opt(i);
                    if (i < 5) {
                        mAdapter.add(jsonObject2.getString(title));
                        mListId.add(jsonObject2.getInt(id));
                    }
                }
            }
        } catch (JSONException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
 
    class DownLoadJson extends AsyncTask {
 
        @Override
        protected String doInBackground(String... params) {
            return util.downLoadJson(params[0]);
        }
 
        @Override
        protected void onPostExecute(String result) {
            if (result != null) {
                getJsonData(result);
            }
        }
 
    }


data-cke-saved-src=/get_pic/2015/09/25/20150925020958802.png

   

我们就简单的取了前五条数据用来模拟我们的新闻,用的是热点热词的Api。

 

3,缓存

@Override
    public void onItemClick(AdapterView arg0, View arg1, int arg2, long arg3) {
        // TODO Auto-generated method stub
        String message = util.getJsonLruCache(mListId.get(arg2));
        Bitmap bitmap = util.getBitmapLruCache(mListId.get(arg2));
 
        if (message != null) {
            intentNewsInfo(arg2, message, bitmap);
        } else {
            intentNewsInfo(arg2, null, null);
        }
 
    }
 
    public void intentNewsInfo(int arg2, String message, Bitmap bitmap) {
        Intent intent = new Intent(MainActivity.this, NewsinfoActivity.class);
        intent.putExtra(message, message);
        intent.putExtra(bitmap, bitmap);
        intent.putExtra(index, arg2);
        intent.putExtra(id, mListId.get(arg2));
        startActivityForResult(intent, 100);
    }


可以看到我们这里先是查找缓存中是否存在数据如果存在直接传给新闻详情界面,如果没有则在第二个界面获取再传回来。

public class NewsinfoActivity extends Activity {
 
    private String NEWS_INFO = http://api.yi18.net/top/show?id=;
    private String imageRes[] = {
            http://d.hiphotos.baidu.com/image/h%3D360/sign=405b763459afa40f23c6c8db9b65038c/562c11dfa9ec8a13508c96e6f403918fa0ecc026.jpg,
            http://c.hiphotos.baidu.com/image/h%3D360/sign=798b4f82caea15ce5eeee60f86013a25/9c16fdfaaf51f3dece3f986397eef01f3a297923.jpg,
            http://f.hiphotos.baidu.com/image/h%3D360/sign=20a94e03940a304e4d22a6fce1c9a7c3/ac4bd11373f082028719ab3848fbfbedab641b29.jpg,
            http://b.hiphotos.baidu.com/image/h%3D360/sign=3a1af7349145d688bc02b4a294c37dab/4b90f603738da977c0f5b82cb351f8198718e3db.jpg,
            http://d.hiphotos.baidu.com/image/h%3D360/sign=75e596560f33874483c5297a610ed937/55e736d12f2eb9381891b2f4d6628535e5dd6f3c.jpg };
    private Intent intent;
    private Util util;
    private int newId, index;
    private ImageView imageView;
    private TextView textView;
    private Bitmap bitmap;
    private String message;
 
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_newsinfo);
        intent = getIntent();
        util = new Util();
        imageView = (ImageView) findViewById(R.id.image);
        textView = (TextView) findViewById(R.id.message);
        newId = intent.getExtras().getInt(id);
        index = intent.getExtras().getInt(index);
        if (intent.getExtras().getString(message) != null) {
            message = intent.getExtras().getString(message);
            bitmap = intent.getParcelableExtra(bitmap);
            textView.setText(Html.fromHtml(message));
            imageView.setImageBitmap(bitmap);
            Toast.makeText(this, 没有访问网络哦, 2000).show();
        } else {
            new DownLoadJson().execute(NEWS_INFO + newId);
            new DownLoadBitmap().execute(imageRes[index]);
            Toast.makeText(this, 访问网络哦, 2000).show();
        }
 
    }
 
    @Override
    public void onBackPressed() {
        Intent dataIntent = new Intent();
        dataIntent.putExtra(message, message);
        dataIntent.putExtra(bitmap, bitmap);
        dataIntent.putExtra(newId, newId);
        setResult(20, dataIntent);
        finish();
        super.onBackPressed();
    }
 
    private void getJsonData(String json) {
        try {
            JSONObject jsonObject = new JSONObject(json);
            if (jsonObject.getBoolean(success)) {
                JSONObject jsonObject2 = new JSONObject(
                        jsonObject.getString(yi18));
                message = jsonObject2.getString(message);
                textView.setText(Html.fromHtml(jsonObject2.getString(message)));
            }
        } catch (JSONException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
 
    class DownLoadJson extends AsyncTask {
 
        @Override
        protected String doInBackground(String... params) {
            return util.downLoadJson(params[0]);
        }
 
        @Override
        protected void onPostExecute(String result) {
            if (result != null) {
                getJsonData(result);
            }
        }
 
    }
 
    class DownLoadBitmap extends AsyncTask {
 
        @Override
        protected Bitmap doInBackground(String... params) {
            // TODO Auto-generated method stub
            return util.downLoadBitmap(params[0]);
        }
 
        @Override
        protected void onPostExecute(Bitmap result) {
            if (result != null) {
                bitmap = result;
                imageView.setImageBitmap(result);
            }
        }
    }
 
}


这就比较清晰明白了,每次我们都把这个界面获取到的信息存到LruCache里面。

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    int newId = data.getExtras().getInt(newId);
    String message = data.getExtras().getString(message);
    Bitmap bitmap = data.getParcelableExtra(bitmap);
    util.addJsonLruCache(newId, message);
    util.addBitmapLruCache(newId, bitmap);
    super.onActivityResult(requestCode, resultCode, data);
}


4,效果

data-cke-saved-src=/get_pic/2015/09/25/20150925021000135.gif


热门栏目