做android应用少不了要和网络打交道,在我刚开始学android的时候总是处理不好网络图片的加载,尤其是图片乱跳的问题,后来发现了各种缓存图片的方法:本地缓存、软引用、LruCache....

我知道的这三种方法中,第一中和其他两种并不冲突,我们完全可以缓存到本地一份,在缓存到内存中一份。软引用这样方式,第一次使用软引用的时候,感觉做一个完美的内存缓存太容易了,可惜在android2.3以后android加强了对软引用的回收,这种方式基本上算是废了。

 

LruCache登场

软引用废了,难道就没有替代品了吗? 有,android sdk中google官方添加了LruCache类, 该类使用Lru算法实现内存缓存,关于LruCache的使用,可以google一下,很简单。

 

本地缓存方案

以前我的本地缓存方案都是自己写流实现的,这种方式完全可以,而且也不是很费劲,现在也可以使用这种方式。但是,有一个更好用的方式:DiskLruCache,广域DiskLruCache,还是google一下就ok。这里说明一下,DiskLruCache并没有在SDK中需要我们自己下载,可以使用我在github上fork的:https://github.com/qibin0506/DiskLruCache 
直接将源码copy到项目中即可。

 

ImageLoader的思路

给定一个url,我们通过这个url,先去内存中取图片,如果内存中不存在,在去本地获取,本地存在的话,再添加到内存中,不存在就去网络中下载,下载完毕后,缓存到本地和内存中。

 

实现

思路很清晰,那么现在开始跟着思路实现一下代码吧。

public class ImageLoader {
private static final int SUCCESS = 1;
private static final int FAILED = 0; private static int sImageWidth; private static Context sContext;
private static ImageLoader sInstance;
private static ExecutorService sThreadPool = Executors.newCachedThreadPool();
private LruCache<String, Bitmap> mLruCache;
private DiskLruCache mDiskCache; /**
* 初始化ImageLoader
* @param context
* @param width 预想的图片宽度
*/
public static void init(Context context, int width) {
sContext = context;
sImageWidth = width;
} /**
* 单例 获取ImageLoader的实例
* @return
*/
public synchronized static ImageLoader getInstance() {
if(null == sInstance) {
sInstance = new ImageLoader();
}
return sInstance;
} private ImageLoader() {
// begin 初始化LruCache
int maxMemory = (int) Runtime.getRuntime().maxMemory();
int maxSize = maxMemory / 8;
mLruCache = new LruCache<String, Bitmap>(maxSize) {
@Override
protected int sizeOf(String key, Bitmap value) {
return value.getByteCount();
}
};
// end // begin 初始化DiskLruCache
try {
mDiskCache = DiskLruCache.open(getCacheDir(), getAppVersion(), 1, 10*1024*1024);
} catch (Exception e) {
e.printStackTrace();
}
// end
} public void load(final String url, final OnImageListener l) {
final Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case SUCCESS:
l.onResult((Bitmap) msg.obj);
break;
case FAILED:
l.onResult(null);
break;
}
}
}; // 从memory中获取
Bitmap bmp = getFromLru(url);
if(null != bmp) {
System.out.println("--getFromLru");
Message msg = handler.obtainMessage(SUCCESS, bmp);
msg.sendToTarget();
return;
} // 如果memory中没有
// 从disk中获取
bmp = getFromDisk(url);
if(null != bmp) {
System.out.println("---getFromDisk");
Message msg = handler.obtainMessage(SUCCESS, bmp);
msg.sendToTarget();
return;
} // 如果disk中没有, 则从网络中下载
sThreadPool.execute(new Runnable() {
@Override
public void run() {
System.out.println("----getFromNet");
DefaultHttpClient client = null;
try {
client = new DefaultHttpClient();
HttpGet get = new HttpGet(url);
HttpResponse response = client.execute(get);
if(200 == response.getStatusLine().getStatusCode()) {
InputStream in = response.getEntity().getContent();
Bitmap bmp = BitmapFactory.decodeStream(in);
bmp = scaleImage(bmp); // 缓存到本地
addToDisk(url, bmp);
// 缓存到memory
addToLru(url, bmp);
Message msg = handler.obtainMessage(SUCCESS, bmp);
msg.sendToTarget();
}
} catch (Exception e) {
e.printStackTrace();
handler.sendEmptyMessage(FAILED);
} finally {
if(null != client) {
client.getConnectionManager().shutdown();
}
}
}
});
} // 缩放图片
private Bitmap scaleImage(Bitmap bmp) {
int sample = bmp.getWidth() / sImageWidth;
int height = bmp.getHeight() / sample;
return ThumbnailUtils.extractThumbnail(bmp, sImageWidth, height);
} // 从memory中获取
private Bitmap getFromLru(String url) {
return mLruCache.get(Encrypt.md5(url));
} // 添加到内存中
private void addToLru(String url, Bitmap bmp) {
if(getFromLru(url) == null) {
System.out.println("++addToLru");
mLruCache.put(Encrypt.md5(url), bmp);
}
} // 从本地缓存获取
private Bitmap getFromDisk(String url) {
Bitmap bmp = null;
try {
DiskLruCache.Snapshot snapshot = mDiskCache.get(Encrypt.md5(url));
InputStream in = snapshot.getInputStream(0);
bmp = BitmapFactory.decodeStream(in);
addToLru(url, bmp); // 这里可以断言 内存中肯定没有
in.close();
} catch (Exception e) {
}
return bmp;
} // 添加到本地缓存
private void addToDisk(String url, Bitmap bmp) throws Exception {
System.out.println("+++addtoDisk");
ByteArrayOutputStream baos = new ByteArrayOutputStream();
bmp.compress(CompressFormat.PNG, 100, baos);
byte[] buf = baos.toByteArray(); DiskLruCache.Editor editor = mDiskCache.edit(Encrypt.md5(url));
OutputStream out = editor.newOutputStream(0);
out.write(buf);
out.flush();
editor.commit();
} // 获取缓存目录
private File getCacheDir() {
File dir = null;
if(Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
dir = new File(sContext.getExternalCacheDir().getPath() + File.separator + "images" + File.separator);
}else {
dir = new File(sContext.getCacheDir().getPath() + File.separator + "images" + File.separator);
}
return dir;
} // 获取软件版本
private int getAppVersion() {
PackageInfo pi;
try {
pi = sContext.getPackageManager().getPackageInfo(sContext.getPackageName(), 0);
return pi.versionCode;
} catch (NameNotFoundException e) {
e.printStackTrace();
}
return 0;
} public interface OnImageListener {
public void onResult(Bitmap bmp);
}
}

可以看到ImageLoader中有一个init方法,该方法主要是初始化ImageLoader类中的context和我们预想的图片的宽度,为什么没有高度? 高度要根据宽度比自动计算的。

27~32行,获取ImageLoader的实例, 因为ImageLoader是单例的。

构造方法中,36~43行初始化LruCache,47~51行初始化DiskLruCache, DiskLruCache的静态方法open接收4个参数,分别是:缓存的路径,软件的版本号,一个key对应多少文件,最大缓存的大小。

load方法中,71~77行,从memory中尝试获取图片,如果获取到了,则发送消息,并停止执行。81~87行,如果从memory中获取不到,则尝试从本地缓存中获取,如果获取到了,则发送消息,并停止执行。94~110行,从网络下载图片,并添加到本地缓存和memory中。

scaleImage方法,是一个简单的缩放图片功能,使用了ThumbnailUtils.extractThumbnail,如果在低版本中,需要自己去实现。

131~141行,是从memory中获取和添加到memory中。

144~169行,是从本地缓存中获取和添加到本地缓存。

剩下的两个方法是获取缓存路径和软件版本号。

 

ImageLoader定义好了以后,怎么使用呢? 很简单,调用load方法就行。

首先自定义一个Adapter继承自BaseAdapter,

public class ImageAdapter extends BaseAdapter {
private Context mContext;
private String[] mData; public ImageAdapter(Context context, String[] data) {
mContext = context;
mData = data;
initImageLoader();
} private void initImageLoader() {
WindowManager wm = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
DisplayMetrics dm = new DisplayMetrics();
wm.getDefaultDisplay().getMetrics(dm);
ImageLoader.init(mContext, dm.widthPixels / 3);
} @Override
public int getCount() {
return mData.length;
} @Override
public Object getItem(int position) {
return mData[position];
} @Override
public long getItemId(int position) {
return position;
} @Override
public View getView(int position, View convertView, ViewGroup parent) {
final ViewHolder holder;
if(null == convertView) {
convertView = View.inflate(mContext, R.layout.image_item, null);
holder = new ViewHolder();
holder.imageView = (ImageView) convertView.findViewById(R.id.iv);
convertView.setTag(R.id.iv, holder);
}else {
holder = (ViewHolder) convertView.getTag(R.id.iv);
} holder.imageView.setImageResource(R.drawable.ic_launcher); ImageLoader imageLoader = ImageLoader.getInstance();
imageLoader.load(mData[position], new ImageLoader.OnImageListener() {
@Override
public void onResult(Bitmap bmp) {
holder.imageView.setImageBitmap(bmp);
}
}); return convertView;
} class ViewHolder {
ImageView imageView;
}
}

标准的BaseAdapter,需要注意到是第45行,主要是为了解决图片跳动的问题。

 

最后看一下效果吧:

图片借用了郭神的, 实在是懒得自己去找了。 效果还不错,挺顺畅的。

打造完美的ImageLoader——LruCache+DiskLruCache的更多相关文章

  1. 安卓开发笔记——关于照片墙的实现(完美缓存策略LruCache+DiskLruCache)

    这几天一直研究在安卓开发中图片应该如何处理,在网上翻了好多资料,这里做点小总结,如果朋友们有更好的解决方案,可以留言一起交流下. 内存缓存技术 在我们开发程序中要在界面上加载一张图片是件非常容易的事情 ...

  2. Android 打造自己的ImageLoader

    Android 打造自己的ImageLoader 学习和参考 Android开发艺术探索 https://blog.csdn.net/column/details/15318.html 郭霖大神的Gl ...

  3. LruCache DiskLruCache 缓存 简介 案例 MD

    Markdown版本笔记 我的GitHub首页 我的博客 我的微信 我的邮箱 MyAndroidBlogs baiqiantao baiqiantao bqt20094 baiqiantao@sina ...

  4. Android 打造完美的侧滑菜单/侧滑View控件

    概述 Android 打造完美的侧滑菜单/侧滑View控件,完全自定义实现,支持左右两个方向弹出,代码高度简洁流畅,兼容性高,控件实用方便. 详细 代码下载:http://www.demodashi. ...

  5. LruCache & DiskLruCache

    在用户界面(UI)加载一张图片时很简单,然而,如果你需要加载多张较大的图像,事情就会变得更加复杂,.在许多情况下(如与像的ListView GridView或ViewPager的组件),屏幕上的图片的 ...

  6. 10款优秀Vim插件帮你打造完美IDE

    导读 如果你稍微写过一点代码,就能知道“集成开发环境”(IDE)是多么的便利.不管是Java.C还是Python,当IDE会帮你检查语法.后台编译,或者自动导入你需要的库时,写代码就变得容易许多.另外 ...

  7. Sphider + SCWS 打造完美PHP中文搜索引擎

    今日需要为几个网站做个全文搜索引擎,找了几个PHP开源项目,先试了一下Sphinx ,可惜是基于数据库的,相当于数据库搜索的扩展.Sphider还不错,不过中文的分词不行,基本只能靠空格和符号进行分词 ...

  8. 打造完美的xml技术解决方案(dom4j/xstream)

    转: XML 技术是随着 Java 的发展而发展起来的.在 XML 出现之前对于简单的数据格式通常是存储在 ini 配置文件等文本文件中,复杂的格式则采用自定义的文件格式,因此对于每种文件格式都要有专 ...

  9. Android 自定义View修炼-打造完美的自定义侧滑菜单/侧滑View控件

    一.概述 在App中,经常会出现侧滑菜单,侧滑滑出View等效果,虽然说Android有很多第三方开源库,但是实际上 咱们可以自己也写一个自定义的侧滑View控件,其实不难,主要涉及到以下几个要点: ...

随机推荐

  1. CSU 1806 Toll 自适应simpson积分+最短路

    分析:根据这个题学了一发自适应simpson积分(原来积分还可以这么求),然后就是套模板了 学习自适应simpson积分:http://blog.csdn.net/greatwall1995/arti ...

  2. 【Poj 1330】Nearest Common Ancestors

    http://poj.org/problem?id=1330 题目意思就是T组树求两点LCA. 这个可以离线DFS(Tarjan)-----具体参考 O(Tn) 0ms 还有其他在线O(Tnlogn) ...

  3. Android下载资源

    下面提供了源码下载地址,供有兴趣的朋友下载, android音乐播放器源码   由于本人才疏学浅,有很多地方不够完善,希望大家指证. 免费下载地址在 http://linux.linuxidc.com ...

  4. ubuntu 16.04 Sqoop 安装

    1.下载:https://mirrors.tuna.tsinghua.edu.cn/apache/sqoop/1.4.6/ sqoop-1.4.6.bin__hadoop-2.0.4-alpha.ta ...

  5. GCD中各种队列和任务执行方式的组合

    一.概念回顾 1.GCD全称 Grand Central Dispatch ,是纯C语言,提供了非常多强大的函数,来进行系统线程的管理. 2.优势:GCD是苹果公司为多核的并行运算提出的解决方案.GC ...

  6. sqlserver新加一自增长的列,并且更新为行号

    --查询行号 select row_number()over(order by CHECKTIME )as RowNum,*from CHECKINOUT --更新id列为行号 update CHEC ...

  7. DNS域名记录

    DNS域名记录 DNS数据库 在DNS的解析过程中用到域名的解析资源的记录,这个解析记录在DNS当中称为DNS数据库. 这个数据库又分为正解和反解,正解就是从主机名到ip的过程,反解就是从ip反响解析 ...

  8. 清北考前刷题day1早安

    立方数(cubic) Time Limit:1000ms   Memory Limit:128MB 题目描述 LYK定义了一个数叫“立方数”,若一个数可以被写作是一个正整数的3次方,则这个数就是立方数 ...

  9. npm更换为淘宝镜像源

    1.通过config命令   1 2 npm config set registry http://registry.cnpmjs.org npm info underscore (如果上面配置正确这 ...

  10. ssm lodop打印图片不显示

    在打印预览的时候图片就是不显示 最终解决方案就是修改过滤器