美文网首页代码优化
ListView优化(1),列表错乱问题

ListView优化(1),列表错乱问题

作者: 反复横跳的龙套 | 来源:发表于2016-09-04 23:57 被阅读943次

    这节我们来讨论一下ListView的优化问题,ListView是我们在开发中非常常用的控件之一,而在开发中也经常会遇到关于ListView的问题,下面我们主要就ListView的两个主要问题进行分析和解决。

    • ListView的列表错乱问题
    • ListView的卡顿问题

    一、ListView的列表错乱问题

    1. 问题描述

    在我们使用ListView异步加载很多图片的时候,会发现有时应该出现图片A的地方出现了图片B,或者某人的头像变成了其他人的头像,这就是ListView的列表错乱问题。

    ListView列表错乱原因有两种情况。

    2. 问题原因

    原因就在于我们在Adapter的getView方法中复用了convertView。原本复用convertView的出发点是好的,是为了避免重复加载列表布局,也就是说本来用于加载显示图片A的列表项布局(这里称为View1)在图片A的位置移除到屏幕外之后,View1会被复用(也就是convertView)并用于加载新的图片B,从而避免了对View1重复加载列表项布局。
    而问题就在于,如果图片B加载失败,没有覆盖复用的View1之前显示的图片A,或者另一种情况,View1在加载图片A完成前,View1已经被复用并完成加载显示图片B,此时图片A才完成加载并显示在View1上。这两种情况都会造成原本应该显示图片B的控件View1显示了图片A。这就是所谓的ListView图片错乱。
    总结两种情况就是:

    1. View1显示图片A,ListView滚动,View1被复用用于显示图片B,而图片B加载不成功,此时View1仍然显示的是图片A。
    2. View1正在加载图片A,ListView滚动,View1被复用用于显示图片B,图片B很快加载显示,然后图片A才加载完并显示在View1中,此时图片A会把图片B覆盖,导致View1显示的是图片A。

    3. 问题代码

    复用convertView的BaseAdapter的getView方法

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ViewHolder vh = null;
        if(convertView == null)
        {
            convertView = LayoutInflater.from(mContext).inflate(R.layout.item_list, parent, false);
            vh = new ViewHolder(
                    ((ImageView)convertView.findViewById(R.id.iv_uil)));
            convertView.setTag(vh);
            
        }else
        {
            vh = (ViewHolder) convertView.getTag();
        }
        
        //在子线程中异步加载图片,其中List包含图片的URL
        loadImageAync(list.get(position),vh.imageView);
        
        return convertView;
    }
    

    异步加载图片并在Handler中显示图片的代码:

    public void loadImageAync(String uri, ImageView imageView)
    {
        //利用线程池加载图片
        THREAD_POOL_EXECUTOR.execute(new Runnable()
            {
                @Override
                public void run()
                {
                    //通过url获取Bitmap
                    Bitmap bitmap = loadBitmap(uri);
                    if(bitmap != null)
                    {
                        //用一个封装类将uri,Bitmap和ImageView传送到Handler处理
                        LoaderResult result = new LoaderResult(iamgeView, uri, bitmap);
                        mMainHandler.obtainMessage(RESULT, reuslt).sendToTarget();
                    }
                }
            });
    }
    
    //用于在主线程中加载图片的Handler
    Handler mMainHandler = new Handler(Looper.getMainLooper())
    {
        @Override
        public void handleMessage(Message msg)
        {
            LoaderResult result = (LoaderResult) msg.obj;
            ImageView imageView = result.getImageView();
            Bitmap bitmap = result.getBitmap();
            //显示图片到imageView中
            imageView.setImageBitmap(bitmap);
        }
    };
    
    

    上面这些就是我们通常用的,在子线程中加载图片,然后通过Handler将图片加载到ImageView当中。也就是这段代码,造成了图片错乱的原因。

    4. 解决方法

    解决切入点

    回顾一下两种造成图片错乱的原因,一个是由于图片B加载不成功导致图片A没有被刷新,另一个是图片A加载时间长把已经加载完成的图片B覆盖了。两种原因不一样,因此要完全解决图片错乱的问题需要分别从两个问题原因入手。

    第一种问题的解决方法

    对于第一种情况,通常有这样的解决思路,那就是不管后面的图片B能不能加载成功,我在开始加载图片前我都对ImageView设置一个正在加载默认图片,这样即使后面的图片B加载失败,那显示的至少也是一个正在加载的图片(还可以在加载失败时显示失败图片),而不是图片A,这样就能避免了图片A显示在应该显示图片B的View上了。

    这个方法能够解决第一种问题,通常这也就够了,因为大部分发生列表错乱的原因就是因为后面的图片加载不出来。

    具体的实现就是,在getView中或者在loadImageAsync中异步加载图片前,给ImageView设置一张正在加载图片,更好的还需要在获取图片失败时将图片设置成一张失败图片,所以有两点需要改进。
    第一种问题的解决方法:

    public void loadImageAync(final String uri, final ImageView imageView)
    {
        //1.在加载图片之前设置一个默认图片
        imageView.setImageDrawable(R.drawable.ic_loading);
    
        THREAD_POOL_EXECUTOR.execute(new Runnable()
            {
                @Override
                public void run()
                {
                    Bitmap bitmap = loadBitmap(uri);
                    //注意判断条件改变了
                    if(bitmap == null)
                    {
                        //2. 加载图片失败时设置一个失败图片
                        bitmap = getDefaultErrorBitmap();
                    }
    
                    LoaderResult result = new LoaderResult(iamgeView, uri, bitmap);
                    mMainHandler.obtainMessage(RESULT, reuslt).sendToTarget();
                }
            });
    }
    
    第二种问题的解决方法

    第二中问题就比较特殊了,根本原因就是,图片A不知道自己在绑定的ImageView中已经过期了,用来显示自己的ImageView已经被复用了,但是图片A还是不要脸的在加载完成之后不管不顾的显示到ImageView中,如果图片B加载的比A久,那么不会出现问题(会出现图片A闪一下然后立刻换成B),如果B加载得快,那么最后图片A就会把图片B给覆盖。

    既然问题找出来了,那么解决方法就好想了,只要让ImageView知道图片A已经过期,不显示图片A就行了。

    实现的方法就是,在加载图片A前,给ImageView设置一个标签,用于表示当前应该显示的图片A的uri。然后在图片A加载完成之后显示图片A前,再次验证ImageView的标签是否还和所加载的图片A的uri一致,这里标签会发生变化的原因就在于,ImageView被复用用于加载另一张图片B时,标签就会标称图片B的uri。如果最后uri一致,说明ImageView还没有被复用,然后便可以心安理得的显示图片A;如果不一致,说明ImageView已经被复用啦,已经不属于图片A的啦,此时就不要再显示A啦。

    在代码中的实现主要分为两步,一是在加载图片之前为ImageView添加Tag,二是在Handler的handleMessage中显示图片前验证uri是否一致,一致的话才显示图片。以下是图片错乱的解决方法,包括了第一种问题的解决办法。

    public void loadImageAync(final String uri, final ImageView imageView)
    {
        imageView.setImageDrawable(R.drawable.ic_loading);
    
        //在加载之前为ImageView设置Tag为uri
        imageView.setTag(uri);
    
        THREAD_POOL_EXECUTOR.execute(new Runnable()
            {
                @Override
                public void run()
                {
                    Bitmap bitmap = loadBitmap(uri);
                    //注意判断条件改变了
                    if(bitmap == null)
                    {
                        bitmap = getDefaultErrorBitmap();
                    }
    
                    LoaderResult result = new LoaderResult(iamgeView, uri, bitmap);
                    mMainHandler.obtainMessage(RESULT, reuslt).sendToTarget();
                }
            });
    }
    
    Handler mMainHandler = new Handler(Looper.getMainLooper())
    {
        @Override
        public void handleMessage(Message msg)
        {
            LoaderResult result = (LoaderResult) msg.obj;
            ImageView imageView = result.getImageView();
            Bitmap bitmap = result.getBitmap();
            String uri = result.getUri();
            //如果uri一致才显示图片到imageView中
            if(uri.equals((String)imageView.getTag()))
            {
                imageView.setImageBitmap(bitmap);
            }else
            {
                Log.w(TAG,"set image bitmap, but uri has changed, ignored!");   
            }
        }
    };
    

    以上就是ListView列表错乱的产生原因以及解决方法啦~

    相关文章

      网友评论

      • 我要蓝天:源码 膜拜一波?地址可有
        感谢
        反复横跳的龙套:@我要蓝天 抱歉没有源码哦,当时只是将问题提取出来写了个小Demo测试一下,所有没有上传源码呢:pray:
      • 7493aed0dffc:使用glide会出现你说的第二种列表错乱的现象吗,那样该如何解决?你写的可真详细。
        反复横跳的龙套:不好意思好久没上了,你的问题没有研究过呢,希望有时间看一下吧~感谢阅读~

      本文标题:ListView优化(1),列表错乱问题

      本文链接:https://www.haomeiwen.com/subject/wuqrettx.html