Android 多种方法解决以及优化ListView的加载网络数据程出现错乱的问题
1.普通的缓存机制存在的问题
下面的代码就是最常见的异步加载图片的方法:
- public View getView(int i, View view, ViewGroup viewGroup) {
- holder = new ViewHolder();
- if (view == null) {
- view = LayoutInflater.from(context).inflate(R.layout.list_item_view, null);
- holder.iv = (ImageView) view.findViewById(R.id.imageView);
- holder.title = (TextView) view.findViewById(R.id.text_title);
- holder.author = (TextView) view.findViewById(R.id.author);
- holder.date = (TextView) view.findViewById(R.id.date);
- view.setTag(holder);
- } else {
- holder = (ViewHolder) view.getTag();
- }
- // holder.iv.setTag(list.get(i).getImageurl());
- new ImageLoader(holder.iv,list.get(i).getImageurl(),list.get(i)).imageLoaderAsynctask();
- holder.title.setText(list.get(i).getTitle());
- holder.date.setText(list.get(i).getDate());
- holder.author.setText(list.get(i).getAuthor());
- return view;
- }
上面的这种缓存复用机制,应该就是大家最常用的方法了吧,当数据量较少时,可能不存在问题,但是在数据量较大或者网络很差时,容易发生图片位置显示错误,或者图片闪动,这是因为AbsListView的缓存机制导致的,

上面的图片是我从网络上下载下来的,基本能说明这个机制的原理,当Item1隐藏了,Item8出来了,那么因为缓存机制,那么Item8就会复用Item1的convertView,但是复用的convertView中的子View也是Item1的,而且里面已经被赋值了,当网络很差或者同时加载的条数太多时,这时Item8不会瞬间加载属于他自己的图片,因为图片还在加载中,所以就会显示Item1的图片,这就是导致图片显示错误的原因,当Item8依然在当前能看见的显示列表里,而且Item8的图片已经加载完成了,那这时Item8又会显示回他原本的图片,这就是发生图片闪烁的原因。
上面的代码中我故意用了Thread.sleep(500)来模拟网络很卡的状态,看下面的显示效果:
上面的gif图能明显看出来卡顿和闪烁吧,这就是用传统的异步加载导致的后果。
1.1解决办法(一)
让整个的Item中的ImageView和文字在同一个Asynctask中进行异步加载:
上面用了startGetData()获取数据
解析数据在parseData.getJsonData()中:
上面的图片加载在
上面的流程应该清楚了吧,我开启了一个Asynctask进行数据加载,然后在这个Asynctask中进行加载所有的东西,包括获取文字和所有的图片。因为Asynctasktask执行的顺序是先执行doInBackGround()里 的东西,也就是下载网络数据,当网络数据完成后,才会执行onPostExecute()方法里面的东西,下面看看效果:
看见了没,卡顿在加载界面有1-2s,你受的了?我的网速还是比较快的,而且
新闻条数只有30条,加载都这么慢,如果有100条,那得卡多久?所以这种办法虽然能解决图片显示错误的问题,但是加载的太慢太慢,所以这种办法是行不通的。
1.2解决办法二:
为每一个ImageVIew标上特定的标示tag,并通过这个tag来判断是否加载图片,这样就不会发生错乱了:
- public View getView(int i, View view, ViewGroup viewGroup) {
- holder = new ViewHolder();
- if (view == null) {
- view = LayoutInflater.from(context).inflate(R.layout.list_item_view, null);
- holder.iv = (ImageView) view.findViewById(R.id.imageView);
- holder.title = (TextView) view.findViewById(R.id.text_title);
- holder.author = (TextView) view.findViewById(R.id.author);
- holder.date = (TextView) view.findViewById(R.id.date);
- view.setTag(holder);
- } else {
- holder = (ViewHolder) view.getTag();
- }
- //为每一个Item里的imageView设置一个特定的tag
- holder.iv.setTag(list.get(i).getImageurl());
- // holder.iv.setImageBitmap(BitmapFactory.decodeResource(context.getResources(),R.drawable.image));
- holder.iv.setImageBitmap(null);
- new ImageLoader(holder.iv, list.get(i).getImageurl(), list.get(i)).imageLoaderAsynctask();
- holder.title.setText(list.get(i).getTitle());
- holder.date.setText(list.get(i).getDate());
- holder.author.setText(list.get(i).getAuthor());
- return view;
- }
上面的代码可以看到,我为每个ImageView都标上了不同的tag:
- class ImageLoaderAsynctask extends AsyncTask {
- @Override
- protected Object doInBackground(Object[] objects) {
- Bitmap bitmap = null;
- try {
- bitmap = ParseData.getImageView(new URL(url));
- if (bitmap != null) {
- item.setImageView(bitmap);
- }
- } catch (IOException e) {
- e.printStackTrace();
- }
- return bitmap;
- }
- @Override
- protected void onPostExecute(Object o) {
- //通过判断之前设定的tag,与当前的url是否相同,来判断是否加载数据。
- if (iv.getTag().equals(url)) {
- iv.setImageBitmap((Bitmap) o);
- }else{
- }
- }
- }
然后在加载的onPostExecute()中判断当前ImageView的tag与这个url是否相同,如果相同就显示,不同就不显示。
看见没有?已经不会再发生错乱了,注意:上面之所以会显示为白色,是因为我在上面的代码中将setImageBitmap(null),如果想为所有的Item预设一个图片,可以将这个null,换成那个图片。
如果用多线程实现异步加载,那就是下面这样。
- private Handler handler=new Handler(){
- @Override
- public void handleMessage(Message msg) {
- //判断tag是否相同
- if(iv.getTag().equals(url)){
- iv.setImageBitmap(bitmap);
- }}
- };
- public void loadImageByThread(){
- new Thread(){
- @Override
- public void run() {
- try {
- bitmap = ParseData.getImageView(new URL(url));
- } catch (IOException e) {
- e.printStackTrace();
- }
- Looper.prepare();
- handler.sendEmptyMessage(0);
- Looper.loop();
- }
- }.start();
- }
2.优化ListView:
上面的Demo中加载网络数据,不知道你注意没有,有些图片已经加载过了,但是当滑动ListView,将已经显示了图片的Item隐藏过后,重新滑动ListView,让之前的Item重新显示,那么这个Item上面的数据会重新加载,不信,你可以看看上面的图片就可以知道了,这样的做法明显不太好,因为这样非常非常的耗流量,使用起来肯定不好,所以下面就对这个问题进行优化
,整个优化的思想就是使用 缓存,将已经加载好的图片缓存起来,当显示一个item之前,先通过与缓存对应的key,来判断缓存中是否有这个key对应的缓存,如果有,就直接使用,不进行网络加载数据,否则就加载数据,并将加载好的数据显示到item上,同时将数据存入缓存中。
上面定义了LruCache,然后传进ImageLoader,开始使用。
简单的来说,LruCache先定义,然后初始化,传进允许使用的最大缓存值,然后重写sizeof方法,然后就可以开始使用这个LruCache了,仔细看看上面的代码就能知道怎么用了下面看看效果吧:
怎么样,之前已经加载过的图片,就不会重新加载了,是不是觉得瞬间流畅了很多?当然,也为用户省了很多流量了!