ListView异步加载图片分析
来源:互联网 发布:mac emoji 快捷键 编辑:程序博客网 时间:2024/06/04 17:57
一.概述
在ListView中,如果是同步加载图片还好,但是如果异步加载图片就会出现问题,下面我们来分析一下。
先准备一些图片作为数据源,我们新建一个Images类保存图片:
public class Images { public final static String[] imageUrls = new String[] { "http://img.my.csdn.net/uploads/201508/05/1438760758_3497.jpg", "http://img.my.csdn.net/uploads/201508/05/1438760758_6667.jpg", "http://img.my.csdn.net/uploads/201508/05/1438760757_3588.jpg", "http://img.my.csdn.net/uploads/201508/05/1438760756_3304.jpg", "http://img.my.csdn.net/uploads/201508/05/1438760755_6715.jpeg", "http://img.my.csdn.net/uploads/201508/05/1438760726_5120.jpg", "http://img.my.csdn.net/uploads/201508/05/1438760726_8364.jpg", "http://img.my.csdn.net/uploads/201508/05/1438760725_4031.jpg", "http://img.my.csdn.net/uploads/201508/05/1438760724_9463.jpg", "http://img.my.csdn.net/uploads/201508/05/1438760724_2371.jpg", "http://img.my.csdn.net/uploads/201508/05/1438760707_4653.jpg", "http://img.my.csdn.net/uploads/201508/05/1438760706_6864.jpg", "http://img.my.csdn.net/uploads/201508/05/1438760706_9279.jpg", "http://img.my.csdn.net/uploads/201508/05/1438760704_2341.jpg", "http://img.my.csdn.net/uploads/201508/05/1438760704_5707.jpg", "http://img.my.csdn.net/uploads/201508/05/1438760685_5091.jpg", "http://img.my.csdn.net/uploads/201508/05/1438760685_4444.jpg", "http://img.my.csdn.net/uploads/201508/05/1438760684_8827.jpg", "http://img.my.csdn.net/uploads/201508/05/1438760683_3691.jpg", "http://img.my.csdn.net/uploads/201508/05/1438760683_7315.jpg", "http://img.my.csdn.net/uploads/201508/05/1438760663_7318.jpg", "http://img.my.csdn.net/uploads/201508/05/1438760662_3454.jpg", "http://img.my.csdn.net/uploads/201508/05/1438760662_5113.jpg", "http://img.my.csdn.net/uploads/201508/05/1438760661_3305.jpg", "http://img.my.csdn.net/uploads/201508/05/1438760661_7416.jpg", "http://img.my.csdn.net/uploads/201508/05/1438760589_2946.jpg", "http://img.my.csdn.net/uploads/201508/05/1438760589_1100.jpg", "http://img.my.csdn.net/uploads/201508/05/1438760588_8297.jpg", "http://img.my.csdn.net/uploads/201508/05/1438760587_2575.jpg", "http://img.my.csdn.net/uploads/201508/05/1438760587_8906.jpg", "http://img.my.csdn.net/uploads/201508/05/1438760550_2875.jpg", "http://img.my.csdn.net/uploads/201508/05/1438760550_9517.jpg", "http://img.my.csdn.net/uploads/201508/05/1438760549_7093.jpg", "http://img.my.csdn.net/uploads/201508/05/1438760549_1352.jpg", "http://img.my.csdn.net/uploads/201508/05/1438760548_2780.jpg", "http://img.my.csdn.net/uploads/201508/05/1438760531_1776.jpg", "http://img.my.csdn.net/uploads/201508/05/1438760531_1380.jpg", "http://img.my.csdn.net/uploads/201508/05/1438760530_4944.jpg", "http://img.my.csdn.net/uploads/201508/05/1438760530_5750.jpg", "http://img.my.csdn.net/uploads/201508/05/1438760529_3289.jpg", "http://img.my.csdn.net/uploads/201508/05/1438760500_7871.jpg", "http://img.my.csdn.net/uploads/201508/05/1438760500_6063.jpg", "http://img.my.csdn.net/uploads/201508/05/1438760499_6304.jpeg", "http://img.my.csdn.net/uploads/201508/05/1438760499_5081.jpg", "http://img.my.csdn.net/uploads/201508/05/1438760498_7007.jpg", "http://img.my.csdn.net/uploads/201508/05/1438760478_3128.jpg", "http://img.my.csdn.net/uploads/201508/05/1438760478_6766.jpg", "http://img.my.csdn.net/uploads/201508/05/1438760477_1358.jpg", "http://img.my.csdn.net/uploads/201508/05/1438760477_3540.jpg", "http://img.my.csdn.net/uploads/201508/05/1438760476_1240.jpg", "http://img.my.csdn.net/uploads/201508/05/1438760446_7993.jpg", "http://img.my.csdn.net/uploads/201508/05/1438760446_3641.jpg", "http://img.my.csdn.net/uploads/201508/05/1438760445_3283.jpg", "http://img.my.csdn.net/uploads/201508/05/1438760444_8623.jpg", "http://img.my.csdn.net/uploads/201508/05/1438760444_6822.jpg", "http://img.my.csdn.net/uploads/201508/05/1438760422_2224.jpg", "http://img.my.csdn.net/uploads/201508/05/1438760421_2824.jpg", "http://img.my.csdn.net/uploads/201508/05/1438760420_2660.jpg", "http://img.my.csdn.net/uploads/201508/05/1438760420_7188.jpg", "http://img.my.csdn.net/uploads/201508/05/1438760419_4123.jpg", }; }
然后是我们的重点,适配器:
public class ImageAdapter extends ArrayAdapter<String>{ LruCache<String, BitmapDrawable> memoryCache ; public ImageAdapter(Context context, int resource, String[] objects) { super(context, resource, objects); //返回系统可用的最大堆内存,单位是byte,我的手机算下来是256MB int maxMemory = (int) Runtime.getRuntime().maxMemory(); //手机:268435456B = 256MB //模拟器:16777216B = 16MB int cacheSize = maxMemory/8;//指定缓存大小为内存的八分之一 memoryCache = new LruCache<String,BitmapDrawable>(cacheSize){ //重写此方法,返回每张图片的大小 protected int sizeOf(String key, BitmapDrawable value) { //每张图片大小:108000B = 105KB return value.getBitmap().getByteCount();}; }; } @Override public View getView(int position, View convertView, ViewGroup parent) { //获取对应位置图片的url地址 String url = getItem(position); View view; if(convertView == null){ view = View.inflate(getContext(), R.layout.image_item, null); }else{ view = convertView; } ImageView imageView = (ImageView) view.findViewById(R.id.imageitem); imageView.setImageResource(R.drawable.ic_launcher); //根据url从内存中取 BitmapDrawable drawable = getBitmapFromMemory(url); if(drawable!=null){ //内存中有,直接设置给imageview imageView.setImageDrawable(drawable); }else{ //内存没有,开启异步任务去下载 BitmapWorkerTask task = new BitmapWorkerTask(imageView); task.execute(url); } return view; } /** * 获取对应位置图片的url地址 */ @Override public String getItem(int position) { return Images.imageUrls[position]; } /** * 将图片添加到lrucache中 * @param key * @param drawable */ public void addBitmapToMemory(String key,BitmapDrawable drawable){ if(getBitmapFromMemory(key)==null){ memoryCache.put(key, drawable); } } /** * 根据url从内存中获取图片 * @param key * @return */ public BitmapDrawable getBitmapFromMemory(String key){ return memoryCache.get(key); } /** * String:启动任务执行的输入参数的类型 *Void:后台任务执行的百分比 *BitmapDrawable:后台执行任务最终的返回结果 */ public class BitmapWorkerTask extends AsyncTask<String, Void, BitmapDrawable>{ private ImageView imageView; private String imageUrl; public BitmapWorkerTask(ImageView imageView) { this.imageView = imageView; } @Override protected BitmapDrawable doInBackground(String... params) { imageUrl = params[0];//获取当前图片的地址 Bitmap bitmap = downloadBitmap(imageUrl); BitmapDrawable bitmapDrawable = new BitmapDrawable(getContext().getResources(),bitmap); //将下载好的图片添加到缓存中 addBitmapToMemory(imageUrl, bitmapDrawable); return bitmapDrawable; } @Override protected void onPostExecute(BitmapDrawable result) { super.onPostExecute(result); if(imageView!=null&&result!=null){ imageView.setImageDrawable(result); } } } /** * 下载图片 * @param imageUrl * @return */ public Bitmap downloadBitmap(String imageUrl){ Bitmap bitmap = null; HttpURLConnection connection = null; try { URL url = new URL(imageUrl); connection = (HttpURLConnection) url.openConnection(); connection.setConnectTimeout(5*000); connection.setReadTimeout(3*1000); bitmap = BitmapFactory.decodeStream(connection.getInputStream()); } catch (Exception e) { e.printStackTrace(); }finally{ if(connection!=null){ connection.disconnect(); } } return bitmap; }}
最后在代码中我们使用这个适配器,给ListView设置数据
public class MainActivity extends Activity { private ListView listView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); listView = (ListView) findViewById(R.id.listview); listView.setAdapter(new ImageAdapter(this, 0, Images.imageUrls)); }}
以上就简单实现了ListView图片异步加载,我们看看效果吧:
可以看到图片出现了错位和乱序的情况,为什么会这样呢?这就和ListView的工作原理有关了。
那么这里我们就可以思考一下了,目前数据源当中大概有60个图片的URL地址,而根据ListView的工作原理,显然不可能为每张图片都单独分配一个ImageView控件,ImageView控件的个数其实就比一屏能显示的图片数量稍微多一点而已,移出屏幕的ImageView控件会进入到RecycleBin当中,而新进入屏幕的元素则会从RecycleBin中获取ImageView控件。
那么,每当有新的元素进入界面时就会回调getView()方法,而在getView()方法中会开启异步请求从网络上获取图片,注意网络操作都是比较耗时的,也就是说当我们快速滑动ListView的时候就很有可能出现这样一种情况,某一个位置上的元素进入屏幕后开始从网络上请求图片,但是还没等图片下载完成,它就又被移出了屏幕。这种情况下会产生什么样的现象呢?根据ListView的工作原理,被移出屏幕的控件将会很快被新进入屏幕的元素重新利用起来,而如果在这个时候刚好前面发起的图片请求有了响应,就会将刚才位置上的图片显示到当前位置上,因为虽然它们位置不同,但都是共用的同一个ImageView实例,这样就出现了图片乱序的情况。
但是还没完,新进入屏幕的元素它也会发起一条网络请求来获取当前位置的图片,等到图片下载完的时候会设置到同样的ImageView上面,因此就会出现先显示一张图片,然后又变成了另外一张图片的情况,那么刚才我们看到的图片会自动变来变去的情况也就得到了解释。
那么如何来解决这个问题呢,下面介绍一种比较简单的方法,当然还有其他的方法,这里就不一一介绍了。
public class ImageAdapter extends ArrayAdapter<String>{ LruCache<String, BitmapDrawable> memoryCache ; private ListView mListView; public ImageAdapter(Context context, int resource, String[] objects) { super(context, resource, objects); //返回系统可用的最大堆内存,单位是byte,我的手机算下来是256MB int maxMemory = (int) Runtime.getRuntime().maxMemory(); //手机:268435456B = 256MB //模拟器:16777216B = 16MB int cacheSize = maxMemory/8;//指定缓存大小为内存的八分之一 memoryCache = new LruCache<String,BitmapDrawable>(cacheSize){ //重写此方法,返回每张图片的大小 protected int sizeOf(String key, BitmapDrawable value) { //每张图片大小:108000B = 105KB return value.getBitmap().getByteCount();}; }; } @Override public View getView(int position, View convertView, ViewGroup parent) { //获取对应位置图片的url地址 String url = getItem(position); if(mListView == null){ mListView = (ListView) parent; } View view; if(convertView == null){ view = View.inflate(getContext(), R.layout.image_item, null); }else{ view = convertView; } ImageView imageView = (ImageView) view.findViewById(R.id.imageitem); imageView.setTag(url); imageView.setImageResource(R.drawable.ic_launcher); //根据url从内存中取 BitmapDrawable drawable = getBitmapFromMemory(url); if(drawable!=null){ //内存中有,直接设置给imageview imageView.setImageDrawable(drawable); }else{ //内存没有,开启异步任务去下载 BitmapWorkerTask task = new BitmapWorkerTask(); task.execute(url); } return view; } /** * 获取对应位置图片的url地址 */ @Override public String getItem(int position) { return Images.imageUrls[position]; } /** * 将图片添加到lrucache中 * @param key * @param drawable */ public void addBitmapToMemory(String key,BitmapDrawable drawable){ if(getBitmapFromMemory(key)==null){ memoryCache.put(key, drawable); } } /** * 根据url从内存中获取图片 * @param key * @return */ public BitmapDrawable getBitmapFromMemory(String key){ return memoryCache.get(key); } /** * String:启动任务执行的输入参数的类型 *Void:后台任务执行的百分比 *BitmapDrawable:后台执行任务最终的返回结果 */ public class BitmapWorkerTask extends AsyncTask<String, Void, BitmapDrawable>{ private ImageView imageView; private String imageUrl;// public BitmapWorkerTask(ImageView imageView) {// this.imageView = imageView;// } @Override protected BitmapDrawable doInBackground(String... params) { imageUrl = params[0];//获取当前图片的地址 Bitmap bitmap = downloadBitmap(imageUrl); BitmapDrawable bitmapDrawable = new BitmapDrawable(getContext().getResources(),bitmap); //将下载好的图片添加到缓存中 addBitmapToMemory(imageUrl, bitmapDrawable); return bitmapDrawable; } @Override protected void onPostExecute(BitmapDrawable result) { imageView = (ImageView) mListView.findViewWithTag(imageUrl); super.onPostExecute(result); if(imageView!=null&&result!=null){ imageView.setImageDrawable(result); } } } /** * 下载图片 * @param imageUrl * @return */ public Bitmap downloadBitmap(String imageUrl){ Bitmap bitmap = null; HttpURLConnection connection = null; try { URL url = new URL(imageUrl); connection = (HttpURLConnection) url.openConnection(); connection.setConnectTimeout(5*000); connection.setReadTimeout(3*1000); bitmap = BitmapFactory.decodeStream(connection.getInputStream()); } catch (Exception e) { e.printStackTrace(); }finally{ if(connection!=null){ connection.disconnect(); } } return bitmap; }}
这里我们使用findViewWithTag来解决这个问题,这里我们可以尝试分析一下findViewWithTag的工作原理,其实顾名思义,这个方法就是通过Tag的名字来获取具备该Tag名的控件,我们先要调用控件的setTag()方法来给控件设置一个Tag,然后再调用ListView的findViewWithTag()方法使用相同的Tag名来找回控件。
那么为什么用了findViewWithTag()方法之后,图片就不会再出现乱序情况了呢?其实原因很简单,由于ListView中的ImageView控件都是重用的,移出屏幕的控件很快会被进入屏幕的图片重新利用起来,那么getView()方法就会再次得到执行,而在getView()方法中会为这个ImageView控件设置新的Tag,这样老的Tag就会被覆盖掉,于是这时再调用findVIewWithTag()方法并传入老的Tag,就只能得到null了,而我们判断只有ImageView不等于null的时候才会设置图片,这样图片乱序的问题也就不存在了
- ListView异步加载图片分析
- listview 异步加载图片
- ListView异步加载图片
- ListView 异步加载图片
- ListView异步加载图片
- ListView 异步加载图片
- listview异步图片加载
- ListView异步加载图片
- listview 异步加载图片
- listView异步加载图片
- ListView异步加载图片
- ListView异步加载图片
- listView 异步加载图片
- listview异步加载图片
- android listview异步加载图片问题分析解决方案
- Android ListView异步加载图片
- Android ListView异步加载图片
- android ListView 异步加载图片
- 苹果证书发布过期为题
- java容器源码--hashmap源码解读
- Typical memory leak (C++中典型的内存泄露)
- Swift Swizzle
- Eclipse中文乱码问题
- ListView异步加载图片分析
- 记录如何在Raid1+LVM的基础上再添加两块硬盘
- CSS中DIV居中
- 用sshpass实现ssh的自动登陆
- Qt浅谈之四十三Linux下有系统托盘再运行弹出已运行的实例
- 文章标题
- 初学Redis(3)——用Redis作为Mysql数据库的缓存
- 关于扩展欧几里得算法(exgcd)的总结与复习
- 深入分析JavaWeb 29 -- 使用JDBC处理大数据(MySql + Oracle)