Android實現圖片緩衝與非同步載入_Android

來源:互聯網
上載者:User

ImageManager2這個類具有非同步從網路下載圖片,從sd讀取本地圖片,記憶體緩衝,硬碟緩衝,圖片使用動畫漸現等功能,已經將其應用在包含大量圖片的應用中一年多,沒有出現oom。

Android程式常常會記憶體溢出,網上也有很多解決方案,如軟引用,手動調用recycle等等。但經過我們實踐發現這些方案,都沒能起到很好的效果,我們的應用依然會出現很多oom,尤其我們的應用程式套件含大量的圖片。android3.0之後軟引用基本已經失效,因為虛擬機器只要碰到軟引用就回收,所以帶不來任何效能的提升。

我這裡的解決方案是HandlerThread(非同步載入)+LruCache(記憶體緩衝)+DiskLruCache(硬碟緩衝)。

作為程式員,我也不多說,直接和大家共用My Code,用代碼交流更方便些。

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();   } }

這裡就是給出了非同步載入、記憶體緩衝和硬碟緩衝的解決方案,希望對大家的學習有所協助。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.