欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Android实现图片缓存与异步加载

程序员文章站 2024-02-24 14:30:58
imagemanager2这个类具有异步从网络下载图片,从sd读取本地图片,内存缓存,硬盘缓存,图片使用动画渐现等功能,已经将其应用在包含大量图片的应用中一年多,没有出现o...

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

这里就是给出了异步加载、内存缓存和硬盘缓存的解决方案,希望对大家的学习有所帮助。