Android-异步图像装载机
在ListView加载图像是非常常见的场景,图像加载几个要求满足以下的:
(1)是否画面位于网络或本地上,装载不应同步。但应该异步加载,例如,使用AsyncTask。
(2)为了避免重复下载图片和网页显示速度,通常做缓存,比方最常见的LruCache。
(3)为了提高Listview的性能。我们通常会用holder来重用Listview的item。
代码大概就是这种:
public class MainActivity extends Activity {
private ImageLoader imageLoader;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
imageLoader = new ImageLoader(new ImageDownloader(){
@Override
public Bitmap download(String path, int width, int height) {
return HttpUtil.download(path);
}
});
final ListView listview = (ListView)this.findViewById(R.id.listview);
Button btn = (Button)this.findViewById(R.id.btn);
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
List<ItemBean> dataList = getDataList();
listview.setAdapter(new ListViewAdapter(MainActivity.this, dataList));
}
});
}
@Override
protected void onDestroy() {
super.onDestroy();
imageLoader.destory();
}
private class ListViewAdapter extends BaseAdapter{
private Context context;
private List<ItemBean> dataList;
public ListViewAdapter(Context context, List<ItemBean> dataList){
this.context = context;
this.dataList = dataList;
}
@Override
public int getCount() {
return dataList.size();
}
@Override
public Object getItem(int position) {
return dataList.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
Holder holder = null;
if(convertView == null){
holder = new Holder();
convertView = new ItemView(context);
holder.itemView = (ItemView)convertView;
convertView.setTag(holder);
}else{
holder = (Holder)convertView.getTag();
}
ItemView itemView = holder.itemView;
ImageView itemImageView = itemView.getImageView();
ItemBean item = dataList.get(position);
// 先设置一个默认的图片
// 假如不设置,当页面滑到了某个正在载入的item上,恰好这个item是复用的前面的已经显示的item
// 那么这个item首先会显示前一个item的图片,等自己的下载完毕以后,再替换掉这个图片,
// 假例如以下载时间非常长,会让用户感觉图片错乱了!
itemImageView.setImageResource(R.drawable.ic_launcher);
//随后下载实际的图片
imageLoader.loadImage(item.getImagePath(), 50, 50, itemImageView);
return itemView;
}
class Holder{
ItemView itemView;
}
}
如今问题就出现了,考虑以下的场景:
下载一幅图片的时间非常长,比方说10s。每一页显示3个item。
用户第一次打开页面,第一页应该展示item0。item1,item2。在item0还没下载完的时候。用户滑到了第3页。第3页应该展示的是item6,item7,item8。那么这一页的item肯定是重用的第一页的那些item。
此时。用户等待页面载入。假如,item6重用的是item0。item7重用的是item1,item8重用的是item2。当item0下载完毕以后。item6上展示的是item0上的图片,这就混乱了。仅仅有当item6自己的图片下载完以后,item6展示的才是正确的图片!
假设在载入的过程中,用户不停的滑动,那么用户看到的页面就是全然错乱的!
本文的图片载入器就能够避免这个问题,是一个同事写的。感觉非常不错。就直接拿过来了。看下代码:
public class ImageLoader {
private static final String TAG = "ImageLoader";
private ImageCache cache;
private HashSet<String> cacheKeys = new HashSet<String>();
private ImageDownloader downloader;
// 保存filepath和ImageView的关系。由于ImageView会复用,所以仅仅有这个关系才是正确的关系
// 一个imageView仅仅能相应一个filepath。一个filepath相应一个物理文件
private WeakHashMap<ImageView, String> imageView2FileMap = new WeakHashMap<ImageView, String>();
// 一个filepath可能相应多个imageView,由于有可能会有多个imageView显示同一张图片
private HashMap<String, HashSet<ImageViewReference>> file2ImageViewMap = new HashMap<String, HashSet<ImageViewReference>>();
// 正在读的或者已经在列队里的filepath,读完删除
private HashSet<String> fileInLoadSet = new HashSet<String>();
public ImageLoader(ImageDownloader downloader) {
if(downloader == null){
throw new RuntimeException("ImageDownloader can not be null");
}
this.cache = ImageCache.getInstance();
this.downloader = downloader;
}
/**
* 给imageView设置图片
*
* @param filePath
* 图片路径
* @param width
* 宽
* @param height
* 高
* @param imageView
* @return 缓存中有。直接设置,并返回true,没有异步读取,读完再设置。返回false
*/
public boolean loadImage(String filePath, int width, int height, ImageView imageView) {
String filePathKey = getKeyForFilePath(filePath, width, height);
Bitmap bmp = cache.get(filePathKey);
if (bmp == null) {
ImageViewReference imageViewRef = new ImageViewReference(imageView);
// 更新imageView和filepath的最新的关系
imageView2FileMap.put(imageView, filePathKey);
HashSet<ImageViewReference> imageViewSet = file2ImageViewMap.get(filePathKey);
if (imageViewSet == null) {
imageViewSet = new HashSet<ImageViewReference>();
file2ImageViewMap.put(filePathKey, imageViewSet);
}
imageViewSet.add(imageViewRef);
// 不会反复下载
if (fileInLoadSet.contains(filePathKey)) {
return false;
} else {
fileInLoadSet.add(filePathKey);
}
Holder holder = new Holder();
holder.width = width;
holder.height = height;
holder.filePath = filePath;
holder.filePathKey = filePathKey;
holder.imageViewRef = imageViewRef;
new ImageLoadTask().execute(holder);
return false;
} else {
imageView.setImageBitmap(bmp);
return true;
}
}
private class ImageLoadTask extends AsyncTask<Holder, Void, Holder> {
@Override
protected Holder doInBackground(Holder... params) {
Holder holder = params[0];
int width = holder.width;
int height = holder.height;
String filePath = holder.filePath;
String filePathKey = holder.filePathKey;
// 找到key相应的全部imageView,假设imageView的数量是0说明不用下载了
int count = getCountOfImageViewForKey(filePathKey);
if (count <= 0) {
return null;
}
try {
Random rnd = new Random();
Thread.sleep((int) (1000 * rnd.nextDouble()));
} catch (Exception e) {
e.printStackTrace();
}
// 開始读取,放入cache
if(downloader != null){
//Bitmap bmp = ImageUtil.compressBitmap(filePath, width, height);
Bitmap bmp = downloader.download(filePath, width, height);
if(bmp != null){
cache.put(filePathKey, bmp);
cacheKeys.add(filePath);
holder.imageData = bmp;
}
}
return holder;
}
@Override
protected void onPostExecute(Holder holder) {
super.onPostExecute(holder);
// 读完图片,把key移除
String filePathKey = holder.filePathKey;
fileInLoadSet.remove(filePathKey);
Bitmap data = holder.imageData;
if(data == null){
return;
}
ArrayList<ImageView> imageViewArrayList = getImageViewListForKey(filePathKey);
if (imageViewArrayList.size() == 0) {
return;
}
// 遍历imageview列表,通过imageView2FileMap查找该imageView相应的最新的latestFilePathKey是不是刚刚下载好的这个filePathKey
// 仅仅有一直才须要显示,假设不一致。说明该imageView已经被复用。相应到了新的key
for (ImageView imageView : imageViewArrayList) {
String latestFilePathKey = imageView2FileMap.get(imageView);
if (latestFilePathKey != null && latestFilePathKey.equals(filePathKey)) {
if (imageView != null) {
imageView.setImageBitmap(data);
Log.e(TAG, "设置图片 ");
/*
* boolean isSet;
* try{
* isSet=(Boolean)
* imageView.getTag();
* }catch(Exception e) {
* isSet=true;
* }
* if(isSet) {
* imageView.setImageBitmap(result);
* Log.e(TAG,"设置图片 ");
* }
*/
}
// 即使不remove。也会自己主动回收
imageView2FileMap.remove(imageView);
} else {
}
}
file2ImageViewMap.remove(filePathKey);
}
}
class Holder {
int width,height;
String filePath, filePathKey;
Bitmap imageData;
ImageViewReference imageViewRef;
}
private String getKeyForFilePath(String imagePath, int width, int height) {
return imagePath + "_" + width + "_" + height;
}
/**
* 销毁ImageLoader
*
* */
public void clear(){
imageView2FileMap.clear();
file2ImageViewMap.clear();
fileInLoadSet.clear();
for(String cacheKey : cacheKeys){
cache.remove(cacheKey);
}
cacheKeys.clear();
imageView2FileMap = null;
file2ImageViewMap = null;
fileInLoadSet = null;
cacheKeys = null;
downloader = null;
cache = null;
}
/**
* 销毁ImageLoader, 应用退出的时候调用
*
* */
public void destory() {
clear();
ImageCache.destroy();
}
public interface ImageDownloader{
public Bitmap download(String path,int width, int height);
}
/**
* 通过file2ImageViewMap获取filePath相应的全部imageView列表 同一时候删除被回收的imageView,
*
* @param filePathKey
* @return
*/
private ArrayList<ImageView> getImageViewListForKey(String filePathKey) {
ArrayList<ImageView> imageViewArrayList = new ArrayList<ImageView>();
HashSet<ImageViewReference> imageViewReferences = file2ImageViewMap.get(filePathKey);
if(imageViewReferences == null){
return null;
}
Iterator<ImageViewReference> it = imageViewReferences.iterator();
while (it.hasNext()) {
ImageViewReference reference = it.next();
if (reference.get() != null) {
imageViewArrayList.add(reference.get());
} else {
it.remove();
}
}
return imageViewArrayList;
}
/**
* 获取指定的filePath相应的有效imageView的数量
*
* @param filePathKey
* @return
*/
private int getCountOfImageViewForKey(String filePathKey) {
ArrayList<ImageView> imageViewArrayList = getImageViewListForKey(filePathKey);
if(imageViewArrayList == null){
return 0;
}else{
return imageViewArrayList.size();
}
}
private static class ImageCache extends LruCache<String, Bitmap> {
private static final int cacheSize = 10 * 1024 * 1024;
private static ImageCache instance = new ImageCache(cacheSize);
public static ImageCache getInstance(){
return instance;
}
private ImageCache(int maxSize) {
super(maxSize);
}
@Override
protected int sizeOf(String key, Bitmap value) {
return value.getByteCount();
}
public static void destroy(){
if(instance == null){
return;
}
instance.evictAll();
instance = null;
}
}
private static class ImageViewReference extends WeakReference<ImageView> {
public ImageViewReference(ImageView r) {
super(r);
}
@Override
public boolean equals(Object o) {
ImageViewReference other=(ImageViewReference)o;
return this.get()==other.get();
}
@Override
public int hashCode() {
ImageView imageView = this.get();
if(imageView != null){
return imageView.hashCode();
}
return 0;
}
}
}
源代码在这里:http://download.csdn.net/download/goldenfish1919/7320823
版权声明:本文博客原创文章,博客,未经同意,不得转载。
Android-异步图像装载机的更多相关文章
- 演化理解 Android 异步加载图片
原文:http://www.cnblogs.com/ghj1976/archive/2011/05/06/2038738.html#3018499 在学习"Android异步加载图像小结&q ...
- Android异步载入全解析之使用多线程
异步载入之使用多线程 初次尝试 异步.异步,事实上说白了就是多任务处理.也就是多线程执行.多线程那就会有各种问题,我们一步步来看.首先.我们创建一个class--ImageLoaderWithoutC ...
- Android异步载入全解析之大图处理
Android异步载入全解析之大图处理 异步载入中很重要的一部分就是对图像的处理,这也是我们前面用异步载入图像做示例的原因. 一方面是由于图像处理不好的话会很占内存,并且easyOOM,还有一方面,图 ...
- Android异步载入全解析之使用AsyncTask
Android异步载入全解析之使用AsyncTask 概述 既然前面提到了多线程,就不得不提到线程池,通过线程池,不仅能够对并发线程进行管理.更能够提高他们运行的效率.优化整个App.当然我们能够自己 ...
- Android异步载入全解析之开篇瞎扯淡
Android异步载入 概述 Android异步载入在Android中使用的很广泛,除了是由于避免在主线程中做网络操作.更是为了避免在显示时由于时间太长而造成ANR,添加显示的流畅性,特别是像List ...
- Android异步回调中的UI同步性问题
Android程序编码过程中,回调无处不在.从最常见的Activity生命周期回调开始,到BroadcastReceiver.Service以及Sqlite等.Activity.BroadcastRe ...
- 【android异步处理】一个关于android异步处理的文章系列
最近读了Android异步处理系列文章索引,感觉这个文章系列写得不错!可以作为参考
- [转载]Android 异步加载解决方案
2013-12-25 11:15:47 Android 异步加载解决方案,转载自: http://www.open-open.com/lib/view/open1345017746897.html 请 ...
- Android异步更新UI的四种方式
Android异步更新UI的四种方式 2015-09-06 09:23 segmentfault 字号:T | T 大家都知道由于性能要求,android要求只能在UI线程中更新UI,要想在其他线程中 ...
- Android异步处理一:使用Thread+Handler实现非UI线程更新UI界面
Android应用的开发过程中需要把繁重的任务(IO,网络连接等)放到其他线程中异步执行,达到不阻塞UI的效果. 下面将由浅入深介绍Android进行异步处理的实现方法和系统底层的实现原理. 本文介绍 ...
随机推荐
- 十依据一个有用的算法来找到最小(最大)的k的数量-线性搜索算法
例如:进入1.2.3,4,5,6.7.8此8数字,最小的4图的1,2,3,4. 思路1:最easy想到的方法:先对这个序列从小到大排序.然后输出前面的最小的k个数就可以.假设选择高速排序法来进行排序, ...
- [ACM] POJ 3252 Round Numbers (的范围内的二元0数大于或等于1数的数目,组合)
Round Numbers Time Limit: 2000MS Memory Limit: 65536K Total Submissions: 8590 Accepted: 3003 Des ...
- Installshield自动安装IIS组件
原文:Installshield自动安装IIS组件 一. 工程类型:IS2010 installscript 二.原理: 1. XP和 Server2003系统:由于系统默认没有自带IIS组件,一般情 ...
- git checkout 命令详解(转)
在日常的git操作中,git checkout——检出,是我们的常用命令.最为常用的两种情形是创建分支和切换分支. 在下面的命令中,使用了一些简写,在这里说明一下: git st # git stat ...
- SpringMVC注释启用
这篇文章是我学习的网络视频SpringMVC写的过程. 谢谢公布各位前辈的视频 以下评论SpringMVC几个关键步骤,注意事项启用: 首先需要加载配置文件(假设请使用自定义路径) <? xml ...
- DB Error: 1 "unrecognized token: ":""
在网上查找http://blog.csdn.net/heihuifeng/article/details/6561615本文 .插入的字符串需加引號', [_db executeUpdate:[NSS ...
- android:更改PagerTabStrip背景颜色,标题字体样式、颜色和图标,以及指示条的颜色
1.更改PagerTabStrip背景颜色 我们直接在布局中设置background属性可以: <android.support.v4.view.ViewPager android:id=&qu ...
- java 科学计算库
stackoverflow 上的讨论, 其中不乏lib的作者... http://stackoverflow.com/questions/529457/performance-of-java-matr ...
- AngularJS 疑难问题解决汇总
AngularJS 防止页面闪烁的方法 angularjs filter 详解 学习资料1 学习资料2 在 AngularJS 应用中处理单选框和复选框 学习资料3 AngularJS 之 Facto ...
- WeakReference and WeakHashMap
弱引用通过WeakReference类实现,弱引用和软引用很像,但弱引用的引用级别更低.对于只有弱引用的对象而言,当系统垃圾回收机制运行时,不管系统北村是否足够,总会回收该对象所占用的内存.当然,并不 ...