Android實(shí)現(xiàn)圖片緩存與異步加載
ImageManager2這個(gè)類具有異步從網(wǎng)絡(luò)下載圖片,從sd讀取本地圖片,內(nèi)存緩存,硬盤緩存,圖片使用動(dòng)畫漸現(xiàn)等功能,已經(jīng)將其應(yīng)用在包含大量圖片的應(yīng)用中一年多,沒(méi)有出現(xiàn)oom。
Android程序常常會(huì)內(nèi)存溢出,網(wǎng)上也有很多解決方案,如軟引用,手動(dòng)調(diào)用recycle等等。但經(jīng)過(guò)我們實(shí)踐發(fā)現(xiàn)這些方案,都沒(méi)能起到很好的效果,我們的應(yīng)用依然會(huì)出現(xiàn)很多oom,尤其我們的應(yīng)用包含大量的圖片。android3.0之后軟引用基本已經(jīng)失效,因?yàn)樘摂M機(jī)只要碰到軟引用就回收,所以帶不來(lái)任何性能的提升。
我這里的解決方案是HandlerThread(異步加載)+LruCache(內(nèi)存緩存)+DiskLruCache(硬盤緩存)。
作為程序員,我也不多說(shuō),直接和大家共享我的代碼,用代碼交流更方便些。
package com.example.util; import java.io.File; import java.util.Iterator; import java.util.LinkedList; import java.util.Queue; import java.util.Stack; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.util.EntityUtils; import android.app.ActivityManager; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.graphics.drawable.TransitionDrawable; import android.media.ThumbnailUtils; import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; import android.os.Message; import android.support.v4.util.LruCache; import android.widget.ImageView; import com.example.MyApplication; /** * 圖片加載類 * * @author 月月鳥 */ public class ImageManager2 { private static ImageManager2 imageManager; public LruCache<String, Bitmap> mMemoryCache; private static final int DISK_CACHE_SIZE = 1024 * 1024 * 20; // 10MB private static final String DISK_CACHE_SUBDIR = "thumbnails"; public DiskLruCache mDiskCache; private static MyApplication myapp; /** 圖片加載隊(duì)列,后進(jìn)先出 */ private Stack<ImageRef> mImageQueue = new Stack<ImageRef>(); /** 圖片請(qǐng)求隊(duì)列,先進(jìn)先出,用于存放已發(fā)送的請(qǐng)求。 */ private Queue<ImageRef> mRequestQueue = new LinkedList<ImageRef>(); /** 圖片加載線程消息處理器 */ private Handler mImageLoaderHandler; /** 圖片加載線程是否就緒 */ private boolean mImageLoaderIdle = true; /** 請(qǐng)求圖片 */ private static final int MSG_REQUEST = 1; /** 圖片加載完成 */ private static final int MSG_REPLY = 2; /** 中止圖片加載線程 */ private static final int MSG_STOP = 3; /** 如果圖片是從網(wǎng)絡(luò)加載,則應(yīng)用漸顯動(dòng)畫,如果從緩存讀出則不應(yīng)用動(dòng)畫 */ private boolean isFromNet = true; /** * 獲取單例,只能在UI線程中使用。 * * @param context * @return */ public static ImageManager2 from(Context context) { // 如果不在ui線程中,則拋出異常 if (Looper.myLooper() != Looper.getMainLooper()) { throw new RuntimeException("Cannot instantiate outside UI thread."); } if (myapp == null) { myapp = (MyApplication) context.getApplicationContext(); } if (imageManager == null) { imageManager = new ImageManager2(myapp); } return imageManager; } /** * 私有構(gòu)造函數(shù),保證單例模式 * * @param context */ private ImageManager2(Context context) { int memClass = ((ActivityManager) context .getSystemService(Context.ACTIVITY_SERVICE)).getMemoryClass(); memClass = memClass > 32 ? 32 : memClass; // 使用可用內(nèi)存的1/8作為圖片緩存 final int cacheSize = 1024 * 1024 * memClass / 8; mMemoryCache = new LruCache<String, Bitmap>(cacheSize) { protected int sizeOf(String key, Bitmap bitmap) { return bitmap.getRowBytes() * bitmap.getHeight(); } }; File cacheDir = DiskLruCache .getDiskCacheDir(context, DISK_CACHE_SUBDIR); mDiskCache = DiskLruCache.openCache(context, cacheDir, DISK_CACHE_SIZE); } /** * 存放圖片信息 */ class ImageRef { /** 圖片對(duì)應(yīng)ImageView控件 */ ImageView imageView; /** 圖片URL地址 */ String url; /** 圖片緩存路徑 */ String filePath; /** 默認(rèn)圖資源ID */ int resId; int width = 0; int height = 0; /** * 構(gòu)造函數(shù) * * @param imageView * @param url * @param resId * @param filePath */ ImageRef(ImageView imageView, String url, String filePath, int resId) { this.imageView = imageView; this.url = url; this.filePath = filePath; this.resId = resId; } ImageRef(ImageView imageView, String url, String filePath, int resId, int width, int height) { this.imageView = imageView; this.url = url; this.filePath = filePath; this.resId = resId; this.width = width; this.height = height; } } /** * 顯示圖片 * * @param imageView * @param url * @param resId */ public void displayImage(ImageView imageView, String url, int resId) { if (imageView == null) { return; } if (imageView.getTag() != null && imageView.getTag().toString().equals(url)) { return; } if (resId >= 0) { if (imageView.getBackground() == null) { imageView.setBackgroundResource(resId); } imageView.setImageDrawable(null); } if (url == null || url.equals("")) { return; } // 添加url tag imageView.setTag(url); // 讀取map緩存 Bitmap bitmap = mMemoryCache.get(url); if (bitmap != null) { setImageBitmap(imageView, bitmap, false); return; } // 生成文件名 String filePath = urlToFilePath(url); if (filePath == null) { return; } queueImage(new ImageRef(imageView, url, filePath, resId)); } /** * 顯示圖片固定大小圖片的縮略圖,一般用于顯示列表的圖片,可以大大減小內(nèi)存使用 * * @param imageView 加載圖片的控件 * @param url 加載地址 * @param resId 默認(rèn)圖片 * @param width 指定寬度 * @param height 指定高度 */ public void displayImage(ImageView imageView, String url, int resId, int width, int height) { if (imageView == null) { return; } if (resId >= 0) { if (imageView.getBackground() == null) { imageView.setBackgroundResource(resId); } imageView.setImageDrawable(null); } if (url == null || url.equals("")) { return; } // 添加url tag imageView.setTag(url); // 讀取map緩存 Bitmap bitmap = mMemoryCache.get(url + width + height); if (bitmap != null) { setImageBitmap(imageView, bitmap, false); return; } // 生成文件名 String filePath = urlToFilePath(url); if (filePath == null) { return; } queueImage(new ImageRef(imageView, url, filePath, resId, width, height)); } /** * 入隊(duì),后進(jìn)先出 * * @param imageRef */ public void queueImage(ImageRef imageRef) { // 刪除已有ImageView Iterator<ImageRef> iterator = mImageQueue.iterator(); while (iterator.hasNext()) { if (iterator.next().imageView == imageRef.imageView) { iterator.remove(); } } // 添加請(qǐng)求 mImageQueue.push(imageRef); sendRequest(); } /** * 發(fā)送請(qǐng)求 */ private void sendRequest() { // 開(kāi)啟圖片加載線程 if (mImageLoaderHandler == null) { HandlerThread imageLoader = new HandlerThread("image_loader"); imageLoader.start(); mImageLoaderHandler = new ImageLoaderHandler( imageLoader.getLooper()); } // 發(fā)送請(qǐng)求 if (mImageLoaderIdle && mImageQueue.size() > 0) { ImageRef imageRef = mImageQueue.pop(); Message message = mImageLoaderHandler.obtainMessage(MSG_REQUEST, imageRef); mImageLoaderHandler.sendMessage(message); mImageLoaderIdle = false; mRequestQueue.add(imageRef); } } /** * 圖片加載線程 */ class ImageLoaderHandler extends Handler { public ImageLoaderHandler(Looper looper) { super(looper); } public void handleMessage(Message msg) { if (msg == null) return; switch (msg.what) { case MSG_REQUEST: // 收到請(qǐng)求 Bitmap bitmap = null; Bitmap tBitmap = null; if (msg.obj != null && msg.obj instanceof ImageRef) { ImageRef imageRef = (ImageRef) msg.obj; String url = imageRef.url; if (url == null) return; // 如果本地url即讀取sd相冊(cè)圖片,則直接讀取,不用經(jīng)過(guò)DiskCache if (url.toLowerCase().contains("dcim")) { tBitmap = null; BitmapFactory.Options opt = new BitmapFactory.Options(); opt.inSampleSize = 1; opt.inJustDecodeBounds = true; BitmapFactory.decodeFile(url, opt); int bitmapSize = opt.outHeight * opt.outWidth * 4; opt.inSampleSize = bitmapSize / (1000 * 2000); opt.inJustDecodeBounds = false; tBitmap = BitmapFactory.decodeFile(url, opt); if (imageRef.width != 0 && imageRef.height != 0) { bitmap = ThumbnailUtils.extractThumbnail(tBitmap, imageRef.width, imageRef.height, ThumbnailUtils.OPTIONS_RECYCLE_INPUT); isFromNet = true; } else { bitmap = tBitmap; tBitmap = null; } } else bitmap = mDiskCache.get(url); if (bitmap != null) { // ToolUtil.log("從disk緩存讀取"); // 寫入map緩存 if (imageRef.width != 0 && imageRef.height != 0) { if (mMemoryCache.get(url + imageRef.width + imageRef.height) == null) mMemoryCache.put(url + imageRef.width + imageRef.height, bitmap); } else { if (mMemoryCache.get(url) == null) mMemoryCache.put(url, bitmap); } } else { try { byte[] data = loadByteArrayFromNetwork(url); if (data != null) { BitmapFactory.Options opt = new BitmapFactory.Options(); opt.inSampleSize = 1; opt.inJustDecodeBounds = true; BitmapFactory.decodeByteArray(data, 0, data.length, opt); int bitmapSize = opt.outHeight * opt.outWidth * 4;// pixels*3 if it's RGB and pixels*4 // if it's ARGB if (bitmapSize > 1000 * 1200) opt.inSampleSize = 2; opt.inJustDecodeBounds = false; tBitmap = BitmapFactory.decodeByteArray(data, 0, data.length, opt); if (imageRef.width != 0 && imageRef.height != 0) { bitmap = ThumbnailUtils .extractThumbnail( tBitmap, imageRef.width, imageRef.height, ThumbnailUtils.OPTIONS_RECYCLE_INPUT); } else { bitmap = tBitmap; tBitmap = null; } if (bitmap != null && url != null) { // 寫入SD卡 if (imageRef.width != 0 && imageRef.height != 0) { mDiskCache.put(url + imageRef.width + imageRef.height, bitmap); mMemoryCache.put(url + imageRef.width + imageRef.height, bitmap); } else { mDiskCache.put(url, bitmap); mMemoryCache.put(url, bitmap); } isFromNet = true; } } } catch (OutOfMemoryError e) { } } } if (mImageManagerHandler != null) { Message message = mImageManagerHandler.obtainMessage( MSG_REPLY, bitmap); mImageManagerHandler.sendMessage(message); } break; case MSG_STOP: // 收到終止指令 Looper.myLooper().quit(); break; } } } /** UI線程消息處理器 */ private Handler mImageManagerHandler = new Handler() { @Override public void handleMessage(Message msg) { if (msg != null) { switch (msg.what) { case MSG_REPLY: // 收到應(yīng)答 do { ImageRef imageRef = mRequestQueue.remove(); if (imageRef == null) break; if (imageRef.imageView == null || imageRef.imageView.getTag() == null || imageRef.url == null) break; if (!(msg.obj instanceof Bitmap) || msg.obj == null) { break; } Bitmap bitmap = (Bitmap) msg.obj; // 非同一ImageView if (!(imageRef.url).equals((String) imageRef.imageView .getTag())) { break; } setImageBitmap(imageRef.imageView, bitmap, isFromNet); isFromNet = false; } while (false); break; } } // 設(shè)置閑置標(biāo)志 mImageLoaderIdle = true; // 若服務(wù)未關(guān)閉,則發(fā)送下一個(gè)請(qǐng)求。 if (mImageLoaderHandler != null) { sendRequest(); } } }; /** * 添加圖片顯示漸現(xiàn)動(dòng)畫 * */ private void setImageBitmap(ImageView imageView, Bitmap bitmap, boolean isTran) { if (isTran) { final TransitionDrawable td = new TransitionDrawable( new Drawable[] { new ColorDrawable(android.R.color.transparent), new BitmapDrawable(bitmap) }); td.setCrossFadeEnabled(true); imageView.setImageDrawable(td); td.startTransition(300); } else { imageView.setImageBitmap(bitmap); } } /** * 從網(wǎng)絡(luò)獲取圖片字節(jié)數(shù)組 * * @param url * @return */ private byte[] loadByteArrayFromNetwork(String url) { try { HttpGet method = new HttpGet(url); HttpResponse response = myapp.getHttpClient().execute(method); HttpEntity entity = response.getEntity(); return EntityUtils.toByteArray(entity); } catch (Exception e) { return null; } } /** * 根據(jù)url生成緩存文件完整路徑名 * * @param url * @return */ public String urlToFilePath(String url) { // 擴(kuò)展名位置 int index = url.lastIndexOf('.'); if (index == -1) { return null; } StringBuilder filePath = new StringBuilder(); // 圖片存取路徑 filePath.append(myapp.getCacheDir().toString()).append('/'); // 圖片文件名 filePath.append(MD5.Md5(url)).append(url.substring(index)); return filePath.toString(); } /** * Activity#onStop后,ListView不會(huì)有殘余請(qǐng)求。 */ public void stop() { // 清空請(qǐng)求隊(duì)列 mImageQueue.clear(); } }
這里就是給出了異步加載、內(nèi)存緩存和硬盤緩存的解決方案,希望對(duì)大家的學(xué)習(xí)有所幫助。
- Android 圖片緩存機(jī)制的深入理解
- Android中Glide加載圖片并實(shí)現(xiàn)圖片緩存
- Android圖片緩存原理、特性對(duì)比
- Android圖片緩存之初識(shí)Glide(三)
- Android圖片緩存之Bitmap詳解(一)
- 直接應(yīng)用項(xiàng)目中的Android圖片緩存技術(shù)
- Android中Glide加載庫(kù)的圖片緩存配置究極指南
- Android開(kāi)發(fā)筆記之圖片緩存、手勢(shì)及OOM分析
- android上的一個(gè)網(wǎng)絡(luò)接口和圖片緩存框架enif簡(jiǎn)析
- Android圖片三級(jí)緩存開(kāi)發(fā)
相關(guān)文章
Android實(shí)現(xiàn)沉浸式狀態(tài)欄功能
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)沉浸式狀態(tài)欄功能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-10-10Pagerslidingtabstrip菜單標(biāo)題欄制作方法
這篇文章主要為大家詳細(xì)介紹了Pagerslidingtabstrip菜單標(biāo)題欄的制作方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-10-10android自定義開(kāi)關(guān)控件-SlideSwitch的實(shí)例
本篇文章主要介紹了android自定義開(kāi)關(guān)控件-SlideSwitch的實(shí)例,實(shí)現(xiàn)了手機(jī)控件開(kāi)關(guān)的功能,感興趣的小伙伴們可以參考一下。2016-11-11Android ViewFlipper的詳解及實(shí)例
這篇文章主要介紹了Android ViewFlipper的詳解及實(shí)例的相關(guān)資料,通過(guò)本文希望能幫助大家理解這部分內(nèi)容,需要的朋友可以參考下2017-08-08Android Lock鎖實(shí)現(xiàn)原理詳細(xì)分析
這篇文章主要介紹了Android Lock鎖實(shí)現(xiàn)原理,Lock接口的實(shí)現(xiàn)類提供了比使用synchronized關(guān)鍵字更加靈活和廣泛的鎖定對(duì)象操作,而且是以面向?qū)ο蟮姆绞竭M(jìn)行對(duì)象加鎖2023-02-02Android 控制車載藍(lán)牙播放音樂(lè)詳解流程
本篇文章介紹了手機(jī)端音樂(lè)暫停和播放狀態(tài),從服務(wù)端告訴客戶端、設(shè)備端實(shí)現(xiàn)暫停、播放、上一首、下一首等功能的實(shí)現(xiàn),通讀本篇對(duì)大家的學(xué)習(xí)或工作具有一定的價(jià)值,需要的朋友可以參考下2021-10-10Android_UI 仿QQ側(cè)滑菜單效果的實(shí)現(xiàn)
相信大家對(duì)QQ側(cè)滑菜單的效果已經(jīng)不陌生了吧,側(cè)滑進(jìn)入個(gè)人頭像一側(cè),進(jìn)行對(duì)頭像的更改,我的收藏,QQ錢包,我的文件等一系列的操作,下面小編給大家分享Android_UI 仿QQ側(cè)滑菜單效果的實(shí)現(xiàn),一起看看吧2017-04-04