热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

自己写Android图片缓存框架之一级内存缓存

Android开发中比较重要的一块就是图片的加载,其中可以说道的地方太多了,无论是加载大图造成的OOM,多图同时加载造成滑动卡顿,以及网络图片重复加载慢而且耗流量这些问题都是

        Android开发中比较重要的一块就是图片的加载,其中可以说道的地方太多了,无论是加载大图造成的OOM,多图同时加载造成滑动卡顿,以及网络图片重复加载慢而且耗流量这些问题都是一个新手遇到过的问题。现在已经有好多开源框架拥有图片加载缓存的功能,Universal-Image-Loader, afinal,Xutils都可以实现图片缓存的效果,但是我们不仅需要会用,而且要知道为什么能这么用。所以就产生了这个系列,自己去写一个图片缓存的框架。

       缓存一般分为二级缓存,一级为内存缓存,二级为磁盘(硬盘)缓存,内存缓存速度快,但是内存大小有限制,磁盘一般为SD卡,或者是手机内存,大小限制上限比较大,但是速度相较于内存慢一点,不过肯定比从网络上加载图片要快的多。

      我们的思路就是将图片从网上下载下来后,存入内存缓存和磁盘缓存中,内存缓存会有一个大小限制,一般设为可用最大内存的四分之一,当达到上限时根据LruCache算法将内存中访问最少的移除,在加载图片时优先从内存中加载,如果内存中没有就从磁盘中加载,如果磁盘中也没有就从网络上获取,然后再写入缓存中。

      为了便于扩展,我们将缓存算法抽象成接口,除了LruCache算法,还可以使用其他的算法实现,但是实现的方法都是差不多的

      MemoryCacheAware.java

public interface MemoryCacheAware {

//存储
boolean put(K key ,V value);

//获取
V get(K key);

//移除
void remove(K key);

//清空
void clear();

//返回所有键名
Collection keys();

}
 Android在v4的包下已经有了LruCache的实现,我们仿照其继承上面的接口实现自己的LruCache算法

LruMemoryCache.java

public class LruMemoryCache implements MemoryCacheAware {

// Lru缓存
private final LinkedHashMap cache;

// 最大缓存空间
private final int maxSize;

// 当前缓存空间
private int currentSize;

public LruMemoryCache(int maxSize) {

if (maxSize <= 0) {
throw new IllegalArgumentException("maxSize<=0");
}

this.maxSize = maxSize;
// 按照访问顺序排序,第三个参数设置为true时按照访问顺序排序,设为false时按照插入顺序排序
this.cache = new LinkedHashMap(0, 0.75f, true);

}

//在链表中插入图片
@Override
public boolean put(String key, Bitmap value) {
if (key == null || value == null) {
throw new NullPointerException("key == null || value == null");
}

synchronized(this){
currentSize += sizeOf(key, value);
//previous是先前的key,value映射,如果没有就为null,这里因为前面加上了内存,所以判断如果先前有的话
//是不会插入的,这样再将内存减去,在操作完后,调用一次trimtosize移除缓存值达到上限时访问最少的对象
Bitmap previous = cache.put(key, value);
if (previous != null) {
currentSize -= sizeOf(key, previous);
}
trimToSize(maxSize);
}

return true;
}

//根据key得到图片
@Override
public Bitmap get(String key) {
if (key == null) {
throw new NullPointerException("key == null");
}

synchronized(this){
return cache.get(key);
}
}

//从链表中移除指定的key
@Override
public void remove(String key) {
if (key == null) {
throw new NullPointerException("key == null");
}

synchronized(this){
Bitmap previous = cache.remove(key);
if (previous != null) {
currentSize -= sizeOf(key, previous);
}
}
}

//清空缓存
@Override
public void clear() {
trimToSize(-1);
}

//获取链表中所有的key值
@Override
public Collection keys() {
return new HashSet(cache.keySet());
}

/**
* 把最近最少使用的对象在缓存值达到预设的值之前移除
* @param maxSize
*/
private void trimToSize(int maxSize) {
while (true) {
String key;
Bitmap value;

synchronized (this) {
if (currentSize <0 || (cache.isEmpty() && currentSize != 0)) {
throw new IllegalStateException(getClass().getName()
+ ".sizeOf() is reporting inconsistent results");
}
if (currentSize <= maxSize || cache.isEmpty()) {
break;
}
Entry entry = cache.entrySet().iterator().next();
if (entry == null) {
break;
}

key = entry.getKey();
value = entry.getValue();

cache.remove(key);
currentSize -= sizeOf(key, value);
}
}
}

/**
* 返回指定图片的大小
*
* @param key
* @param value
* @return
*/
private int sizeOf(String key, Bitmap value) {
return value.getRowBytes() * value.getHeight();
}

@Override
public synchronized final String toString() {
return String.format("Lrucache[maxSize=d%]", maxSize);
}

}

 主要是使用的LinkedHashMap的方法,具体细节注释已经写的很详细了,然后我们实现一个总类,负责实例化线程池,设置内存缓存的大小,加入设置加载中的图片显示,加载失败的图片显示,以及放入内存,和从内存中取图片的方法。

public class AsyncImageLoader {

private static AsyncImageLoader imageLoader;

private Context context;
// 异步任务执行者
private Executor executor;
// 加载任务的集合
public Set taskCollection;
// 内存缓存
public LruMemoryCache memoryCache;
// 加载中显示的bitmap
public Bitmap loadingBitmap;
// 加载完成显示的Bitmap
public Bitmap loadfailBitmap;

public static AsyncImageLoader getInstance(Context context) {
if (imageLoader == null) {
imageLoader = new AsyncImageLoader(context);
}
return imageLoader;
}

public AsyncImageLoader(Context context) {
this.cOntext= context;
// 初始化线程池
executor = new ThreadPoolExecutor(3, 200, 10, TimeUnit.SECONDS,
new LinkedBlockingQueue());
// 初始化任务集合
taskCollection = new HashSet();
// 获取应用程序最大可用内存
int maxMemory = (int) Runtime.getRuntime().maxMemory();
int cacheSize = maxMemory / 4;
// 设置内存缓存为最大可用内存的四分之一
memoryCache = new LruMemoryCache(cacheSize);
}

// 设置加载中的图片
public void setLoadingDrawable(int resourceId) {
loadingBitmap = BitmapFactory.decodeResource(context.getResources(),
resourceId);
}

// 设置加载失败的图片
public void setFailDrawable(int resourceId) {
loadfailBitmap = BitmapFactory.decodeResource(context.getResources(),
resourceId);
}

/**
* 加载图片,先是加载中图片,如果内存中没有再加载网络图片
*
* @param view
* @param imageView
* @param imgUrl
*/
public void loadBitmaps(View view, ImageView imageView, String imgUrl) {
if (imageView != null && loadingBitmap != null) {
imageView.setImageBitmap(loadingBitmap);
}
Bitmap bitmap = getBitmapFromMemoryCache(imgUrl);
if (bitmap == null) {
BitmapWorkerTask task = new BitmapWorkerTask(imageLoader, view);
taskCollection.add(task);
task.executeOnExecutor(executor, imgUrl);
} else {
if (imageView != null && bitmap != null) {
imageView.setImageBitmap(bitmap);
}
}
}

/**
* 设置图片到内存缓存中
*
* @param key
* @param value
*/
public void addBitmapToMemoryCache(String key, Bitmap value) {

if (getBitmapFromMemoryCache(key) == null) {
memoryCache.put(key, value);
}
}

/**
* 根据key从memorycache中取图片
*
* @param key
* @return
*/
public Bitmap getBitmapFromMemoryCache(String key) {
return memoryCache.get(key);
}

/**
* 取消所有正在下载或等待下载的任务
*/
public void cancelAllTask() {
if (taskCollection != null) {
for (BitmapWorkerTask task : taskCollection) {
task.cancel(false);
}
}
}
}
 里面使用的相当于一个单例的模式,通过getInstance获取一个AsyncImageLoader对象,如果没有就调用构造函数,并在其中实例化线程池和memorycache的内存大小,除此之外每次加载图片时我们采用一个类继承于AsyncTask,这样就能在同时加载多张图片时进行异步加载,在加载时将BitmapWorkerTask对象加入Set集合中,在加载完成后再从集合中删除。

public class BitmapWorkerTask extends AsyncTask {

private AsyncImageLoader imageLoader;
// 显示图片控件所在的视图
private View view;
// 图片Url地址
protected String imageUrl;

public BitmapWorkerTask(AsyncImageLoader imageLoader, View view) {
this.imageLoader = imageLoader;
this.view = view;
}

@Override
protected Bitmap doInBackground(String... params) {
imageUrl = params[0];
// 通过url下载图片
Bitmap bitmap = downloadBitmap(params[0]);
if (bitmap != null) {
// 将图片放入内存缓存中
imageLoader.addBitmapToMemoryCache(params[0], bitmap);
}

return bitmap;
}

@Override
protected void onPostExecute(Bitmap result) {
super.onPostExecute(result);
// 通过tag返回一个imageview对象
ImageView imageView = (ImageView) view.findViewWithTag(imageUrl);
if (imageView != null) {
if (result != null) {
// 加载成功
imageView.setImageBitmap(result);
} else {
// 加载失败
if (imageLoader.loadfailBitmap != null) {
imageView.setImageBitmap(imageLoader.loadfailBitmap);
}
}
}
imageLoader.taskCollection.remove(this);
}

/**
* 通过http协议,根据url返回bitmap对象
*
* @param imgUrl
* @return
*/
private Bitmap downloadBitmap(String imgUrl) {
Bitmap bitmap = null;
HttpURLConnection cOnn= null;
try {
URL url = new URL(imgUrl);
cOnn= (HttpURLConnection) url.openConnection();
conn.setConnectTimeout(6 * 1000);
conn.setReadTimeout(10 * 1000);
bitmap = BitmapFactory.decodeStream(conn.getInputStream());
} catch (Exception e) {
e.printStackTrace();
} finally {
if (conn != null) {
conn.disconnect();
}
}

return bitmap;
}

}
 中间的ImageView之所以是根据tag而不是id得到对象,是因为listview的加载机制,listview的item在滑动时如果移动了屏幕外会进入RecycleBin中,而我们在getview中开启的异步线程加载网络图片可能在图片还没加载出来时就已经被滑到屏幕外了,这时RecycleBin为新划入的item进行了复用,会用一个imageview实例,导致现在才加载出来的图片显示在这个上面,造成了位置顺序的错乱。所以为每一个imageview设置一个tag,这样就不会乱序了。

	@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder viewHolder = null;
if (cOnvertView== null) {
viewHolder = new ViewHolder();
cOnvertView= mInflater.inflate(R.layout.list_item, null);
viewHolder.imageView = (ImageView) convertView
.findViewById(R.id.imageview);
convertView.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) convertView.getTag();
}
// 给imageview设置一个tag,是加载时不会乱序
viewHolder.imageView.setTag(mdatas.get(position));
// 开启异步线程加载图片
AsyncImageLoader.getInstance(mContext).loadBitmaps(mListView,
viewHolder.imageView, mdatas.get(position));
return convertView;
}


这样一级内存缓存就实现了,我们在实际调用时只需要只需要先设置加载中的图片以及加载失败的图片

		AsyncImageLoader.getInstance(this).setLoadingDrawable(
R.drawable.loading);
AsyncImageLoader.getInstance(this).setFailDrawable(
R.drawable.ic_launcher);

下一篇就是disk磁盘缓存的内容了,使用的是google推荐的DiskLruCache
源码已经开源github,并加入了演示demo:地址    https://github.com/sheepm/Cache





 

推荐阅读
  • Iamtryingtomakeaclassthatwillreadatextfileofnamesintoanarray,thenreturnthatarra ... [详细]
  • Java中包装类的设计原因以及操作方法
    本文主要介绍了Java中设计包装类的原因以及操作方法。在Java中,除了对象类型,还有八大基本类型,为了将基本类型转换成对象,Java引入了包装类。文章通过介绍包装类的定义和实现,解答了为什么需要包装类的问题,并提供了简单易用的操作方法。通过本文的学习,读者可以更好地理解和应用Java中的包装类。 ... [详细]
  • 个人学习使用:谨慎参考1Client类importcom.thoughtworks.gauge.Step;importcom.thoughtworks.gauge.T ... [详细]
  • Android源码深入理解JNI技术的概述和应用
    本文介绍了Android源码中的JNI技术,包括概述和应用。JNI是Java Native Interface的缩写,是一种技术,可以实现Java程序调用Native语言写的函数,以及Native程序调用Java层的函数。在Android平台上,JNI充当了连接Java世界和Native世界的桥梁。本文通过分析Android源码中的相关文件和位置,深入探讨了JNI技术在Android开发中的重要性和应用场景。 ... [详细]
  • Android系统源码分析Zygote和SystemServer启动过程详解
    本文详细解析了Android系统源码中Zygote和SystemServer的启动过程。首先介绍了系统framework层启动的内容,帮助理解四大组件的启动和管理过程。接着介绍了AMS、PMS等系统服务的作用和调用方式。然后详细分析了Zygote的启动过程,解释了Zygote在Android启动过程中的决定作用。最后通过时序图展示了整个过程。 ... [详细]
  • 基于PgpoolII的PostgreSQL集群安装与配置教程
    本文介绍了基于PgpoolII的PostgreSQL集群的安装与配置教程。Pgpool-II是一个位于PostgreSQL服务器和PostgreSQL数据库客户端之间的中间件,提供了连接池、复制、负载均衡、缓存、看门狗、限制链接等功能,可以用于搭建高可用的PostgreSQL集群。文章详细介绍了通过yum安装Pgpool-II的步骤,并提供了相关的官方参考地址。 ... [详细]
  • 本文介绍了使用Java实现大数乘法的分治算法,包括输入数据的处理、普通大数乘法的结果和Karatsuba大数乘法的结果。通过改变long类型可以适应不同范围的大数乘法计算。 ... [详细]
  • 本文介绍了一个Java猜拳小游戏的代码,通过使用Scanner类获取用户输入的拳的数字,并随机生成计算机的拳,然后判断胜负。该游戏可以选择剪刀、石头、布三种拳,通过比较两者的拳来决定胜负。 ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • 本文讨论了如何优化解决hdu 1003 java题目的动态规划方法,通过分析加法规则和最大和的性质,提出了一种优化的思路。具体方法是,当从1加到n为负时,即sum(1,n)sum(n,s),可以继续加法计算。同时,还考虑了两种特殊情况:都是负数的情况和有0的情况。最后,通过使用Scanner类来获取输入数据。 ... [详细]
  • 阿,里,云,物,联网,net,core,客户端,czgl,aliiotclient, ... [详细]
  • 本文介绍了在Java中gt、gtgt、gtgtgt和lt之间的区别。通过解释符号的含义和使用例子,帮助读者理解这些符号在二进制表示和移位操作中的作用。同时,文章还提到了负数的补码表示和移位操作的限制。 ... [详细]
  • 本文介绍了如何在给定的有序字符序列中插入新字符,并保持序列的有序性。通过示例代码演示了插入过程,以及插入后的字符序列。 ... [详细]
  • JavaSE笔试题-接口、抽象类、多态等问题解答
    本文解答了JavaSE笔试题中关于接口、抽象类、多态等问题。包括Math类的取整数方法、接口是否可继承、抽象类是否可实现接口、抽象类是否可继承具体类、抽象类中是否可以有静态main方法等问题。同时介绍了面向对象的特征,以及Java中实现多态的机制。 ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
author-avatar
刘刘敬
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有