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

  }

}

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

(0)

相关推荐

  • 在Android的应用中实现网络图片异步加载的方法

    前言 其实很幸运,入职一周之后就能跟着两个师兄做android开发,师兄都是大神,身为小白的我只能多多学习,多多努力.最近一段时间都忙的没机会总结,今天刚完成了android客户端图片异步加载的类,这里记录一下(ps:其实我这里都是参考网上开源实现) 原理 在ListView或者GridView中加载图片的原理基本都是一样的: 先从内存缓存中获取,取到则返回,取不到进行下一步     从文件缓存中获取,取到则返回并更新到内存缓存,取不到则进行进行下一步     从网络上下载图片,并更新内存缓存和

  • Android实现Listview异步加载网络图片并动态更新的方法

    本文实例讲述了Android实现Listview异步加载网络图片并动态更新的方法.分享给大家供大家参考,具体如下: 应用实例:解析后台返回的数据,把每条都显示在ListView中,包括活动图片.店名.活动详情.地址.电话和距离等. 在布局文件中ListView的定义: <ListView android:id="@id/maplistview" android:background="@drawable/bg" android:layout_width=&qu

  • Android App中实现图片异步加载的实例分享

    一.概述 一般大量图片的加载,比如GridView实现手机的相册功能,一般会用到LruCache,线程池,任务队列等:那么异步消息处理可以用哪呢? 1.用于UI线程当Bitmap加载完成后更新ImageView 2.在图片加载类初始化时,我们会在一个子线程中维护一个Loop实例,当然子线程中也就有了MessageQueue,Looper会一直在那loop停着等待消息的到达,当有消息到达时,从任务队列按照队列调度的方式(FIFO,LIFO等),取出一个任务放入线程池中进行处理. 简易的一个流程:当

  • Android 异步加载图片分析总结

    研究了android从网络上异步加载图像,现总结如下: (1)由于android UI更新支持单一线程原则,所以从网络上取数据并更新到界面上,为了不阻塞主线程首先可能会想到以下方法. 在主线程中new 一个Handler对象,加载图像方法如下所示 复制代码 代码如下: private void loadImage(final String url, final int id) { handler.post(new Runnable() { public void run() { Drawable

  • Android中使用二级缓存、异步加载批量加载图片完整案例

    一.问题描述 Android应用中经常涉及从网络中加载大量图片,为提升加载速度和效率,减少网络流量都会采用二级缓存和异步加载机制,所谓二级缓存就是通过先从内存中获取.再从文件中获取,最后才会访问网络.内存缓存(一级)本质上是Map集合以key-value对的方式存储图片的url和Bitmap信息,由于内存缓存会造成堆内存泄露, 管理相对复杂一些,可采用第三方组件,对于有经验的可自己编写组件,而文件缓存比较简单通常自己封装一下即可.下面就通过案例看如何实现网络图片加载的优化. 二.案例介绍 案例新

  • android异步加载图片并缓存到本地实现方法

    在android项目中访问网络图片是非常普遍性的事情,如果我们每次请求都要访问网络来获取图片,会非常耗费流量,而且图片占用内存空间也比较大,图片过多且不释放的话很容易造成内存溢出.针对上面遇到的两个问题,首先耗费流量我们可以将图片第一次加载上面缓存到本地,以后如果本地有就直接从本地加载.图片过多造成内存溢出,这个是最不容易解决的,要想一些好的缓存策略,比如大图片使用LRU缓存策略或懒加载缓存策略.今天首先介绍一下本地缓存图片. 首先看一下异步加载缓存本地代码: 复制代码 代码如下: public

  • Android 异步加载图片的实例代码

    异步加载图片的主要流程是进行判断缓存中是否存在图片,如果存在则直接返回,如果不存在则进行下载并进行缓存. 以下是建立一个异步下载类: 复制代码 代码如下: /** * User: Tom * Date: 13-5-13 * Time: 下午8:07 */public class AsnycImageLoader { //定义一个HashMap进行存放缓存的Image key为String Value为一个弱引用的一个资源文件    // 图片 为了方便JAVA的回收    private Map

  • Android实现ListView异步加载图片的方法

    本文实例讲述了Android实现ListView异步加载图片的方法.分享给大家供大家参考.具体如下: ListView异步加载图片是非常实用的方法,凡是是要通过网络获取图片资源一般使用这种方法比较好,用户体验好,不用让用户等待下去,下面就说实现方法,先贴上主方法的代码: package cn.wangmeng.test; import java.io.IOException; import java.io.InputStream; import java.lang.ref.SoftReferen

  • Android实现异步加载图片

    麦洛开通博客以来,有一段时间没有更新博文了.主要是麦洛这段时间因项目开发实在太忙了.今天周六还在公司加班,苦逼程序猿都是这样生活的. 今天在做项目的时候,有一个实现异步加载图片的功能,虽然比较简单但还是记录一下吧.因为麦洛之前实现异步加载图片都是使用了AsynTask这个API,继续这个类,实现起来非常简单也很方便.在doInBackground()方法里实现下载逻辑.具体实现如下 实现逻辑是:先从内存中读取,如果内存中有这张图片,则直接使用;如果内存没有再到sdcard上读取,如果有则显示;如

  • Android中ListView异步加载图片错位、重复、闪烁问题分析及解决方案

    Android ListView异步加载图片错位.重复.闪烁分析以及解决方案,具体问题分析以及解决方案请看下文. 我们在使用ListView异步加载图片的时候,在快速滑动或者网络不好的情况下,会出现图片错位.重复.闪烁等问题,其实这些问题总结起来就是一个问题,我们需要对这些问题进行ListView的优化. 比如ListView上有100个Item,一屏只显示10个Item,我们知道getView()中convertView是用来复用View对象的,因为一个Item的对应一个View对象,而Ima

随机推荐