Android的圖片緩存ImageCache(轉)

爲何要作緩存?      java

        在UI界面加載一張圖片時很簡單,然而若是須要加載多張較大的圖像,事情就會變得更加複雜。在許多狀況下(如ListView、GridView或ViewPager等的組件),屏幕上的圖片的總數伴隨屏幕的滾動會大大增長,且基本上是無限的。緩存

       爲了使內存使用保持在穩定範圍內,防止出現OOM,這些組件會在子view畫出屏幕後,對其進行資源回收,並從新顯示新出現的圖片,垃圾回收機制會釋放掉再也不顯示的圖片的內存空間。可是這樣頻繁地處理圖片的加載和回收不利於操做的流暢性,而內存或者磁盤的Cache就會幫助解決這個問題,實現快速加載已加載的圖片。
       在緩存上,主要有兩種級別的Cache:LruCache和DiskLruCache。 前者是基於內存的,後者是基於磁盤的。

如何在內存中作緩存?app

       經過內存緩存能夠快速加載緩存圖片,但會消耗應用的內存空間。LruCache類(經過兼容包能夠支持到sdk4)很適合作圖片緩存,它經過LinkedHashMap保持圖片的強引用方式存儲圖片,當緩存空間超過設置定的限值時會釋放掉早期的緩存。異步

  注:在過去,經常使用的內存緩存實現是經過SoftReference或WeakReference,但不建議這樣作。從Android2.3(API等級9)垃圾收集器開始更積極收集軟/弱引用,這使得它們至關無效。此外,在Android 3.0(API等級11)以前,存儲在native內存中的可見的bitmap不會被釋放,可能會致使應用程序暫時地超過其內存限制並崩潰。ide

 

爲了給LruCache設置合適的大小,須要考慮如下幾點因素:性能

  • 你的應用中空閒內存是多大?測試

  • 你要在屏幕中一次顯示多少圖片? 你準備多少張圖片用於顯示?大數據

  • 設備的屏幕大小與density 是多少?超高屏幕density的設備(xhdpi)像Galaxy Nexus 比 Nexus S (hdpi)這樣的設備在緩存相同的圖片時須要更大的Cache空間。ui

  • 圖片的大小和屬性及其須要佔用多少內存空間?this

  • 圖片的訪問頻率是多少? 是否比其餘的圖片使用的頻率高?若是這樣你可能須要考慮將圖片長期存放在內存中或者針對不一樣類型的圖片使用不一樣的緩存策略。

  • 如何平衡質量與數量,有事你可能會存儲一些經常使用的低質量的圖片用戶顯示,而後經過異步線程加載高質量的圖片。

圖片緩存方案沒有固定的模式使用全部的的應用,你須要根據應用的具體應用場景進行分析,選擇合適的方案來作,緩存過小不能發揮緩存的優點,太大可能佔用過多的內存,下降應用性能,或者發生內存溢出異常,

下面是一個使用LruCache的例子:

private LruCache mMemoryCache; 
                                                                         
@Override
protected void onCreate(Bundle savedInstanceState) 
{ 
    ... 
    // Get memory class of this device, exceeding this amount will throw an OutOfMemory exception. 
    final int memClass = ((ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE)).getMemoryClass(); 
                                                                         
    // Use 1/8th of the available memory for this memory cache. 
    final int cacheSize = 1024 * 1024 * memClass / 8; 
                                                                         
    mMemoryCache = new LruCache(cacheSize) 
    { 
        @Override
        protected int sizeOf(String key, Bitmap bitmap) 
        { 
            // The cache size will be measured in bytes rather than number of items. 
            return bitmap.getByteCount(); 
        } 
    }; 
    ... 
} 
                                                                         
public void addBitmapToMemoryCache(String key, Bitmap bitmap) 
{ 
    if (getBitmapFromMemCache(key) == null) 
    { 
        mMemoryCache.put(key, bitmap); 
    } 
} 
                                                                         
public Bitmap getBitmapFromMemCache(String key) 
{ 
    return mMemoryCache.get(key); 
}

  注意:在這個例子中,應用八分之一的內存分配給圖片緩存,在普通/hdpi設備中大約爲4MB(32/8)。GirdView全屏時在800x480分辨率的設備中須要1.5M圖片空間(800*480*4 bytes),這樣就能夠在內存中緩存2.5屏的圖片。

 

運用LruCache向ImageView添加圖片時首先先檢查圖片是否存在,若是在直接更行ImageView,不然經過後臺線程加載圖片:

public void loadBitmap(int resId, ImageView imageView) 
{ 
    final String imageKey = String.valueOf(resId); 
                                                         
    final Bitmap bitmap = getBitmapFromMemCache(imageKey); 
    if (bitmap != null) 
    { 
        imageView.setImageBitmap(bitmap); 
    } 
    else 
    { 
        imageView.setImageResource(R.drawable.image_placeholder); 
        BitmapWorkerTask task = new BitmapWorkerTask(imageView); 
        task.execute(resId); 
    } 
}

 

BitmapWorkerTask須要將將加載的圖片添加到緩存中:

class BitmapWorkerTask extends AsyncTask 
{ 
    ... 
    // Decode image in background. 
    @Override
    protected Bitmap doInBackground(Integer... params) 
    { 
        final Bitmap bitmap = decodeSampledBitmapFromResource(getResources(), params[0], 100, 100)); 
        addBitmapToMemoryCache(String.valueOf(params[0]), bitmap); 
        return bitmap; 
    } 
    ... 
}

 

如何使用磁盤緩存?

內存緩存對訪問最近使用的圖片時很高效,可是你不能保證它一直會在緩存中。像GirdView這樣大數據量的組件很容易充滿內存緩存。你的應用可能會被「來電」打斷,在後臺時可能會被殺掉,內存緩存就會失效,一旦用戶從新回到應用中時,你須要從新處理每一個圖片。

在這種狀況下咱們能夠運用磁盤緩存存儲已處理的圖片,當圖片再也不內存中時,減小從新加載的時間,固然從磁盤加載圖片時要比內存中慢,須要在後臺線程中作,由於磁盤的讀取時間是未知的。

注意:若是你常常訪問圖片,ContentProvider應該是存儲圖片的好地方,如:Gallery圖片管理應用。

下面是一個簡單的DiskLruCache實現。然而推薦的實現DiskLruCache方案請參考Android4.0中(libcore/luni/src/main/java/libcore/io/DiskLruCache.java)源碼。本文使用的是以前版本中的簡單實現(Quick Search中是另外的實現).

顯示是簡單實現DiskLruCache更新後的例子:

private DiskLruCache mDiskCache; 
private static final int DISK_CACHE_SIZE = 1024 * 1024 * 10; // 10MB 
private static final String DISK_CACHE_SUBDIR = "thumbnails"; 
                                   
@Override
protected void onCreate(Bundle savedInstanceState) 
{ 
    ... 
    // Initialize memory cache 
    ... 
    File cacheDir = getCacheDir(this, DISK_CACHE_SUBDIR); 
    mDiskCache = DiskLruCache.openCache(this, cacheDir, DISK_CACHE_SIZE); 
    ... 
} 
                                   
class BitmapWorkerTask extends AsyncTask 
{ 
    ... 
    // Decode image in background. 
    @Override
    protected Bitmap doInBackground(Integer... params) 
    { 
        final String imageKey = String.valueOf(params[0]); 
                                   
        // Check disk cache in background thread 
        Bitmap bitmap = getBitmapFromDiskCache(imageKey); 
                                   
        if (bitmap == null) 
        { 
            // Not found in disk cache 
            // Process as normal 
            final Bitmap bitmap = decodeSampledBitmapFromResource(getResources(), params[0], 100, 100)); 
        } 
                                   
        // Add final bitmap to caches 
        addBitmapToCache(String.valueOf(imageKey, bitmap); 
                                   
        return bitmap; 
    } 
    ... 
} 
                                   
public void addBitmapToCache(String key, Bitmap bitmap) 
{ 
    // Add to memory cache as before 
    if (getBitmapFromMemCache(key) == null) 
    { 
        mMemoryCache.put(key, bitmap); 
    } 
                                   
    // Also add to disk cache 
    if (!mDiskCache.containsKey(key)) 
    { 
        mDiskCache.put(key, bitmap); 
    } 
} 
                                   
public Bitmap getBitmapFromDiskCache(String key) 
{ 
    return mDiskCache.get(key); 
} 
                                   
// Creates a unique subdirectory of the designated app cache directory. Tries to use external but if not mounted, falls back on internal storage. 
public static File getCacheDir(Context context, String uniqueName) 
{ 
    // Check if media is mounted or storage is built-in, if so, try and use external cache dir otherwise use internal cache dir 
    final String cachePath = (Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED || 
                             !Environment.isExternalStorageRemovable()) ? 
                             context.getExternalCacheDir().getPath() : 
                             context.getCacheDir().getPath(); 
                                   
    return new File(cachePath + File.separator + uniqueName); 
}

內存緩存檢查在UI線程中作,磁盤緩存的檢查在後臺線程中。硬盤操做不該在UI線程中。圖片處理完成後應將其加入正在使用的內存、磁盤緩存中。

如何處理配置的改變?

應用運行中配置改變時,如屏幕方向改變時爲了應用新的配置Android會銷燬從新運行當前的Activity,此時,爲了給用戶快速、平緩的用戶體驗你可能不想從新加載圖片。

多虧你運行了緩存技術,緩存能夠經過 setRetainInstance(true))傳遞給新的Activity,在Activity重啓後,你能夠經過附着的Fragment從新使用已存在的緩存,這樣就能夠快速加載到ImageView中了。

下面是一個當配置改變時用Fragment重用已有的緩存的例子:

private LruCache mMemoryCache; 
                  
@Override
protected void onCreate(Bundle savedInstanceState) 
{ 
    ... 
    RetainFragment mRetainFragment = 
            RetainFragment.findOrCreateRetainFragment(getFragmentManager()); 
    mMemoryCache = RetainFragment.mRetainedCache; 
    if (mMemoryCache == null) 
    { 
        mMemoryCache = new LruCache(cacheSize) 
        { 
            ... // Initialize cache here as usual 
        } 
        mRetainFragment.mRetainedCache = mMemoryCache; 
    } 
    ... 
} 
                  
class RetainFragment extends Fragment 
{ 
    private static final String TAG = "RetainFragment"; 
    public LruCache mRetainedCache; 
                  
    public RetainFragment() {} 
                  
    public static RetainFragment findOrCreateRetainFragment(FragmentManager fm) 
    { 
        RetainFragment fragment = (RetainFragment) fm.findFragmentByTag(TAG); 
        if (fragment == null) 
        { 
            fragment = new RetainFragment(); 
        } 
        return fragment; 
    } 
                  
    @Override
    public void onCreate(Bundle savedInstanceState) 
    { 
        super.onCreate(savedInstanceState); 
        setRetainInstance(true); 
    } 
}

爲了測試,在使用和未使用Fragment的狀況下,強制旋轉屏幕,你會發現從保留內存緩存加載圖片時幾乎沒有滯後。

 

轉自:連接

相關文章
相關標籤/搜索