• Android异步载入全解析之使用多线程


    异步载入之使用多线程

    初次尝试

    异步、异步,事实上说白了就是多任务处理。也就是多线程执行。多线程那就会有各种问题,我们一步步来看。首先。我们创建一个class——ImageLoaderWithoutCaches,从命名上。大家也看出来,这个类,我们实现的是不带缓存的图像载入,不多说,我们再创建一个方法——showImageByThread,通过多线程来载入图像:

    /**
     * Using Thread
     * @param imageView
     * @param url
     */
    public void showImageByThread(final ImageView imageView, final String url) {
    
        mHandler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                Bitmap bitmap = (Bitmap) msg.obj;
                imageView.setImageBitmap(bitmap);
            }
        };
        new Thread() {
            @Override
            public void run() {
                Bitmap bitmap = getBitmapFromUrl(url);
                Message message = Message.obtain();
                message.obj = bitmap;
                mHandler.sendMessage(message);
            }
        }.start();
    }

    在这种方法中,我们开启了一个线程,并在新线程中使用我们前面说的下载图像的方法进行下载,由于这时候已经不是主线程了,所以我们想怎么下就能够怎么下。天高皇帝远。
    下载好了之后,通过Handler对象将消息告知在主线程中等了一辈子的Handler,收到消息之后,Handler操纵UI,改动imageView的图像。OK。跑起。


    卧草,坑我啊,有的图老闪,有的图动都不动,滑一滑还不停的变,这是什么鬼。

    这当然不是我们想要看见的结果。可是为什么会这样呢?我们先来分析下。首先,我们须要了解下ListView的Recycler机制。


    ListView之Recycler机制

    public View getView(int position, View convertView, ViewGroup parent)

    这个就是ListView中的getView()方法,參数convertView就是我们的头号嫌疑犯。假如我们有100条数据,可是页面上仅仅能显示10条。Android还不会白痴到所有先创建出来。在初始创建的时候,显示多少,convertView就创建了多少,当你滑动的时候。比方向上滑动一个Item。那么Item1就会隐藏。Item11会从以下出来,那么这时候,假设Android再去创建一个convertView,那它就要被人骂死了,由于它首先要回收Item1,再去创建Item11。有意思吗?
    为了解决这样一个问题,Android在ListView中提供了Recycler机制,说白了就是创建了一个Item的缓冲池,当Item1消失后。它并没有被回收。而是进入了缓冲池。成了二手货,当Item11显示的时候。它会从缓冲池中取出Item1。把Item1的数据擦擦干净继续用。所以这个时候,Item11和Item1显示的内容会是同样的。由于它们在内存中的地址是一样的,本质上是一个Item。但这个时候,Item1已经看不见了。所以它显示成什么,跟Item11没一点关系。

    当它再显示的时候,又一次再写上正确的数据就好了。

    这里在网上盗了张图,帮助大家来理解这样一个捡二手货的概念:

    当然,假设不是异步操作,屁事都没有,由于Android UI是单线程的,或者说你重用了convertView但每次都new一个,那也没事。当然你也不会这样做。太Low了。

    可是异步就不一样了,就拿我们这里下载图片来说,每一个图片都有自己的脾气。大家下载的速度都是不一样的,当初始显示ListView时。Item1的图片下载太慢,当你滑上去,显示Item11了,Item11的图显示的飞起,立即就下好了。OK,Item11的图显示好了,可立即。Item1下的慢的图也下好了,它去找Item1,可是它不知道这个时候的Item1已经变成Item11了。它还去给人家设置图像。这不就乱了吗?

    OK,知道了原因,我们怎么解决呢?如今的问题就是,下载的图像就好像一个冬眠的人,它睡了20年。起来发现,他大爷已经不是当年的那个大爷了,但它的记忆还停在20年前。

    所以,我们须要建立一个标志,来让冬眠的朋友回来看看清楚,如今的这个大爷是不是你曾经的那个大爷。

    在Android中。我们能够很方便的使用tag来对View进行标识。Item1显示的时候,tag被设置为url1。然后它就去下载url1了,当滑动后,显示Item11了,这时候tag被改动为url11,下载url11的图了。而当url1的图片下载好了之后,回来仅仅要看看tag是不是url1就知道大爷还是不是那个大爷了,假设不是。就不显示了,这样就能够避免错位的问题了。
    当然。前面的样例中另一个坑,我就不埋大家了,完成下载后。通过handler将图片通知UI线程改动ImageView,可是这个时候。參数中的ImageView与这时候传递过来的图像可能并非一一相应的关系,显示肯定不对了,所以我们还须要让url和相应的ImageView进行下配对,最简单的方法就是使用一个对象来进行保存。

    多线程异步下载正解

    在进行初次尝试并分析了失败原因后。我们将上面两个坑填起来,改动后的方法例如以下:
    /**
     * Using Thread
     * @param imageView
     * @param url
     */
    public void showImageByThread(final ImageView imageView, final String url) {
    
        mHandler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                ImgHolder holder = (ImgHolder) msg.obj;
                if (holder.imageView.getTag().equals(holder.url)) {
                    holder.imageView.setImageBitmap(holder.bitmap);
                }
            }
        };
        new Thread() {
            @Override
            public void run() {
                Bitmap bitmap = getBitmapFromUrl(url);
                Message message = Message.obtain();
                message.obj = new ImgHolder(imageView, bitmap, url);
                mHandler.sendMessage(message);
            }
        }.start();
    }

    private class ImgHolder {
        public Bitmap bitmap;
        public ImageView imageView;
        public String url;
    
        public ImgHolder(ImageView iv, Bitmap bm,String url) {
            this.imageView = iv;
            this.bitmap = bm;
            this.url = url;
        }
    }

    改动后的方法,依旧是使用Handler,这个没有别的选择,首先我们下载图像,然后将图像和ImageView通过一个对象来进行匹配保存。在Handler拿到对象后,通过推断当前的tag与url是否相应来决定是否载入。

    这里就利用到了我们在Android异步载入全解析之开篇瞎扯淡里面所提到的:

    viewHolder.imageView.setTag(url);

    这里就派上用场了。


    最后。在Adapter中改动下代码:
    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        String url = mData.get(position);
        ViewHolder viewHolder = null;
        if (convertView == null) {
            viewHolder = new ViewHolder();
            convertView = mInflater.inflate(R.layout.listview_item, null);
            viewHolder.imageView = (ImageView) convertView.findViewById(R.id.iv_lv_item);
            convertView.setTag(viewHolder);
        } else {
            viewHolder = (ViewHolder) convertView.getTag();
        }
        viewHolder.imageView.setTag(url);
        viewHolder.imageView.setImageResource(R.drawable.ic_launcher);
        mImageLoader.showImageByThread(viewHolder.imageView, url);
        return convertView;
    }

    在getView的时候,异步载入图片。


    这时候,我们再来执行程序,效果例如以下:


    OK,已经能够正常显示了。



    我的Github
    我的视频 慕课网



  • 相关阅读:
    javascript提升复习
    关于加解密的
    java动态代理汇总
    ActiveMQ 使用
    16年上半年小结,下半年计划
    多线程之ReentrantReadWriteLock
    xml转换之
    2015-03 月份学习总结 分类: 学习总结 2015-04-01 20:25 87人阅读 评论(0) 收藏
    2015-03 月份学习总结 分类: 学习总结 2015-04-01 20:25 88人阅读 评论(0) 收藏
    IBM Rational AppScan 无法记录登录序列 分类: 数据安全 2015-03-18 16:46 157人阅读 评论(0) 收藏
  • 原文地址:https://www.cnblogs.com/lcchuguo/p/5153868.html
Copyright © 2020-2023  润新知