异步载入之使用多线程

初次尝试

异步、异步,事实上说白了就是多任务处理。也就是多线程执行。多线程那就会有各种问题,我们一步步来看。首先。我们创建一个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。跑起。

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvZWNsaXBzZXh5cw==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="" />

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

这当然不是我们想要看见的结果。可是为什么会这样呢?我们先来分析下。首先,我们须要了解下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 我的视频 慕课网

Android异步载入全解析之使用多线程的更多相关文章

  1. Android异步载入全解析之大图处理

    Android异步载入全解析之大图处理 异步载入中很重要的一部分就是对图像的处理,这也是我们前面用异步载入图像做示例的原因. 一方面是由于图像处理不好的话会很占内存,并且easyOOM,还有一方面,图 ...

  2. Android异步载入全解析之使用AsyncTask

    Android异步载入全解析之使用AsyncTask 概述 既然前面提到了多线程,就不得不提到线程池,通过线程池,不仅能够对并发线程进行管理.更能够提高他们运行的效率.优化整个App.当然我们能够自己 ...

  3. Android异步载入全解析之IntentService

    Android异步载入全解析之IntentService 搞什么IntentService 前面我们说了那么多,异步处理都使用钦定的AsyncTask.再不济也使用的Thread,那么这个Intent ...

  4. Android异步载入全解析之开篇瞎扯淡

    Android异步载入 概述 Android异步载入在Android中使用的很广泛,除了是由于避免在主线程中做网络操作.更是为了避免在显示时由于时间太长而造成ANR,添加显示的流畅性,特别是像List ...

  5. Android异步载入AsyncTask具体解释

    曾看见有人说过.认为非常有道理.分享一下:   技术分为术和道两种:   (1)具体做事的方法是术.   (2)做事的原理和原则是道. 近期项目发现个重大问题.结果打log跟踪查是AsyncTask导 ...

  6. Android 异步消息处理机制解析

    Android 中的异步消息处理主要由四个部分组成,Message.Handler.MessageQueue.Looper.下面将会对这四个部分进行一下简要的介绍. 1. Message: Messa ...

  7. Android系统启动过程全解析

    Android系统是一款基于Linux的移动操作系统,那么Android是如何启动起来的呢?本文就详细阐述Android系统的启动过程. 从内核之上,我们首先应该从文件系统的init开始,因为 ini ...

  8. Android异步载入学习笔记之四:利用缓存优化网络载入图片及ListView载入优化

    假设不做不论什么处理.直接用网络载入图片在网速快的情况下可能没什么不好的感觉.可是假设使用移动流量或是网络不好的时候.问题就来了,要么用户会抱怨流量使用太多.要么抱怨图片载入太慢.如论从哪个角度出发, ...

  9. Eclipse,到了说再见的时候了——Android Studio最全解析

    转自:http://blog.jobbole.com/77635/ 去年的Google大会上,Google带给我们一个小玩具——Android Studio,说它是玩具,是因为它确实比较菜,界面过时, ...

随机推荐

  1. java学习之二叉树的实现

    二叉树是一种数据结构,每个节点都有两个子节点. 二叉树的遍历有三种方式, 先序遍历是 根节点,左子树,右子树: 中序遍历是 左子树,根节点,右子树: 后序遍历是 左子树,右子树,根节点: java实现 ...

  2. Java for循环用法

    Java中for循环多了一种写法--foreach写法(一般仅仅用于遍历整个数组,不用操心越界等问题). 1.1)常规写法: package foreach.main.sn; public class ...

  3. sublime2/3自总结经常使用快捷键(2的居多)

    Ctrl+D 选词 (重复按快捷键,就可以继续向下同一时候选中下一个同样的文本进行同一时候编辑) Ctrl+鼠标左键 能够同一时候选择要编辑的多处文本 Shift+鼠标右键(或使用鼠标中键)能够用鼠标 ...

  4. javascript 学习随笔1

    html部分 <body onload="message()"><!--主题部分加载就调用-->document.getElementById(" ...

  5. Week2(9月19日):增加一个CodeFirst的例子来说明

    Part I:提问  =========================== 1.上堂课中我们使用了()数据库,它是()可部署的,只需要将相应的()文件添加到应用程序的()文件夹,就可以使用了,该数据 ...

  6. c语言‘\0’ ,‘0’, “0” ,0之间的区别

    首先比较一下‘\0’和‘0’的区别.有一个共同点就是它们都是字符,在c语言中,字符是按其所对应的ASCII码来存储的,一个字符占一个字节.请翻开你的ASCII字符集表吧,一般在你的C语言教材的附录上, ...

  7. Spring连接MySQL、Oracle和SQL Server

    其中applicationContext.xml的配置如下: <?xml version="1.0" encoding="UTF-8"?> < ...

  8. 修改、设置root密码

    参考文档: http://zhidao.baidu.com/link?url=OaUTAj6FrMGDjbPZHWv3NNDOaIl3HNqZz_3lF_Zpi8oZpLkBfnHfPlpgE1EvN ...

  9. ArrayList集合--C#

    static void Main(string[] args) { //实例化出一个集合对象 ArrayList list = new ArrayList(); /*添加*/ //--添加单个元素 l ...

  10. BZOJ 4057: [Cerc2012]Kingdoms( 状压dp )

    状压dp.... 我已开始用递归结果就 TLE 了... 不科学啊...我dp基本上都是用递归的..我只好改成递推 , 刷表法 将全部公司用二进制表示 , 压成一个数 . 0 表示破产 , 1 表示没 ...