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

作者:lijiao 时间:2022-02-25 14:21:06 

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

}

}

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

标签:Android,异步加载,缓存
0
投稿

猜你喜欢

  • 详解Spring boot/Spring 统一错误处理方案的使用

    2023-11-24 12:56:07
  • C#实现把科学计数法(E)转化为正常数字值

    2022-09-06 23:41:47
  • 通过实例解析JMM和Volatile底层原理

    2023-05-20 19:10:48
  • JAVA实现社会统一信用代码校验的方法

    2023-04-10 08:38:21
  • JAVA IDEA 打开assert 设置方式

    2022-08-19 13:48:49
  • UnityShader使用Plane实现翻书效果

    2022-05-19 14:46:14
  • 自定义Spring Security的身份验证失败处理方法

    2022-09-27 13:18:31
  • Android封装实现短信验证码的获取倒计时

    2023-06-28 22:57:23
  • C++类中六个默认的成员函数详解

    2022-11-25 10:46:30
  • C# WebApi 路由机制剖析

    2022-01-23 05:20:16
  • C#如何绑定多个按钮到同一个事件

    2022-09-09 16:20:12
  • Android防止点击过快造成多次响应事件的解决方法

    2023-05-24 16:26:09
  • Java SWT中常见弹出框实例总结

    2023-08-22 00:42:22
  • Java Socket编程(三) 服务器Sockets

    2023-05-24 21:18:19
  • 教你使用java实现去除各种空格

    2022-09-21 21:27:07
  • Java获取指定字符串出现次数的方法

    2022-05-11 16:06:23
  • 浅谈SpringBoot资源初始化加载的几种方式

    2022-03-09 08:14:34
  • java中Callback简单使用总结

    2022-12-03 19:07:38
  • C#实现给图片添加日期信息的示例详解

    2021-07-29 21:19:28
  • ERROR/AndroidRuntime(17121)的问题解决

    2023-02-10 04:13:12
  • asp之家 软件编程 m.aspxhome.com