圖片快取(源於SDK文件)
大家都知道,現在的手機螢幕解析度是越來越大了,雖然之前我們介紹了非同步載入圖片的方法。但要知道,一個應用可用的記憶體是有限的。我們不可能將所有的記憶體都用來儲存圖片,也不可能為了記憶體而每次取圖片時都上網下載(流量費是很貴滴,而且下載也很耗電啊)。
因此,對於已下載的圖片,我們需要在本地維持一個快取。
記憶體快取
LurCache是一個記憶體快取類(Android3.1引入,通過v4的支援包,可以在API Level 4以上使用)。它使用一個強連線的LinkedHashMap,將使用頻率最高的圖片快取在記憶體中。
PS:在這之前,最流行的快取方式是使用SoftReference與WeakReference。但從Android2.3開始,垃圾回收對於軟引用與弱引用來說,變得越來越積極了。這也就造成了軟引用與弱引用的效率變得很低(沒幾下就被回收了,然後又得再建立,和沒快取沒太大區別)。同時,在Android3.0之前,Bitmap的資料是儲存在所謂的native記憶體區中(可以想象成是用C語言申請的記憶體,很難被垃圾回收自動釋放)。這也造成了應用非常容易發生記憶體溢位。
當然,要想使用LurCache,我們需要給定一個快取大小。而要想確定快取佔多少記憶體,需要考慮以下條件:
- 應用的其他地方對記憶體的佔用有多緊張?
- 有多少圖片會同時在螢幕上顯示?在它們顯示時,要確保多少的其餘圖片可以馬上顯示而無明顯延遲?
- 螢幕大小與密度如何?xhdpi的機子明顯要比hdpi的機子需要更多的快取。
- 每張圖片大概有多大?
- 圖片訪問的頻率是多少?有沒有部分圖片的訪問頻率遠高於其他圖片?如果是的話,你可能需要將這些圖片進行快取,甚至需要多個LurCache物件來快取不同等級的圖片。
- 你可以平衡質量與數量嗎?有的時候,儲存大量低解析度的圖片更有用。你可以在後臺任務中載入高解析度的。
沒有絕對合適的大小或計算方法,你必須根據自身應用的特點來確定相應的解決方案。快取太小,反而會由於頻繁的重新建立而降低效率。快取太大,自然也同樣會造成記憶體溢位了。
以下是一個使用LurCache的例子,使用八分之一的可用記憶體作為快取。在一個普通的hdpi的機子上,大概是4MB以上。
在一個800x480的螢幕上的全屏GridView顯示的圖片,大概需要1.5MB (800*480*4 bytes)的記憶體。也就是說,這個例子裡的快取,可以儲存至少2.5屏的圖片。
- private LruCache<String, Bitmap> mMemoryCache;
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- ...
- // 獲取可用記憶體的最大值,超過這個數,就會產生OutOfMemory.
- // 將其以KB為單位儲存.
- final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
- // 使用八分之一的可用記憶體作為快取.
- final int cacheSize = maxMemory / 8;
- mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
- @Override
- protected int sizeOf(String key, Bitmap bitmap) {
- // 快取大小以KB為單位進行計算.
- 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);
- }
接著《一種非同步載入資源的方法(源於SDK文件)》的例子,在載入圖片時,先去快取裡查一下。如果快取裡有,直接設上去就行了。
- 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> {
- ...
- // 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;
- }
- ...
- }
磁碟快取
雖然記憶體快取很有用,但光靠它還是不夠的。像一些專門看圖片的應用,裡面的照片多了去了。很容易快取就滿了。或者當應用在後臺被殺死時,記憶體快取也會立刻清空。你還是得重新建立。
在這種情況下,就需要磁碟快取來將圖片儲存到本地。這樣,當記憶體快取被清空時,可以通過磁碟快取加快圖片的載入。
以下的例子就是使用原始碼中提供的DiskLurCache來完成磁碟快取的功能。
它並不是替代記憶體快取,而是在記憶體快取之外再額外備份了一次。
- 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) {
- ...
- // Initialize memory cache
- ...
- // Initialize disk cache on background thread
- 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; // Finished initialization
- mDiskCacheLock.notifyAll(); // Wake any waiting threads
- }
- return null;
- }
- }
- class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
- ...
- // 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(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
- synchronized (mDiskCacheLock) {
- if (mDiskLruCache != null && mDiskLruCache.get(key) == null) {
- mDiskLruCache.put(key, bitmap);
- }
- }
- }
- public Bitmap getBitmapFromDiskCache(String key) {
- synchronized (mDiskCacheLock) {
- // Wait while disk cache is started from background thread
- while (mDiskCacheStarting) {
- try {
- mDiskCacheLock.wait();
- } catch (InterruptedException e) {
- }
- }
- if (mDiskLruCache != null) {
- return mDiskLruCache.get(key);
- }
- }
- return null;
- }
- // 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 getDiskCacheDir(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.MEDIA_MOUNTED.equals(Environment
- .getExternalStorageState()) || !isExternalStorageRemovable() ? getExternalCacheDir(
- context).getPath() : context.getCacheDir().getPath();
- return new File(cachePath + File.separator + uniqueName);
- }
PS:即使是初始化磁碟快取,也需要相應的磁碟操作。因此,使用了一個非同步任務InitDiskCacheTask來進行。另外,為了防止在磁碟快取建立成功前就去訪問,在getBitmapFromDiskCache方法中,進行了wait操作。當磁碟快取初始化後,如果提前訪問了,相應的執行緒將被notifyAll喚醒。
處理Configuration Change
在程式執行時,我們經常會遇到Configuration Change,像橫豎屏切換、語言改變等等。
而這些情況,往往會使得當前執行的Activity被銷燬並重新建立。
為了防止在銷燬與建立過程中重新建立快取(耗時太久且影響效率,要是能直接儲存就好了),我們可以通過Fragment。只要setRetainInstance(true)就行了。
如下圖所示,在Activity的onCreate裡,先判斷相應的Fragment在不在FragmentManager裡,要是在的話,直接獲取相應的快取物件。並且在Fragment的onCreate中setRetainInstance(true)。
- 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);
- }
- }
相關文章
- 開源框架——圖片載入和快取方案總結框架快取
- React Native圖片快取元件React Native快取元件
- Android圖片快取框架GlideAndroid快取框架IDE
- 關於瀏覽器快取問題(圖片更換後,頁面仍優先讀取快取)瀏覽器快取
- Swift iOS : WebView快取圖片的方法SwiftiOSWebView快取
- 用於Github的圖片資源Github
- Ocelot中文文件-快取快取
- BlueHost主機網站圖片快取刪除教程網站快取
- 微信小程式 實現網路圖片本地快取微信小程式快取
- offscreenCanvas+worker+IndexedDB實現無感大量圖片快取CanvasIndex快取
- Android 圖片載入快取問題:為什麼你的Glide快取沒有起作用?Android快取IDE
- Java 設定、刪除、獲取Word文件背景(基於Spire.Cloud.SDK for Java)JavaCloud
- 如何給基於 SAP Cloud SDK 的應用增添快取支援 Cache supportCloud快取
- 讀取本地圖片地圖
- 圖解HTTP快取圖解HTTP快取
- 圖解 HTTP 快取圖解HTTP快取
- 對於前端快取的理解(快取機制和快取型別)前端快取型別
- python--字串格式化用於批量讀取圖片Python字串格式化
- 基於七牛SDK構建的Vue單頁圖片管理應用Vue
- 微信小程式根據本地快取圖片路徑,生成縮圖的方法微信小程式快取
- 必踩坑!在微信sdk呼叫chooseImage獲取圖片後採取getLocalImgData轉換base64
- mPaaS 3.0 多媒體元件釋出 | 支付寶百億級圖片元件 XMedia 錘鍊之路(圖片快取篇)元件快取
- Java 圖片裁剪,擷取Java
- AotucCrawler 快速爬取圖片
- windwos清理圖示快取快取
- 關於 Flutter 網路圖片載入與快取使用過程總結 |8月更文挑戰Flutter快取
- OCR文件識別:圖片快速轉換成電子文件
- 關於快取穿透、快取擊穿、快取雪崩的模擬與解決(Redis)快取穿透Redis
- Python 批次合併圖片到word文件Python
- Python基於opencv呼叫攝像頭獲取個人圖片PythonOpenCV
- iOS 獲取視訊圖片iOS
- 獲取本地圖片/視訊地圖
- 求擷取圖片等比公式公式
- 【API】隨機獲取圖片API隨機
- 開源圖片工具箱(Img Toolbox) 格式轉換 新增水印 圖片壓縮 圖片裁剪 圖片旋轉 圖片縮放
- 基於ObjectMapper的本地快取ObjectAPP快取
- Java 新增、刪除、格式化Word中的圖片( 基於Spire.Cloud.SDK for Java )JavaCloud
- Flutter 圖片庫重磅開源!Flutter
- Android獲取本機各種型別檔案列表(音樂、影片、圖片、文件等)Android型別