三級緩存的提出就是為了提升用戶體驗。當我們第一次打開應用獲取圖片時,先到網路去下載圖片,然後依次存入記憶體緩存,磁碟緩存,當我們再一次需要用到剛纔下載的這張圖片時,就不需要再重覆的到網路上去下載,直接可以從記憶體緩存和磁碟緩存中找,由於記憶體緩存速度較快,我們優先到記憶體緩存中尋找該圖片,如果找到則運用,如 ...
三級緩存的提出就是為了提升用戶體驗。當我們第一次打開應用獲取圖片時,先到網路去下載圖片,然後依次存入記憶體緩存,磁碟緩存,當我們再一次需要用到剛纔下載的這張圖片時,就不需要再重覆的到網路上去下載,直接可以從記憶體緩存和磁碟緩存中找,由於記憶體緩存速度較快,我們優先到記憶體緩存中尋找該圖片,如果找到則運用,如果沒有找到(記憶體緩存大小有限),那麼我們再到磁碟緩存中去找。只要我們合理的去協調這三層緩存運用,便可以提升應用性能。三級緩存指的是:記憶體緩存、本地緩存、網路緩存。其各自的特點是記憶體緩存速度快, 優先讀取,本地緩存速度其次, 記憶體沒有,讀本地,網路緩存速度最慢, 本地也沒有,才訪問網路。對於網路緩存理解起來較為容易直接從網路中獲取資源,本地緩存可以存在SD卡中,記憶體緩存一般存在數組或集合中。需要在註意的是,數組和集合的生命周期依賴於它存在的activity中,因此當程式退出,一般情況下數組和集合中的資源會被釋放。在具體瞭解三級緩存的工作原理之前有必要先介紹幾個概念。
實例和對象:
對象是類的一個實例,創建對象的過程也叫類的實例化。對象是以類為模板來創建的。這樣在安卓的底部就會用堆來存儲對象的實例,棧來存儲類的對象。引用是指某些對象的實例化需要其它的對象實例,比如ImageView的實例化就需要Context對象,就是表示ImageView對於Context持有引用(ImageView holds a reference to Context)。
垃圾回收機制(GC):
對虛擬機可用記憶體空間,即堆空間中的對象進行識別,如果對象正在被引用,那麼稱其為存活對象,反之,如果對象不再被引用,則為垃圾對象,可以回收其占據的空間,用於再分配。更細緻來講就是對於GC來說,當程式員創建對象時,GC就開始監控這個對象的地址、大小以及使用情況。通常GC採用有向圖的方式記錄並管理堆中的所有對象,通過這種方式確定哪些對象時“可達”,哪些對象時“不可達”。當對象不可達的時候,即對象不再被引用的時候,就會被垃圾回收。該機制對虛擬機中的記憶體進行標記,並確定哪些記憶體需要回收,根據一定的回收策略,自動的回收記憶體,永不停息(Nerver Stop)的保證虛擬機中的記憶體空間,防止出現記憶體泄露和溢出問題。
記憶體泄露:
當不再需要某個實例後,但是這個對象卻仍然被引用,這個情況就叫做記憶體泄露(Memory Leak)。安卓虛擬機為每一個應用分配一定的記憶體空間,當記憶體泄露到達一定的程度就會造成記憶體溢出。
記憶體的引用:
記憶體的引用級別包括,強引用、軟引用、弱引用、虛引用。強引用是預設的引用方式, 即使記憶體溢出,也不會回收。軟引用(softReference), 記憶體不夠時, 會考慮回收。 弱引用 (WeakReference)記憶體不夠時, 更會考慮回收。虛引用(PhantomReference) 記憶體不夠時, 最優先考慮回收! 一般我們常用到的引用就是強引用,比如引用創建一個成員變數裡面的引用。對於GC來說, SoftReference的強度明顯低於 SrongReference。SoftReference修飾的引用,其告訴GC:我是一個 軟引用,當記憶體不足的時候,我指向的這個記憶體是可以給你釋放掉的。一般對於這種占用記憶體資源比較大的,又不是必要的變數;或者一些占用大量記憶體資源的一些緩存的變數,就需要考慮 SoftReference。對於GC來說, WeakReference 的強度又明顯低於 SoftReference 。 WeakReference 修飾的引用,其告訴GC:我是一個弱引用,對於你的要求我沒有話說,我指向的這個記憶體是可以給你釋放掉的。虛引用其實和上面講到的各種引用不是一回事的,他主要是為跟蹤一個對象何時被GC回收。在android裡面也是有用到的:FileCleaner.java 。這些避免記憶體溢出的引用方式在Android 2.3+的版本上已經不再起太大作用, 因為垃圾回收器會頻繁回收非強引用的對象, Android官方建議使用LRUCache。所以當我們用軟引用進行記憶體緩存時會發現記憶體中的資源會被系統頻繁回收。最終是從本地進行讀數據。
這樣我們就能很好的理解要三級緩存了。首先,在記憶體讀數據。記憶體中讀數據需要用到最近最少引用演算法(lrucache)。Lrucache演算法要求為new LruCache<String, Bitmap>()傳入一個分配給軟體的最大記憶體,同時重寫sizeof()方法,計算每一張圖片的大小。這樣就可以直接調用LruCache的put()和get()方法。當發現記憶體中沒用數據是時,找到SD卡中的存儲文件。通過Bitmap的compress()方法向文件夾中寫數據,通過點陣圖工廠BitmapFactory的decodeStream()讀取數據,同時可以為decodeStream()方法傳入options參數,縮小圖片。最後如果,本地仍然沒有獲取數據,在從網路獲取。網路獲取數據可以用非同步任務來執行(耗時操作不能再主線程中執行)。非同步任務需要重寫onPostExecute()方法和doInBackground()方法。doInBackground()方法中訪問網路,這裡用到的是Httpurlconnection,通過連接得到輸入流,利用點陣圖工廠轉換成點陣圖,返回。onPostExecute()方法在doInBackground()方法執行後執行,傳入的參數數doInBackground()方法的返回值。
接下來是代碼實,這是我調用工具類的寫法:
import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.widget.ImageView; import com.example.huang.demo.utils.CacheUtils; public class MainActivity extends AppCompatActivity { private static final String TAG = "MainActivity"; private ImageView iv; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); getBitmap(); } private void getBitmap() { iv = (ImageView) findViewById(R.id.iv); CacheUtils utils = new CacheUtils(); utils.diaplay(iv,"http://192.168.23.48:8080/test.jpg"); } }
佈局文件只要一個imageview就不單獨寫出來,直接來看緩存工具類:
import android.graphics.Bitmap; import android.util.Log; import android.widget.ImageView; /** * Created by huang on 2016/12/3. */ public class CacheUtils { private static final String TAG = "CacheUtils"; private MemoryCacheUtils mMemoryCacheUtils; private LocalCacheUtils mLocalCacheUtils; private NetCacheUtils mNetCacheUtils; public CacheUtils() { mMemoryCacheUtils = new MemoryCacheUtils(); mLocalCacheUtils = new LocalCacheUtils(); mNetCacheUtils = new NetCacheUtils(mMemoryCacheUtils, mLocalCacheUtils); } public void diaplay(ImageView imageView, String url) { //記憶體緩存 生命周期同調用者 Bitmap bitmap = mMemoryCacheUtils.getBitmapToMemory(url); if (bitmap != null) { imageView.setImageBitmap(bitmap); Log.i(TAG, "diaplay: 221111111111"); return; } //本地緩存 bitmap = LocalCacheUtils.getBitmapToLoacl(url); if (bitmap != null) { Log.i(TAG, "diaplay: 1111111"); imageView.setImageBitmap(bitmap); mMemoryCacheUtils.putBitmapToMemory(bitmap, url); return; } //網路緩存 mNetCacheUtils.getBitmapFromNet(imageView, url); } }
記憶體緩存的工具類
import android.graphics.Bitmap; import android.util.Log; import android.util.LruCache; import static android.content.ContentValues.TAG; /** * Created by huang on 2016/12/3. */ public class MemoryCacheUtils { private LruCache<String, Bitmap> mMemoryCache; public MemoryCacheUtils() { int maxmemory = (int) Runtime.getRuntime().maxMemory(); Log.i(TAG, "MemoryCacheUtils: " + maxmemory); mMemoryCache = new LruCache<String, Bitmap>(maxmemory / 8) { @Override protected int sizeOf(String key, Bitmap value) { return value.getRowBytes() * value.getHeight(); } }; } public void putBitmapToMemory(Bitmap bitmap, String url) { Log.i(TAG, "putBitmapToMemory: "); mMemoryCache.put(url, bitmap); } public Bitmap getBitmapToMemory(String url) { Log.i(TAG, "getBitmapToMemory: "); Bitmap bitmap = mMemoryCache.get(url); return bitmap; } }
本地緩存(SD卡)的工具類:
import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.os.Environment; import android.util.Log; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import static android.content.ContentValues.TAG; /** * Created by huang on 2016/12/3. */ public class LocalCacheUtils { public static void putBitmapToLoacl(Bitmap bitmap, String url) { String encode = Md5Utils.encode(url); File file = new File(Environment.getExternalStorageDirectory(), encode); Log.i(TAG, "putBitmapToLoacl: " + file.toString()); File parent = file.getParentFile(); if (!parent.exists()) { parent.mkdirs(); } try { bitmap.compress(Bitmap.CompressFormat.JPEG, 100, new FileOutputStream(file)); } catch (FileNotFoundException e) { e.printStackTrace(); } } public static Bitmap getBitmapToLoacl(String url) { String encode = Md5Utils.encode(url); File file = new File(Environment.getExternalStorageDirectory(), encode); if (file.exists()) { BitmapFactory.Options opts = new BitmapFactory.Options(); opts.inSampleSize = 3; try { Bitmap bitmap = BitmapFactory.decodeStream(new FileInputStream(file), null, opts); return bitmap; } catch (FileNotFoundException e) { e.printStackTrace(); return null; } } return null; } }
網路緩存的工具類:
import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.os.AsyncTask; import android.widget.ImageView; import java.io.IOException; import java.io.InputStream; import java.net.HttpURLConnection; import java.net.URL; /** * Created by huang on 2016/12/3. */ public class NetCacheUtils { private MemoryCacheUtils mMemoryCacheUtils; private LocalCacheUtils mLocalCacheUtils; public NetCacheUtils(MemoryCacheUtils mMemoryCacheUtils, LocalCacheUtils mLocalCacheUtils) { this.mLocalCacheUtils = mLocalCacheUtils; this.mMemoryCacheUtils = mMemoryCacheUtils; } public void getBitmapFromNet(ImageView imageView, String url) { imageView.setTag(url); Bitmaptask task = new Bitmaptask(); task.execute(imageView, url); } class Bitmaptask extends AsyncTask<Object, Void, Bitmap> { private HttpURLConnection urlConnection; private ImageView imageView; private String url; @Override protected void onPostExecute(Bitmap bitmap) { if (bitmap != null) { if (url.equals(imageView.getTag())) { imageView.setImageBitmap(bitmap); System.out.print("onPostExecute"); mLocalCacheUtils.putBitmapToLoacl(bitmap, url); mMemoryCacheUtils.putBitmapToMemory(bitmap, url); } } } @Override protected Bitmap doInBackground(Object[] params) { imageView = (ImageView) params[0]; url = (String) params[1]; Bitmap bitmap = downloadFromNet(url); return bitmap; } private Bitmap downloadFromNet(String url) { try { urlConnection = (HttpURLConnection) new URL(url).openConnection(); urlConnection.setConnectTimeout(5000); urlConnection.setRequestMethod("GET"); int responseCode = urlConnection.getResponseCode(); if (responseCode == 200) { InputStream inputStream = urlConnection.getInputStream(); BitmapFactory.Options opts = new BitmapFactory.Options(); opts.inSampleSize = 2; Bitmap bitmap = BitmapFactory.decodeStream(inputStream, null, opts); return bitmap; } } catch (IOException e) { e.printStackTrace(); return null; } finally { urlConnection.disconnect(); } return null; } } }
本地緩存中用到的MD5Utils工具類:
import java.security.MessageDigest; /** * Created by huang on 2016/12/3. */ public class Md5Utils { public static String encode(String pwd) { try { MessageDigest digest = MessageDigest.getInstance("md5"); byte[] bs = digest.digest(pwd.getBytes()); StringBuilder sb = new StringBuilder(); for (byte b : bs) { int number = b & 0xff; String str = Integer.toHexString(number); if (str.length() == 1) { sb.append("0"); } sb.append(number); } return sb.toString(); } catch (Exception e) { e.printStackTrace(); return null; } } }
最會別忘了添加許可權:
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />