編輯:關於Android編程
ImageManager2這個類具有異步從網絡下載圖片,從sd讀取本地圖片,內存緩存,硬盤緩存,圖片使用動畫漸現等功能,已經將其應用在包含大量圖片的應用中一年多,沒有出現oom。
Android程序常常會內存溢出,網上也有很多解決方案,如軟引用,手動調用recycle等等。但經過我們實踐發現這些方案,都沒能起到很好的效果,我們的應用依然會出現很多oom,尤其我們的應用包含大量的圖片。android3.0之後軟引用基本已經失效,因為虛擬機只要碰到軟引用就回收,所以帶不來任何性能的提升。
我這裡的解決方案是HandlerThread(異步加載)+LruCache(內存緩存)+DiskLruCache(硬盤緩存)。
作為程序員,我也不多說,直接和大家共享我的代碼,用代碼交流更方便些。
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; /** 圖片加載隊列,後進先出 */ private Stack<ImageRef> mImageQueue = new Stack<ImageRef>(); /** 圖片請求隊列,先進先出,用於存放已發送的請求。 */ private Queue<ImageRef> mRequestQueue = new LinkedList<ImageRef>(); /** 圖片加載線程消息處理器 */ private Handler mImageLoaderHandler; /** 圖片加載線程是否就緒 */ private boolean mImageLoaderIdle = true; /** 請求圖片 */ private static final int MSG_REQUEST = 1; /** 圖片加載完成 */ private static final int MSG_REPLY = 2; /** 中止圖片加載線程 */ private static final int MSG_STOP = 3; /** 如果圖片是從網絡加載,則應用漸顯動畫,如果從緩存讀出則不應用動畫 */ 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; } /** * 私有構造函數,保證單例模式 * * @param context */ private ImageManager2(Context context) { int memClass = ((ActivityManager) context .getSystemService(Context.ACTIVITY_SERVICE)).getMemoryClass(); memClass = memClass > 32 ? 32 : memClass; // 使用可用內存的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 { /** 圖片對應ImageView控件 */ ImageView imageView; /** 圖片URL地址 */ String url; /** 圖片緩存路徑 */ String filePath; /** 默認圖資源ID */ int resId; int width = 0; int height = 0; /** * 構造函數 * * @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)); } /** * 顯示圖片固定大小圖片的縮略圖,一般用於顯示列表的圖片,可以大大減小內存使用 * * @param imageView 加載圖片的控件 * @param url 加載地址 * @param resId 默認圖片 * @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)); } /** * 入隊,後進先出 * * @param imageRef */ public void queueImage(ImageRef imageRef) { // 刪除已有ImageView Iterator<ImageRef> iterator = mImageQueue.iterator(); while (iterator.hasNext()) { if (iterator.next().imageView == imageRef.imageView) { iterator.remove(); } } // 添加請求 mImageQueue.push(imageRef); sendRequest(); } /** * 發送請求 */ private void sendRequest() { // 開啟圖片加載線程 if (mImageLoaderHandler == null) { HandlerThread imageLoader = new HandlerThread("image_loader"); imageLoader.start(); mImageLoaderHandler = new ImageLoaderHandler( imageLoader.getLooper()); } // 發送請求 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: // 收到請求 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相冊圖片,則直接讀取,不用經過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: // 收到應答 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; } } // 設置閒置標志 mImageLoaderIdle = true; // 若服務未關閉,則發送下一個請求。 if (mImageLoaderHandler != null) { sendRequest(); } } }; /** * 添加圖片顯示漸現動畫 * */ 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); } } /** * 從網絡獲取圖片字節數組 * * @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; } } /** * 根據url生成緩存文件完整路徑名 * * @param url * @return */ public String urlToFilePath(String url) { // 擴展名位置 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不會有殘余請求。 */ public void stop() { // 清空請求隊列 mImageQueue.clear(); } }
這裡就是給出了異步加載、內存緩存和硬盤緩存的解決方案,希望對大家的學習有所幫助。
IPC是Inter-Process Communication的縮寫,即跨進程通信。Android中跨進程通信有多種方式,如文件共享、使用ContentProvider、
昨天晚上我看了Google training裡面Manage the Activity Lifecycle這一節,看了以後學到很多以前看書,看視頻都沒有了解過的東西,Go
在android的項目開發中,都會遇到後期功能拓展增強與主程序代碼變更的現實矛盾,也就是程序的靈活度。 由於linux平台的安全機制,再加上dalvik的特殊機制,各種權
Button類的繼承結構: java.lang.Object ? android.view.View ?android.widget.TextView ?an