zoukankan      html  css  js  c++  java
  • 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
    我的视频 慕课网



  • 相关阅读:
    Account group in ERP and its mapping relationship with CRM partner group
    错误消息Number not in interval XXX when downloading
    错误消息Form of address 0001 not designated for organization
    Algorithm类介绍(core)
    梯度下降与随机梯度下降
    反思
    绘图: matplotlib核心剖析
    ORB
    SIFT
    Harris角点
  • 原文地址:https://www.cnblogs.com/lcchuguo/p/5153868.html
Copyright © 2011-2022 走看看