当前位置: 移动技术网 > 移动技术>移动开发>Android > Android实现图片缓存与异步加载

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

2019年07月24日  | 移动技术网移动技术  | 我要评论

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

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

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网