(BUG已修改,最优化)安卓ListView异步加载网络图片与缓存软引用图片,线程池,只加载当前屏之说明

时间:2023-03-08 20:04:51

原文:http://blog.****.net/java_jh/article/details/20068915

迟点出更新的.这个还有BUG.因为软引应不给力了.2.3之后

前几天的原文有一个线程管理与加载源过多,造成浪费流量的问题.下面对这进下改进的一些说明(红色为新加)

这两天一直在优化这个问题.google也很多种做法.但发现都是比较不全面.

比如:

一些只实现了异步加载,却没有线程池与软引用.

一些是用AsynTast的,

一些有了线程池但加载所有的图片,这样造成具大资源浪费

一些是用显示当前屏的item,但却用的是线程等待唤醒的方法.(这个不推荐)

AsynTast这个开始我也是用它的.后来发现很不理想呀

.于是今天就总结了一个思路.同时把前两天的代码改一下

部份代码来自网络

最终思路:  子线程加载网络图片并用缓存图片软引用..线程池管理子线程...根据当前屏第一item的potion与最后item的position加载当前屏显示的item图片.

拖动过程中的不加载..根据onScrollListener来监听是否停止拖动. l

istView.getFirstVisiblePosition()  当前屏第一条的下标

listView.getLastVisiblePosition();当前屏最后一条下标

最近开发个应用,里面大量的activity要用到listView这个控件.由于为了更加美观显示,就要自定义一个.

这下问题出来了.因为是获取网络图片.按传统的做法没办法及时加载对应的图片或者图片错位.
在网上找了很久.也抠了一天的源码..发现网上的都没有比较系统的说明.所以这里整理一下.
方便以后自己回看.
========================
先说一下思路: 我的理解为---- 因为要网络操作.所以加载图片在子线程中. 有延迟.但主线程都不等你子线程是否获取结果.它就走下去了.这样setImageDrawable(这里当然是没有了).
所以就会在你到ListView加载完时.看不到图片的原因. 
那么.在加载图片的子线程中,如果获取到图片之后.就handler发送一个信息到主线程.让它根据当前行的下标(postion)来更新图片.我管你主线程跟到哪了.管你等不等我.
反正我慢慢地下载图..下载到了我再叫你更新.
========================
首先说自定义的SimpleAdapter..
这里的传统做法大家都应懂的了.就是那个getView() 方法可以有点难理解
简单地说. 就是加载每一行数据(单行ListView).就调一次getView() 
public View getView(int position, View convertView, ViewGroup parent){}
position: 这个参数是指当前一行的下标. (从0开始的);
converTiew: 是可以理解为当前一屏..(不知对不对.我是这样理解的.)第一次执行convertView,如果是第一次就进行布局资源的创建操作 
如果拖进屏幕时.就可以复用到它了.不用每一屏都新建一个.这里下面代码里有说明
到图片加载了.我们定义一个图片加载的类.用一个静态方法来获取图片的Drawable
但由于优化内存使用,为了ListView加载了太多图片在内存中.那么.我们就进行缓存软引用机制来管理图片.
说得这么绕..无非就是指, 把得到的Drawable变成一个软引用.然后再把它放进map中.让系统自己的决定什么时候回收内存中的图片.
关于软引用...我个人的用法就是.但到一个drawable之后.马上new SoftReference<Drawable> (drawable) 存到map 中...那什么时候变回普通drawable呢
我认为当要从map中取出来之后.第一步就要变回普通的drawable(--softReference.get()--).这样的话.当我回来拖进listView时...就不会因为系统清理我的软引用导致看不到图了

下面上代码..先上异步获取图片的类

图片加载器就加了个线程池.

AsyncImageTask.java

  1. package com.naxieshu.util;
  2. import java.io.IOException;
  3. import java.io.InputStream;
  4. import java.lang.ref.SoftReference;
  5. import java.net.URL;
  6. import java.util.HashMap;
  7. import java.util.Map;
  8. import android.graphics.drawable.Drawable;
  9. import android.os.Handler;
  10. import android.os.Message;
  11. /**
  12. * 异步加截图片类
  13. * @author Hai Jiang
  14. * @Email 672335219@qq.ciom
  15. * @Data 2014-2-26
  16. */
  17. public class AsyncImageTask {
  18. <span style="color:#ff0000;">//开线程池
  19. ExecutorService executorService =  Executors.newCachedThreadPool()</span>;
  20. //缓存图片 把图片的软引用放到map中
  21. private Map<String, SoftReference<Drawable>> imageMap;
  22. //构造器
  23. public AsyncImageTask() {
  24. super();
  25. this.imageMap = new HashMap<String, SoftReference<Drawable>>();
  26. }
  27. //ID为标记,标记哪条记录image . 这个ID来自于自定义adapter的getView()方法中其中一个参数position
  28. public Drawable loadImage(final int id, final String imageUrl,
  29. final ImageCallback callback){
  30. //先看缓存(Map)中是否存在
  31. if(imageMap.containsKey(imageUrl)){
  32. SoftReference<Drawable> softReference = imageMap.get(imageUrl);
  33. if( softReference != null){
  34. Drawable draeable = softReferenct.get();
  35. if(drawable != null){
  36. callback.imageLoaded(drawable, id);
  37. return drawable;
  38. }
  39. }
  40. }
  41. //主线程更新图片
  42. final Handler handler = new Handler() {
  43. public void handleMessage(Message message) {
  44. callback.imageLoaded((Drawable) message.obj, id);
  45. }
  46. };
  47. //加载图片的线程
  48. executorService.submit(
  49. //加载图片的线程
  50. new Thread() {
  51. public void run() {
  52. //加载图片
  53. Drawable drawable = AsyncImageTask.loadImageByUrl(imageUrl);
  54. //加入缓存集合中 注意  这里就要把得到的图片变成软引用放到map中了
  55. imageMap.put(imageUrl, new SoftReference<Drawable>(drawable));
  56. //通知消息主线程更新UI  . 这里就是是否能异步刷新的留意点.
  57. Message message = handler.obtainMessage(0, drawable);
  58. handler.sendMessage(message);
  59. }
  60. });
  61. return null;
  62. //到这里就获取图片的静态方法就完了
  63. }
  64. //根据图片地址加载图片,并保存为Drawable
  65. //这里不用说了吧.都是一些基本的.从API从可以看
  66. public static Drawable loadImageByUrl(String imageUrl){
  67. URL url = null;
  68. InputStream inputStream = null;
  69. try {
  70. url = new URL(Constant.TARGETURL+imageUrl);
  71. inputStream = (InputStream) url.getContent();
  72. Drawable drawable = Drawable.createFromStream(inputStream,"src");
  73. return drawable;
  74. } catch (Exception e) {
  75. e.printStackTrace();
  76. } finally {
  77. try {
  78. if(inputStream != null)
  79. inputStream.close();
  80. } catch (IOException e) {
  81. e.printStackTrace();
  82. }
  83. }
  84. return null;
  85. }
  86. //利用接口回调,更新图片UI
  87. public interface ImageCallback {
  88. public void imageLoaded(Drawable obj, int id);
  89. }
  90. }

这里是自定义adapter类(这里因为主要是加个停止监听)

MyListAdapter.java

  1. package com.naxieshu.adapter;
  2. import java.lang.ref.SoftReference;
  3. import java.util.HashMap;
  4. import java.util.List;
  5. import java.util.Map;
  6. import android.content.ClipData.Item;
  7. import android.content.Context;
  8. import android.graphics.Bitmap;
  9. import android.graphics.drawable.Drawable;
  10. import android.util.Log;
  11. import android.view.LayoutInflater;
  12. import android.view.View;
  13. import android.view.ViewGroup;
  14. import android.widget.AbsListView;
  15. import android.widget.BaseAdapter;
  16. import android.widget.ImageView;
  17. import android.widget.ListView;
  18. import android.widget.SimpleAdapter;
  19. import android.widget.TextView;
  20. import android.widget.AbsListView.OnScrollListener;
  21. import com.naxieshu.activity.FindActivity;
  22. import com.naxieshu.activity.R;
  23. import com.naxieshu.domain.Book;
  24. import com.naxieshu.util.AsyncImageTask;
  25. import com.naxieshu.util.AsyncImageTask.ImageCallback;
  26. import com.naxieshu.util.ImageUtil;
  27. /**
  28. * 自定义List内容控件
  29. * @author Hai Jiang
  30. * @Email 672335219@qq.ciom
  31. * @Data 2014-2-26
  32. */
  33. public class MyListAdapter extends SimpleAdapter{
  34. public List<? extends Map<String, ?>> data;
  35. private LayoutInflater inflater;
  36. /**异步加载图片实例*/
  37. private AsyncImageTask imageTask;
  38. /**被绑定对象*/
  39. private ListView listView;
  40. /**Item对象集*/
  41. HashMap<String, Object> itemMap =  new HashMap<String, Object>();
  42. public MyListAdapter(final ListView listView,Context context,
  43. List<? extends Map<String, ?>> data) {
  44. super(context, data, 0, null, null);
  45. this.data = data;
  46. this.listView = listView;
  47. inflater = LayoutInflater.from(context);
  48. imageTask = new AsyncImageTask();
  49. /**注册监听事件*/
  50. listView.setOnScrollListener(onScrollListener);
  51. }
  52. /**
  53. * 在创建View资源对象的时候提供效率的缓存策略
  54. */
  55. class ViewHold{
  56. //book.cover
  57. public ImageView image;
  58. //book.title book.shortIntro
  59. public TextView namtView,idView,introView;
  60. }
  61. ViewHold hold =null;
  62. @Override
  63. public View getView(int position, View convertView, ViewGroup parent) {
  64. //数据源中与当前item对应的数据
  65. Book book = (Book) data.get(position).get(position+"");
  66. //判断是否第一次执行convertView,如果是第一次就进行布局资源的创建操作
  67. if (convertView == null){
  68. hold = new ViewHold();
  69. //填充加载布局资源
  70. convertView = inflater.inflate(R.layout.activity_find_listview, null);
  71. hold.image = (ImageView)convertView.findViewById(R.id.bookImage);
  72. hold.image.setImageDrawable(imageTask.loadImage(position, book.getCover(),imageCallback));
  73. hold.namtView = (TextView)convertView.findViewById(R.id.bookName);
  74. hold.idView = (TextView)convertView.findViewById(R.id.bookId);
  75. hold.introView = (TextView)convertView.findViewById(R.id.bookShortIntro);
  76. //保存标记
  77. convertView.setTag(hold);
  78. } else {
  79. hold = (ViewHold) convertView.getTag();
  80. }
  81. /**获取数据,进行数据填充*/
  82. // 标记图片视图,注意不能放在上面
  83. hold.image.setTag(position);
  84. hold.image.setImageResource(R.drawable.ic_launcher);
  85. hold.namtView.setText(book.getTitle());
  86. hold.idView.setText(book.getId());
  87. hold.idView.setVisibility(View.GONE);
  88. hold.introView.setText(book.getShortIntro());
  89. itemMap.put(position+"", hold);
  90. return convertView;
  91. }
  92. /**
  93. * 屏幕停止滚动监听器
  94. */
  95. AbsListView.OnScrollListener onScrollListener = new AbsListView.OnScrollListener() {
  96. public void onScrollStateChanged(AbsListView view, int scrollState) {
  97. if (scrollState == AbsListView.OnScrollListener.SCROLL_STATE_IDLE) {
  98. Log.i("--", "--");
  99. int start_index = listView.getFirstVisiblePosition();
  100. int end_index = listView.getLastVisiblePosition();
  101. pageImgLoad(start_index,end_index);
  102. }
  103. }
  104. public void onScroll(AbsListView view, int firstVisibleItem,
  105. int visibleItemCount, int totalItemCount) {
  106. // TODO Auto-generated method stub
  107. }
  108. };
  109. /**
  110. * 加载当前屏的图片
  111. */
  112. private void pageImgLoad(int start_index, int end_index) {
  113. for (; start_index < end_index; start_index++) {
  114. Book book = (Book) data.get(start_index).get(start_index+"");
  115. imageTask.loadImage(start_index, book.getCover(),imageCallback);
  116. }
  117. }
  118. /**回调函数*/
  119. AsyncImageTask.ImageCallback imageCallback = new ImageCallback(){
  120. public void imageLoaded(Drawable image, int position) {
  121. if (image != null) {
  122. //获取刚才标识的组件,并更新
  123. ImageView imageView = (ImageView) listView
  124. .findViewWithTag(position);
  125. if (imageView != null) {
  126. imageView.setImageDrawable(image);
  127. }
  128. }
  129. }
  130. };
  131. @Override
  132. public int getCount() {
  133. return data.size();
  134. }
  135. @Override
  136. public Object getItem(int position) {
  137. return itemMap.get(position+"");
  138. }
  139. @Override
  140. public long getItemId(int position) {
  141. return position;
  142. }
  143. }
    1. 到这里关键的都说完了.Activity那里那就贴代码了
    2. 主要是什么时候实例MyListAdapter要注意一下
    3. 如果你的是像搜结果显示在listView中的这种.
    4. 那么MyListAdapter的数据源就要放在Button的点击响应事件里获取..然后通Message把数据源发送到handler中.在这handler中实例MyListAdapter对象.再绑定到listView.
    5. 顺便说一下.另一种情况
    6. 得到数据源.但ListView不显示内容.这是为什么 ?
    7. 一般有两种原因.
    8. 1, ListView不在handler中绑定数据..因为对组件的更新更改操作.一 定要在主线程中弄
    9. 2.就是布局问题.你的ListView里的item不指定高度.----这个是最常见的..ListView的item一定要指定高度.
    10. 就是你定义准备套在ListView中的那个layout_xxxx.xml这个文件中的LinearLayout这些要指定高度(最外面一层)<pre></pre>
    11. <pre></pre>
    12. <pre></pre>
    13. <pre></pre>
    14. <pre></pre>
    15. <pre></pre>
    16. <pre></pre>