Bitmap處理之Bitmap緩存

在ListView和GridView中,如果子View在屏幕中看不到,對應的子View的資源會被自動的釋放,如果子View重新在屏幕中顯示,就需要去重新加載資源,如果是從網絡或者從硬盤中去讀取資源,這樣導致的後果是滑動的時候,顯示的效果不是很流暢,如果能將之前已經加載過的資源 放在緩存中,每次都是從緩存中讀取資源,這樣顯示的效果就會變得很流暢,下面分別介紹兩種緩存的形式,內存緩存和硬盤緩存

內存緩存

內存緩存主要是使用應用本身的一部分內存作爲bitmap的緩存,主要是使用class LruCache ,這個類還有對應的兼容包,主要的原理就是把bitmap先加載到強引用LinkedHashMap中,釋放掉不再使用的bitmap,官方網站還介紹了一些需要考慮的因素......這裏不一一說明,有興趣的童鞋可以去官方文檔,文章的最後有相應的鏈接,下面直接上代碼:
private LruCache<String, Bitmap> mMemoryCache;

@Override
protected void onCreate(Bundle savedInstanceState) {
    ...

    //獲得虛擬機的內存,如果超出這個容量就會報出OutOfMemory exception
    final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);

    //使用內存的1/8作爲bitmap的緩存
    final int cacheSize = maxMemory / 8;
    
    mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
        @Override
        protected int sizeOf(String key, Bitmap bitmap) {
            // The cache size will be measured in kilobytes rather than
            // number of items.
            return bitmap.getByteCount() / 1024;
        }
    };
    ...
}

//往緩存中添加在資源
public void addBitmapToMemoryCache(String key, Bitmap bitmap) {
    if (getBitmapFromMemCache(key) == null) {
        mMemoryCache.put(key, bitmap);
    }
}

//從緩存中獲取資源
public Bitmap getBitmapFromMemCache(String key) {
    return mMemoryCache.get(key);
}

當ImageView要顯示時,首先判斷緩存中是否已經有對應的資源,如果有,就馬上顯示,沒有的話,纔會在後臺去加載對應的資源

public void loadBitmap(int resId, ImageView imageView) {
    final String imageKey = String.valueOf(resId);

    final Bitmap bitmap = getBitmapFromMemCache(imageKey);
    if (bitmap != null) {
        mImageView.setImageBitmap(bitmap);
    } else {
        mImageView.setImageResource(R.drawable.image_placeholder);
        BitmapWorkerTask task = new BitmapWorkerTask(mImageView);
        task.execute(resId);
    }
}
這時對應的BitmapWorkerTask也要作相應的調整 
class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
    private final WeakReference<ImageView> imageViewReference;
    private int data = 0;

    public BitmapWorkerTask(ImageView imageView) {
        // Use a WeakReference to ensure the ImageView can be garbage collected
        imageViewReference = new WeakReference<ImageView>(imageView);
    }

    // Decode image in background.
    // 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;
    }
    
    // Once complete, see if ImageView is still around and set bitmap.
    @Override
    protected void onPostExecute(Bitmap bitmap) {
        if (isCancelled()) {
            bitmap = null;
        }

        if (imageViewReference != null && bitmap != null) {
            final ImageView imageView = imageViewReference.get();
            final BitmapWorkerTask bitmapWorkerTask =
                    getBitmapWorkerTask(imageView);
            if (this == bitmapWorkerTask && imageView != null) {
                imageView.setImageBitmap(bitmap);
            }
        }
    }
}

在後臺加載圖片時是將圖片放在緩存中

硬盤內存

對於Gridiew這樣的組件,有時候會很容易就會把內存緩存佔滿,應用本身如果被其他的應用打斷(來電),如果重新回到這個應用,據需要重新處理圖片資源。這種情況下開源使用DiskCache,DiskCache緩存處理過的bitmap,當然從硬盤中讀取可能會比從內存中讀取的速度要低一些,這些操作需要放在後臺線程中去處理

private DiskLruCache mDiskLruCache;
private final Object mDiskCacheLock = new Object();
private boolean mDiskCacheStarting = true;
//緩存的大小
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) {
    ...
    // 初始化緩存
    ...
    // 在後臺線程中初始化緩存
    File cacheDir = getDiskCacheDir(this, DISK_CACHE_SUBDIR);
    new InitDiskCacheTask().execute(cacheDir);
    ...
}

//初始化緩存的線程
class InitDiskCacheTask extends AsyncTask<File, Void, Void> {
    @Override
    protected Void doInBackground(File... params) {
        synchronized (mDiskCacheLock) {
            File cacheDir = params[0];
            mDiskLruCache = DiskLruCache.open(cacheDir, DISK_CACHE_SIZE);
            mDiskCacheStarting = false; // 初始化是否完成
            mDiskCacheLock.notifyAll(); // 通知其他的等待線程
        }
        return null;
    }
}

//加載Bitmap的後臺線程
class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
    ...
    // 在後臺解析bitmap.
    @Override
    protected Bitmap doInBackground(Integer... params) {
        final String imageKey = String.valueOf(params[0]);

        // 檢查緩存中的資源
        Bitmap bitmap = getBitmapFromDiskCache(imageKey);

        if (bitmap == null) { // 沒有資源
            // 處理bitamp
            final Bitmap bitmap = decodeSampledBitmapFromResource(
                    getResources(), params[0], 100, 100));
        }

        // 加載bitmap到緩存中
        addBitmapToCache(imageKey, bitmap);

        return bitmap;
    }
    ...
}

public void addBitmapToCache(String key, Bitmap bitmap) {
    // 在內存緩存中添加資源
    if (getBitmapFromMemCache(key) == null) {
        mMemoryCache.put(key, bitmap);
    }

    // 同樣在disk緩存中也添加對應的資源
    synchronized (mDiskCacheLock) {
        if (mDiskLruCache != null && mDiskLruCache.get(key) == null) {
            mDiskLruCache.put(key, bitmap);
        }
    }
}

//總緩存中獲得資源
public Bitmap getBitmapFromDiskCache(String key) {
    synchronized (mDiskCacheLock) {
        // 如果diskCache在後臺運行,則等待
        while (mDiskCacheStarting) {
            try {
                mDiskCacheLock.wait();
            } catch (InterruptedException e) {}
        }
        if (mDiskLruCache != null) {
            return mDiskLruCache.get(key);
        }
    }
    return null;
}


//創建一個唯一的文件夾作爲緩存區,如果有外置的sdcard,就使用,否則就使用內部的sdcard
public static File getDiskCacheDir(Context context, String uniqueName) {

    //檢查外置sdcard的狀態
    final String cachePath =
            Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) ||
                    !isExternalStorageRemovable() ? getExternalCacheDir(context).getPath() :
                            context.getCacheDir().getPath();

    return new File(cachePath + File.separator + uniqueName);
}
內存緩存的檢查在UI線程中而硬盤緩存的檢查中後臺線程中,不要在UI線程中作讀寫硬盤的操作,最終處理完成的bitmap會分別加載到內存緩存和硬盤緩存中

處理配置的變化

運行時配置的變化(屏幕方向的變化),在Android中會destory、restart,慶幸的是開源使用內存緩存,內存緩存在Fragment可以傳遞到新的activity的,例子使用Fragment的例子

private LruCache<String, Bitmap> mMemoryCache;

@Override
protected void onCreate(Bundle savedInstanceState) {
    ...
    RetainFragment mRetainFragment =
            RetainFragment.findOrCreateRetainFragment(getFragmentManager());
    mMemoryCache = RetainFragment.mRetainedCache;
    if (mMemoryCache == null) {
        mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
            ... // Initialize cache here as usual
        }
        mRetainFragment.mRetainedCache = mMemoryCache;
    }
    ...
}

class RetainFragment extends Fragment {
    private static final String TAG = "RetainFragment";
    public LruCache<String, Bitmap> 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);
    }
}


原文鏈接 http://developer.android.com/training/displaying-bitmaps/cache-bitmap.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章