1. 程式人生 > >Android中三級快取實現原理及LruCache 原始碼分析

Android中三級快取實現原理及LruCache 原始碼分析

介紹

oom異常:大圖片導致
圖片的三級快取:記憶體、磁碟、網路
下面通過一張圖來了解下三級快取原理:

程式碼:

public class Davince {
    //使用固定執行緒池優化 
    private static  ExecutorService threadPool = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * 2);
    private static final String TAG = "Davince";
    private static Davince sInstance = new
Davince(); public static Context sContext; public static Davince with(Context context) { //執行緒池多少個執行緒:有當前的裝置的cpu核數決定 sContext = context; return sInstance; } public Requestor load(String url) { return new Requestor(url); } //給 LruCache分配最大的使用記憶體 //使用lru快取必須重寫sizeOf方法
public static LruCache<String, Bitmap> mCaches = new LruCache<String, Bitmap>((int) (Runtime.getRuntime().maxMemory() / 3)) { @Override protected int sizeOf(String key, Bitmap value) { //快取當前要存放的Bitmap的大小 return value.getByteCount(); } }; //圖片的記憶體快取 以前的快取方式,現在不用
//private Map<String, SoftReference<Bitmap>> mCaches = new HashMap<String, SoftReference<Bitmap>>(); public static final Handler sHandler = new Handler(Looper.getMainLooper()); //載入圖片的類 public class Requestor { private String mUrl; public Requestor(String mUrl) { this.mUrl = mUrl; } private int mErrorResId; public Requestor error(int errorResId) { this.mErrorResId = errorResId; return this; } //載入圖片:三級快取 public void into(ImageView target) { //從記憶體載入圖片:設計記憶體快取 //Bitmap bitmap = mCaches.get(mUrl); //從Lru獲取 Bitmap bitmap = mCaches.get(mUrl); //從軟引用獲取 捨棄不用 // SoftReference<Bitmap> reference = mCaches.get(mUrl); // if (reference!=null) { // // bitmap = reference.get(); // } //記憶體有圖片 if (bitmap != null) { Log.i(TAG, "從記憶體獲取"); target.setImageBitmap(bitmap); return; } //記憶體沒有圖片,從disk獲取 bitmap = getBitmapFromDisk(mUrl); //磁碟有圖片: if (bitmap != null) { Log.i(TAG, "從磁盤獲取"); //存放到記憶體 //存放到Lru快取 mCaches.put(mUrl, bitmap); //mCaches.put(mUrl, bitmap); // //存放到軟引用 捨棄不用 // mCaches.put(mUrl, new SoftReference<Bitmap>(bitmap)); //顯示圖片 target.setImageBitmap(bitmap); return; } //磁碟沒有圖片,從網路獲取 //每次new Thread效能消耗會很大 //new Thread(new LoadBitmapTask(mUrl, target, mErrorResId)).start(); //從執行緒池中 開啟執行緒 threadPool.execute(new LoadBitmapTask(mUrl, target, mErrorResId)); } } /* 載入的圖片的任務 */ public class LoadBitmapTask implements Runnable { private String mUrl; private ImageView mImageView; private int mErrorResId; private Bitmap bitmap; public LoadBitmapTask(String mUrl, ImageView mImageView, int mErrorResId) { this.mUrl = mUrl; this.mImageView = mImageView; this.mErrorResId = mErrorResId; } @Override public void run() { //從網路載入圖片 try { URLConnection urlConnection = new URL(mUrl).openConnection(); urlConnection.setReadTimeout(5000); urlConnection.setConnectTimeout(5000); urlConnection.connect(); //獲取到的圖片 InputStream inputStream = urlConnection.getInputStream(); bitmap = BitmapFactory.decodeStream(inputStream); //從網路載入成功 //存放到磁碟 saveBitmapToDisk(mUrl, bitmap); Log.i(TAG, "從網路獲取"); //儲存的記憶體 //存放到Lru快取 mCaches.put(mUrl, bitmap); //mCaches.put(mUrl, bitmap); // //存放到軟引用 捨棄不用 // mCaches.put(mUrl, new SoftReference<Bitmap>(bitmap)); //顯示圖片 跳轉主執行緒顯示 sHandler.post(new Runnable() { @Override public void run() { mImageView.setImageBitmap(bitmap); } }); } catch (Exception e) { e.printStackTrace(); //出錯了,載入錯誤的圖片 sHandler.post(new Runnable() { @Override public void run() { mImageView.setImageResource(mErrorResId); } }); } } } private void saveBitmapToDisk(String mUrl, Bitmap bitmap) { try { //檔案路徑加MD5加密檔名 File file = new File(getMyCacheDir(), MD5Utils.encode(mUrl)); FileOutputStream fileOutputStream = new FileOutputStream(file); //包Bitmap儲存到檔案 bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fileOutputStream); } catch (FileNotFoundException e) { e.printStackTrace(); } } private File getMyCacheDir() { //獲取apk: data/data/包民/cache File dir = new File(sContext.getCacheDir(), "davince-cache"); if (!dir.exists()) { dir.mkdirs(); } return dir; } //從磁盤獲取圖片 private Bitmap getBitmapFromDisk(String mUrl) { File file = new File(getMyCacheDir(), MD5Utils.encode(mUrl)); return BitmapFactory.decodeFile(file.getAbsolutePath()); } }

曾經,在各大快取圖片的框架沒流行的時候。有一種很常用的記憶體快取技術:SoftReference 和 WeakReference(軟引用和弱引用)。
但是走到了 Android 2.3(Level 9)時代,垃圾回收機制更傾向於回收 SoftReference 或 WeakReference 的物件。後來,
又來到了 Android3.0,圖片快取在內容中,因為不知道要在是什麼時候釋放記憶體,沒有策略,沒用一種可以預見的場合去將其釋放。這就造成了記憶體溢位。
優化點:

1.用執行緒池去管理執行緒,不用每次都去new 一個執行緒
2.Bitmap的快取優化:Bitmap佔用記憶體很大,如果不能被即時的回收就會oom
強引用:
軟引用:SoftReference, 用軟引用包裹Bitmap物件,當記憶體不足的時候就算Bitmap有引用也會回收Bitmap
弱引用:WeakReference 只要垃圾回收器以啟動就會回收弱引用的物件
虛引用:PhantomReference: 如果物件用虛引用包裹,物件只要一使用完就被回收
安卓:3.0之後Google不推薦使用了:art : android runtime
3、改成使用LRU快取:
1.設定最大的使用記憶體:
2.重寫sizeof方法:因為存放物件的時候LRU會計算記憶體的使用大小進行累加,當超出設定的最大使用記憶體的時候會把使用最少的物件移除

LRU 是 Least Recently Used 最近最少使用演算法。

接下來我們看下LruCache原始碼:

首先我們開下他的英文註釋,這是我們瞭解一個類的作用的最快最有效的方式,以下是LruCache類的官方註釋:

A cache that holds strong references to a limited number of values. Each time a value is accessed, it is moved to the head of a queue. When a value is
added to a full cache, the value at the end of that queue is evicted and may
become eligible for garbage collection.

這段話什麼意思尼?其實就是設個類是個快取作用的類,裡面儲存的資料是強引用,當每次訪問裡面的一個值的時候,那麼就將該數值放到佇列的頭部,另一方面,當新增的資料充滿整個佇列的時候,就將佇列的末尾資料移除,該移除的資料才有可能被系統進行垃圾回收。

其實簡單一句話描述LruCache的功能就是採用佇列方式儲存,採用最近最少使用演算法,維護一個快取。

首先我們看下原始碼部分的建構函式:

 public LruCache(int maxSize) {
        if (maxSize <= 0) {
            throw new IllegalArgumentException("maxSize <= 0");
        }
        this.maxSize = maxSize;//設定Lrucache的最大容量
        //初始容量為零,0.75是載入因子,表示容量達到最大容量的75%的時候會把記憶體增加一半。最後這個引數至關重要。
        //表示訪問元素的排序方式,true表示按照訪問順序排序,false表示按照插入的順序排序。(實現Lru演算法的關鍵)
        this.map = new LinkedHashMap<K, V>(0, 0.75f, true);//存放資料的集合
    }

下面再看下 put方法

 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);
            if (previous != null) {
                size -= safeSizeOf(key, previous);
            }
        }

        if (previous != null) {
            entryRemoved(false, key, previous, value);
        }

        trimToSize(maxSize);
        return previous;
    }

首先把size增加,然後判斷是否以前已經有元素,如果有,就更新當前的size,並且呼叫entryRemoved方法,entryRemoved是一個空實現,
如果我們使用LruCache的時候需要掌握元素移除的資訊,可以重寫這個方法。最後就會呼叫trimToSize,來調整集合中的內容。

接下來我們再看下 trimToSize方法:

 public 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!");
                }

                if (size <= maxSize) {
                    break;
                }

                Map.Entry<K, V> toEvict = map.eldest();//獲得最老資料
                if (toEvict == null) {
                    break;
                }

                key = toEvict.getKey();
                value = toEvict.getValue();
                map.remove(key);
                size -= safeSizeOf(key, value);
                evictionCount++;
            }

            entryRemoved(true, key, value, null);
        }
    }

該方法的目的就是刪除最老的條目,直到剩餘條目的總數達到或低於要求的大小。具體就是:這個方法是一個無限迴圈,跳出迴圈的條件是,size < maxSize或者 map 為空。主要的功能是判斷當前容量時候已經超出最大的容量,如果超出了maxSize的話,就會迴圈移除map中的第一個元素,直到達到跳出迴圈的條件。由上面的分析知道,map中的第一個元素就是最近最少使用的那個元素。

我們再看下 get方法:

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) {
                hitCount++;
                return mapValue;
            }
            missCount++;
        }

        /*
         * 嘗試建立一個值。 這可能需要很長時間,並且create()返回時map集合可能會有所不同。 
         * 如果在create()正在工作時將衝突值新增到地圖,則我們將該值保留在地圖中並釋放建立的值。
         */

        V createdValue = create(key);
        if (createdValue == null) {
            return null;
        }

        synchronized (this) {
            createCount++;
            mapValue = map.put(key, createdValue);

            if (mapValue != null) {
                // There was a conflict so undo that last put
                map.put(key, mapValue);
            } else {
                size += safeSizeOf(key, createdValue);
            }
        }

        if (mapValue != null) {
            entryRemoved(false, key, createdValue, mapValue);
            return mapValue;
        } else {
            trimToSize(maxSize);
            return createdValue;
        }
    }

這個方法就先通過key來獲取value,如果能獲取到就就直接返回,獲取不到的話,就呼叫create()方法建立一個,事實上,
如果我們不重寫這個create方法的話是return null的,所以整個流程就是獲取得到就直接返回,獲取不到就返回null。

remove方法:

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;
    }

就是使用remove方法移除一個元素。

相關推薦

Android三級快取實現原理LruCache 原始碼分析

介紹 oom異常:大圖片導致 圖片的三級快取:記憶體、磁碟、網路 下面通過一張圖來了解下三級快取原理: 程式碼: public class Davince { //使用固定執行緒池優化 private static Exec

JavaHashMap底層實現原理(JDK1.8)原始碼分析

在JDK1.6,JDK1.7中,HashMap採用位桶+連結串列實現,即使用連結串列處理衝突,同一hash值的連結串列都儲存在一個連結串列裡。但是當位於一個桶中的元素較多,即hash值相等的元素較多時,通過key值依次查詢的效率較低。而JDK1.8中,HashMap採用位桶+

(轉載)JavaHashMap底層實現原理(JDK1.8)原始碼分析

近期在看一些java底層的東西,看到一篇分析hashMap不錯的文章,跟大家分享一下。 在JDK1.6,JDK1.7中,HashMap採用位桶+連結串列實現,即使用連結串列處理衝突,同一hash值的連結串列都儲存在一個連結串列裡。但是當位於一個桶中的元素較多,即hash值

Android ListView動畫特效實現原理源代碼

stat 每一個 應該 所有 ner haar .get tde pri Android 動畫分三種,當中屬性動畫為我們最經常使用動畫,且能滿足項目中開發差點兒所有需求,google官方包支持3.0+。我們能夠引用三方包nineoldandr

JDK8的HashMap實現原理原始碼分析

本篇所述原始碼基於JDK1.8.0_121 在寫上一篇線性表的文章的時候,筆者看的是Android原始碼中support24中的Java程式碼,當時發現這個ArrayList和LinkedList的原始碼和Java官方的沒有什麼區別,然而在閱讀HashMap原

Android三種動畫實現原理使用

Android動畫目前分為三種:Frame Animation 幀動畫,通過順序播放一系列影象從而產生動畫效果,。圖片過多時容易造成OOM(Out Of Memory記憶體用完)異常。Tween Animation 補間動畫(又叫view動畫),是通過對場景裡的物件不斷做影象

Java synchronized 的實現原理偏向鎖、輕量級鎖、自旋鎖、公平鎖簡介

    在多執行緒程式設計中,synchronized 一直都是元老級別的存在,很多人都稱之為重量級鎖。本文來簡單介紹synchronized的實現原理,以及為減少獲得鎖和釋放鎖所帶來的效能損耗而引進的偏向鎖與輕量級鎖。     Java中使用synchronized來實現

JDK1.8ArrayList的實現原理原始碼分析

一、概述              ArrayList是Java開發中使用比較頻繁的一個類,通過對原始碼的解讀,可以瞭解ArrayList的內部結構以及實現方法,清楚它的優缺點,以便我們在程式設計時靈活運用。 二、原始碼分析 2.1 類結構  JDK1.8原始碼中的A

Go定時器實現原理原始碼解析

> 轉載請宣告出處哦~,本篇文章釋出於luozhiyun的部落格:https://www.luozhiyun.com > > 本文使用的go的原始碼15.7,需要注意的是由於timer是1.14版本進行改版,但是1.14和1.15版本的timer並無很大區別 我在春節期間寫了一篇文章有關時間輪的:https

ReentrantLock實現原理源碼分析

獲取 累加 還在 set 共享變量 font except 區別 bool   ReentrantLock是Java並發包中提供的一個可重入的互斥鎖。ReentrantLock和synchronized在基本用法,行為語義上都是類似的,同樣都具有可重入性。只不過相比原生的S

1.Java集合-HashMap實現原理源碼分析

int -1 詳細 鏈接 理解 dac hash函數 順序存儲結構 對象儲存   哈希表(Hash Table)也叫散列表,是一種非常重要的數據結構,應用場景及其豐富,許多緩存技術(比如memcached)的核心其實就是在內存中維護一張大的哈希表,而HashMap的實

HashMap實現原理源碼分析

響應 應用場景 取模運算 圖片 mat 直接 maximum 計算 時間復雜度 哈希表(hash table)也叫散列表,是一種非常重要的數據結構,應用場景及其豐富,許多緩存技術(比如memcached)的核心其實就是在內存中維護一張大的哈希表,而HashMap的實現原理也

ReentrantLock在JavaLock的實現原理拿鎖過程分析

import java.util.concurrent.locks.ReentrantLock; public class App { public static void main(String[] args) throws Exception {

HashMap實現原理源碼分析(jdk1.8)

for 離散 兩種 收集器 sta 循環 false lar rem HashMap底層由數組+鏈表+紅黑樹組成,可接受null值,非線程安全 1、基本屬性 transient Node<K,V>[] table; //hashmap

synchronized實現原理ReentrantLock原始碼

#### synchronized ###### synchronized的作用範圍 ``` public class SynchronizedTest { // 例項方法,方法訪問標誌ACC_SYNCHRONIZED,鎖物件是物件例項 public synchronized void t

Android通過NTP伺服器獲取時間功能原始碼分析

1 相關檔案: frameworks\base\services\java\com\android\server\ SystemServer.java frameworks\base\services\java\com\android\server\ NetworkTime

AndroidViewGroup、View事件分發機制原始碼分析總結(雷驚風)

1.概述         很長時間沒有回想Android中的事件分發機制了,開啟目前的原始碼發現與兩三年前的實現程式碼已經不一樣了,5.0以後發生了變化,更加複雜了,但是萬變不離其宗,實現原理還是一樣的,在這裡將5.0以前的時間分發機制做一下原始碼剖析及總結。會涉及到幾個方

java動態代理基本原理proxy原始碼分析

本系列文章主要是博主在學習spring aop的過程中瞭解到其使用了java動態代理,本著究根問底的態度,於是對java動態代理的本質原理做了一些研究,於是便有了這個系列的文章 為了儘快進入正題,這裡先跳過spring aop和java動態代理的使用流程的講解,這部分內容後面再單獨寫文章整理   不

Android原始碼分析 - LRUCache快取實現原理

一、Android中的快取策略 一般來說,快取策略主要包含快取的新增、獲取和刪除這三類操作。如何新增和獲取快取這個比較好理解,那麼為什麼還要刪除快取呢?這是因為不管是記憶體快取還是硬碟快取,它們的快取大小都是有限的。當快取滿了之後,再想其新增快取,這個時候就需要刪除一些舊的快取

Android 圖片三級快取載入框架原理解析與程式碼實現

本文主要介紹三級快取的原理解析與實現方式。以前一直覺得三級快取圖片載入是一個很難理解的東西,但是自己看了一下午再試著寫了一遍之後感覺還是隻要沉下心思考還時很容易熟悉掌握的。 所謂三級快取:首先是記憶體-檔案(外存)-網路三級快取機制。 首先: 框架需要一個接入方法NGIm