Android 解决ListView异步加载网络数据(图片文字)出现位置错乱以及优化ListView的加载

本文详细探讨了Android中ListView在加载网络数据(包括图片和文字)时可能出现的位置错乱问题,并提供了两种解决方案:1. 通过在`getView()`中设置特定的tag来避免错位;2. 使用LruCache优化图片加载,减少内存消耗。同时,文章还介绍了如何通过优化ListView的缓存机制来提高性能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Android 多种方法解决以及优化ListView的加载网络数据程出现错乱的问题



1.普通的缓存机制存在的问题


下面的代码就是最常见的异步加载图片的方法:

  1. public View getView(int i, View view, ViewGroup viewGroup) {  
  2.         holder = new ViewHolder();  
  3.         if (view == null) {  
  4.             view = LayoutInflater.from(context).inflate(R.layout.list_item_view, null);  
  5.             holder.iv = (ImageView) view.findViewById(R.id.imageView);  
  6.             holder.title = (TextView) view.findViewById(R.id.text_title);  
  7.             holder.author = (TextView) view.findViewById(R.id.author);  
  8.             holder.date = (TextView) view.findViewById(R.id.date);  
  9.             view.setTag(holder);  
  10.         } else {  
  11.             holder = (ViewHolder) view.getTag();  
  12.         }  
  13.        // holder.iv.setTag(list.get(i).getImageurl());  
  14.         new ImageLoader(holder.iv,list.get(i).getImageurl(),list.get(i)).imageLoaderAsynctask();  
  15.         holder.title.setText(list.get(i).getTitle());  
  16.         holder.date.setText(list.get(i).getDate());  
  17.         holder.author.setText(list.get(i).getAuthor());  
  18.         return view;  
  19.     }  

  1. class ImageLoaderAsynctask extends AsyncTask {  
  2.       @Override  
  3.       protected Object doInBackground(Object[] objects) {  
  4.           Bitmap bitmap = null;  
  5.           try {  
  6.               bitmap = ParseData.getImageView(new URL(url));  
  7.               if (bitmap != null) {  
  8.                   item.setImageView(bitmap);  
  9. [java] view plain copy
  1.   //Thread.sleep(500);  
  2.         }  
  3.     } catch (IOException e) {  
  4.         e.printStackTrace();  
  5.     } catch (InterruptedException e) {  
  6.         e.printStackTrace();  
  7.     }  
  8.     return bitmap;  
  9. }  

上面的这种缓存复用机制,应该就是大家最常用的方法了吧,当数据量较少时,可能不存在问题,但是在数据量较大或者网络很差时,容易发生图片位置显示错误,或者图片闪动,这是因为AbsListView的缓存机制导致的,
\

上面的图片是我从网络上下载下来的,基本能说明这个机制的原理,当Item1隐藏了,Item8出来了,那么因为缓存机制,那么Item8就会复用Item1的convertView,但是复用的convertView中的子View也是Item1的,而且里面已经被赋值了,当网络很差或者同时加载的条数太多时,这时Item8不会瞬间加载属于他自己的图片,因为图片还在加载中,所以就会显示Item1的图片,这就是导致图片显示错误的原因,当Item8依然在当前能看见的显示列表里,而且Item8的图片已经加载完成了,那这时Item8又会显示回他原本的图片,这就是发生图片闪烁的原因。

上面的代码中我故意用了Thread.sleep(500)来模拟网络很卡的状态,看下面的显示效果:



上面的gif图能明显看出来卡顿和闪烁吧,这就是用传统的异步加载导致的后果。

1.1解决办法(一)

让整个的Item中的ImageView和文字在同一个Asynctask中进行异步加载:
  1. protected Object doInBackground(Object[] objects) {  
  2.        try {  
  3.            Log.d("zt","doInBackGround");  
[java]  view plain  copy
  1. //在此方法中获取网络数据  
  2.             getListViewData.startGetData();  
  3.         } catch (IOException e) {  
  4.             e.printStackTrace();  
  5.         } catch (JSONException e) {  
  6.             e.printStackTrace();  
  7.         }  
  8.         Log.d("zt","...................."+"Data.datacount.get(position)"+AllNewsData.newscount.get(position));  
  9.         Log.d("zt","AllNewsData.allNews.get(position)"+AllNewsData.allNews.get(position).size());  
  10.         adapter=  new MyListViewBaseAdapter( AllNewsData.allNews.get(position), context,AllNewsData.newscount.get(position) );  
  11.         return adapter;  
  12.     }  
上面用了startGetData()获取数据
  1. public void startGetData() throws IOException, JSONException {  
  2.        URL url = new URL((String) ListViewUtils.list.get(position));  
  3.        Log.d("zz", ListViewUtils.list.get(position) + "");  
  4.        HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();  
  5.        urlConnection.setRequestMethod("GET");  
  6.        urlConnection.setReadTimeout(CONNECT_READ_TIMENOUT);  
  7.        urlConnection.setUseCaches(false);  
  8.        urlConnection.connect();  
  9.        String a="";  
  10.        String data="";  
  11.        if (urlConnection.getResponseCode() == HttpURLConnection.HTTP_OK) {  
  12.            BufferedReader reader = new BufferedReader(new InputStreamReader(urlConnection.getInputStream()));  
  13.           while((a=reader.readLine())!=null){  
  14.               data+=a;  
  15.           }  
[java]  view plain  copy
  1. //此处解析数据  
  2.         parseData = new ParseData(data, position);  
  3.         parseData.getJsonData();  
  4.         reader.close();  
  5.     }  
  6.     urlConnection.disconnect();  
  7. }  
解析数据在parseData.getJsonData()中:
  1. public void getJsonData() throws JSONException, IOException {  
  2.        Log.d(ListViewUtils.TAG,"getJsonData"+"..."+da);  
  3.        JSONObject datas=new JSONObject(da);  
  4.        JSONObject result=datas.getJSONObject("result");  
  5.        JSONArray data=result.getJSONArray("data");  
  6.        Log.d(ListViewUtils.TAG,data.length()+"dataLength");  
  7.        for(int i=0;i<data.length();i++){  
  8.            newsItem=new NewsItem();  
  9.            JSONObject object=data.getJSONObject(i);  
  10.            String thumbnail_pic_s=object.getString("thumbnail_pic_s");  
  11.            newsItem.setImageurl(thumbnail_pic_s);  
[java]  view plain  copy
  1. //因为这个Json数据中提供的不是Image图片,而是另一条URL,所以在getImageView中进行图片的加载  
  2.             Bitmap bitmap= getImageView(new URL(thumbnail_pic_s));  
  3.             newsItem.setImageView(bitmap);  
  4.             String title=object.getString("title");  
  5.             newsItem.setTitle(title);  
  6.             String date=object.getString("date");  
  7.             newsItem.setDate(date);  
  8.             String author_name=object.getString("author_name");  
  9.             newsItem.setAuthor(author_name);  
  10.             String url=object.getString("url");  
  11.             newsItem.setArticleUrl(url);  
  12.             AllNewsData.allNews.get(position).add(newsItem);  
  13.         }  
  14.         AllNewsData.newscount.put(position, data.length());  
  15.         Log.d(ListViewUtils.TAG,data.length()+"..."+"getJsonData");  
  16.     }  
上面的图片加载在
  1. public static Bitmap  getImageView(URL url) throws IOException {  
  2.         if(url!=null){  
  3.             HttpURLConnection urlConnection= (HttpURLConnection) url.openConnection();  
  4.             urlConnection.setRequestMethod("GET");  
  5.             urlConnection.setReadTimeout(30000);  
  6.             urlConnection.setUseCaches(false);  
  7.             urlConnection.connect();  
  8.             Bitmap bitmap= BitmapFactory.decodeStream(urlConnection.getInputStream());  
  9.             urlConnection.disconnect();  
  10.             return  bitmap;  
  11.         }else{  
  12.             return null;  
  13.         }  
  14.     }  
[java]  view plain  copy
  1. @Override  
  2.   protected void onPostExecute(Object o) {  
  3.       lv.setAdapter((ListAdapter) o);  
  4.     //  dialog.dismiss();  
  5.   }  
上面的流程应该清楚了吧,我开启了一个Asynctask进行数据加载,然后在这个Asynctask中进行加载所有的东西,包括获取文字和所有的图片。因为Asynctasktask执行的顺序是先执行doInBackGround()里 的东西,也就是下载网络数据,当网络数据完成后,才会执行onPostExecute()方法里面的东西,下面看看效果:


上面的流程应该清楚了吧,我开启了一个Asynctask进行数据加载,然后在这个Asynctask中进行加载所有的东西,包括获取文字和所有的图片。因为Asynctasktask执行的顺序是先执行doInBackGround()里 的东西,也就是下载网络数据,当网络数据完成后,才会执行onPostExecute()方法里面的东西,下面看看效果:
看见了没,卡顿在加载界面有1-2s,你受的了?我的网速还是比较快的,而且 新闻条数只有30条,加载都这么慢,如果有100条,那得卡多久?所以这种办法虽然能解决图片显示错误的问题,但是加载的太慢太慢,所以这种办法是行不通的。


1.2解决办法二:

为每一个ImageVIew标上特定的标示tag,并通过这个tag来判断是否加载图片,这样就不会发生错乱了:


  1. public View getView(int i, View view, ViewGroup viewGroup) {  
  2.     holder = new ViewHolder();  
  3.     if (view == null) {  
  4.         view = LayoutInflater.from(context).inflate(R.layout.list_item_view, null);  
  5.         holder.iv = (ImageView) view.findViewById(R.id.imageView);  
  6.         holder.title = (TextView) view.findViewById(R.id.text_title);  
  7.         holder.author = (TextView) view.findViewById(R.id.author);  
  8.         holder.date = (TextView) view.findViewById(R.id.date);  
  9.         view.setTag(holder);  
  10.     } else {  
  11.         holder = (ViewHolder) view.getTag();  
  12.     }  
  13. //为每一个Item里的imageView设置一个特定的tag
  14.     holder.iv.setTag(list.get(i).getImageurl());  
  15.  //   holder.iv.setImageBitmap(BitmapFactory.decodeResource(context.getResources(),R.drawable.image));  
  16.    holder.iv.setImageBitmap(null);  
  17.     new ImageLoader(holder.iv, list.get(i).getImageurl(), list.get(i)).imageLoaderAsynctask();  
  18.     holder.title.setText(list.get(i).getTitle());  
  19.     holder.date.setText(list.get(i).getDate());  
  20.     holder.author.setText(list.get(i).getAuthor());  
  21.     return view;  
  22. }  


上面的代码可以看到,我为每个ImageView都标上了不同的tag:

  1. class ImageLoaderAsynctask extends AsyncTask {  
  2.         @Override  
  3.         protected Object doInBackground(Object[] objects) {  
  4.             Bitmap bitmap = null;  
  5.             try {  
  6.                 bitmap = ParseData.getImageView(new URL(url));  
  7.                 if (bitmap != null) {  
  8.                     item.setImageView(bitmap);  
  9.                 }  
  10.             } catch (IOException e) {  
  11.                 e.printStackTrace();  
  12.             }  
  13.             return bitmap;  
  14.         }  
  15.   
  16.         @Override  
  17.         protected void onPostExecute(Object o) {  
  18.           //通过判断之前设定的tag,与当前的url是否相同,来判断是否加载数据。  
  19.           if (iv.getTag().equals(url)) {  
  20.                 iv.setImageBitmap((Bitmap) o);  
  21.             }else{  
  22.   
  23.             }  
  24.         }  
  25.     }  

然后在加载的onPostExecute()中判断当前ImageView的tag与这个url是否相同,如果相同就显示,不同就不显示。

看见没有?已经不会再发生错乱了,注意:上面之所以会显示为白色,是因为我在上面的代码中将setImageBitmap(null),如果想为所有的Item预设一个图片,可以将这个null,换成那个图片。

如果用多线程实现异步加载,那就是下面这样。


  1. private Handler handler=new Handler(){  
  2.     @Override  
  3.     public void handleMessage(Message msg) { 
  4. //判断tag是否相同 
  5.         if(iv.getTag().equals(url)){  
  6.         iv.setImageBitmap(bitmap);  
  7.     }}  
  8. };  
  9.   
  10.   
  11. public void loadImageByThread(){  
  12.     new Thread(){  
  13.         @Override  
  14.         public void run() {  
  15.             try {  
  16.                 bitmap = ParseData.getImageView(new URL(url));  
  17.             } catch (IOException e) {  
  18.                 e.printStackTrace();  
  19.             }  
  20.   
  21.             Looper.prepare();  
  22.   
  23.             handler.sendEmptyMessage(0);  
  24.             Looper.loop();  
  25.         }  
  26.     }.start();  
  27. }  

2.优化ListView:

上面的Demo中加载网络数据,不知道你注意没有,有些图片已经加载过了,但是当滑动ListView,将已经显示了图片的Item隐藏过后,重新滑动ListView,让之前的Item重新显示,那么这个Item上面的数据会重新加载,不信,你可以看看上面的图片就可以知道了,这样的做法明显不太好,因为这样非常非常的耗流量,使用起来肯定不好,所以下面就对这个问题进行优化 ,整个优化的思想就是使用 缓存,将已经加载好的图片缓存起来,当显示一个item之前,先通过与缓存对应的key,来判断缓存中是否有这个key对应的缓存,如果有,就直接使用,不进行网络加载数据,否则就加载数据,并将加载好的数据显示到item上,同时将数据存入缓存中。

  1. public class MyListViewBaseAdapter extends BaseAdapter {  
  2.   
  3.     private ArrayList<NewsItem> list;  
  4.     private int position;  
  5.     private Context context;  
  6.     private int dataCount;  
  7.     private ViewHolder holder;  
  8.   
  9.     //定义LruCache  
  10.     private LruCache<String,Bitmap> lruCache;  
  11.     public MyListViewBaseAdapter(ArrayList<NewsItem> ll, Context context, int datacount) {  
  12.         this.list = ll;  
  13.         this.context = context;  
  14.        //获取最大缓存值  
  15.         int memorisize= (int) Runtime.getRuntime().maxMemory();  
  16.         //初始化LruCache并未该LruCache分配最大的缓存值  
  17.         lruCache=new LruCache<String,Bitmap>(memorisize/4){  
  18.             @Override  
  19.             protected int sizeOf(String key, Bitmap value) {  
  20.                //返回存储进缓存的Bitmap的大小  
  21.                 return value.getByteCount();  
  22.             }  
  23.         };  
  24.         dataCount = datacount;  
  25.     }  
  26.   
  27.     @Override  
  28.     public int getCount() {  
  29.         return dataCount;  
  30.     }  
  31.   
  32.     @Override  
  33.     public Object getItem(int i) {  
  34.         return null;  
  35.     }  
  36.   
  37.     @Override  
  38.     public long getItemId(int i) {  
  39.         return 0;  
  40.     }  
  41.   
  42.     private ImageView imageView;  
  43.     private TextView author, date, title;  
  44.   
  45.     @Override  
  46.     public View getView(int i, View view, ViewGroup viewGroup) {  
  47.         holder = new ViewHolder();  
  48.         if (view == null) {  
  49.             view = LayoutInflater.from(context).inflate(R.layout.list_item_view, null);  
  50.             holder.iv = (ImageView) view.findViewById(R.id.imageView);  
  51.             holder.title = (TextView) view.findViewById(R.id.text_title);  
  52.             holder.author = (TextView) view.findViewById(R.id.author);  
  53.             holder.date = (TextView) view.findViewById(R.id.date);  
  54.             view.setTag(holder);  
  55.         } else {  
  56.             holder = (ViewHolder) view.getTag();  
  57.         }  
  58.         holder.iv.setTag(list.get(i).getImageurl());  
  59.      //   holder.iv.setImageBitmap(BitmapFactory.decodeResource(context.getResources(),R.drawable.image));  
  60.        holder.iv.setImageBitmap(null);  
[java]  view plain  copy
  1. //通过构造器传进ImageLoader,让ImageLoader使用这个LruCache  
  2.        new ImageLoader(holder.iv, list.get(i).getImageurl(), list.get(i),lruCache).imageLoaderAsynctask();  
  3.        holder.title.setText(list.get(i).getTitle());  
  4.        holder.date.setText(list.get(i).getDate());  
  5.        holder.author.setText(list.get(i).getAuthor());  
  6.        return view;  
  7.    }  
  8.   
  9.    class ViewHolder {  
  10.        ImageView iv;  
  11.        TextView author, date, title;  
  12.    }  
上面定义了LruCache,然后传进ImageLoader,开始使用。
  1. public class ImageLoader {  
  2.   
  3.     private ImageView iv;  
  4.     private String url;  
  5.     private NewsItem item;  
  6.     private LruCache<String, Bitmap> lruCache;  
  7.       
  8.     public ImageLoader(ImageView iv, String url, NewsItem item, LruCache<String, Bitmap> lruCache) {  
  9.         this.iv = iv;  
  10.         this.url = url;  
  11.         this.item = item;  
[java]  view plain  copy
  1.      //传进来LruCache  
  2.         this.lruCache = lruCache;  
  3.   
  4.     }  
  5.   
  6.     //通过url来从缓存中获取对应的Bitmap  
  7.     private Bitmap getBitmapFromLruCache(String url) {  
  8.         return lruCache.get(url);  
  9.     }  
  10.   
  11.     //将Bitmap设置进缓存中  
  12.     private void setBitmapInLruCache(String url, Bitmap bitmap) {  
  13.         Bitmap bitmap1 = getBitmapFromLruCache(url);  
  14.         if (bitmap1 == null) {  
  15.             lruCache.put(url, bitmap);  
  16.         }  
  17.     }  
  18.   
  19.     public void imageLoaderAsynctask() {  
  20.         //加载图片之前先判断缓存中是否有该图片,如果存在,那么就直接使用缓存中的图片,否则通过网络数据加载。  
  21.         Bitmap bitmap = getBitmapFromLruCache(url);  
  22.         if (bitmap != null) {  
  23.             if (iv.getTag().equals(url)) {  
  24.                 iv.setImageBitmap(bitmap);  
  25.             }  
  26.         } else  
  27.             //缓存中不存在该数据,通过网络加载该数据。  
  28.             new ImageLoaderAsynctask().execute();  
  29.     }  
  30.   
  31.     private Bitmap bitmap = null;  
  32.     class ImageLoaderAsynctask extends AsyncTask {  
  33.         @Override  
  34.         protected Object doInBackground(Object[] objects) {  
  35.   
  36.             try {  
  37.                 bitmap = ParseData.getImageView(new URL(url));  
  38.                 if (bitmap != null) {  
  39.                     item.setImageView(bitmap);  
  40.   
  41.                     Log.d("ztt", url + "..." + bitmap);  
  42.                 }  
  43.                 //判断缓存中是否存在该数据  
  44.                 if (getBitmapFromLruCache(url) == null) {  
  45.                     //将通过网络加载的图片放入缓存中。  
  46.                     setBitmapInLruCache(url, bitmap);  
  47.                 }  
  48.             } catch (IOException e) {  
  49.                 e.printStackTrace();  
  50.             }  
  51.   
  52.   
  53.             return bitmap;  
  54.         }  
  55.   
  56.         @Override  
  57.         protected void onPostExecute(Object o) {  
  58.             if (iv.getTag().equals(url)) {  
  59.                 iv.setImageBitmap((Bitmap) o);  
  60.             } else {  
  61.   
  62.             }  
  63.         }  
  64.     }  
简单的来说,LruCache先定义,然后初始化,传进允许使用的最大缓存值,然后重写sizeof方法,然后就可以开始使用这个LruCache了,仔细看看上面的代码就能知道怎么用了下面看看效果吧:



怎么样,之前已经加载过的图片,就不会重新加载了,是不是觉得瞬间流畅了很多?当然,也为用户省了很多流量了!


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值