我们知道Android为了不阻塞UI线程(main线程),不允许在非UI线程中进行UI操作以及网络请求等操作,为了不阻塞UI,我们往往就要进行异步加载.
我们以异步加载图片为例子,来学习一下异步加载
方法一:Thread+Handler+Message
1.我们新建线程,在线程中获取图片(Bitmap对象
)
new Thread() {
@Override
public void run() {
super.run();
try {
//Android中非主线程无法在线程中更新ui,可通过Handler把数据传递到主线程
Bitmap bitmap = getBitmapFromUrl(url);
//新建Message对象作为载体
Message message = Message.obtain();
//将Bitmap对象与消息绑定
message.obj = bitmap;
//通过Handler发送消息
mHandler.sendMessage(message);
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}.start();
2.新建Handler
用来传递消息,重写handleMessage
方法,从Message中取出图片,并设置给ImageView
.
private Handler mHandler = new Handler() {
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
if (mImageView.getTag().equals(mUrl)) //只有ImageView的tag为当前url时,才进行设置
mImageView.setImageBitmap((Bitmap) msg.obj);
}
};
方法二:AsyncTask
1.新建一个loadIMageAsynTask
类并继承AsynTask
,重写doInBackgroud和onPostExecute
方法
public class loadImageAsynTask extends AsyncTask<String, Void, Bitmap> {//Params,Progress,Result
private ImageView mImageView;
private String mUrl;
public loadImageAsynTask(ImageView imageView, String url) {
mImageView = imageView;
mUrl = url;
}
//在doInBackground方法中获取Bitmap对象,并返回
@Override
protected Bitmap doInBackground(String... urls) {
Bitmap bitmap = null;
try {
//从网络上获取图片
bitmap = getBitmapFromUrl(urls[0]);
if (bitmap != null) {
//将下载好的图片保存到LruCache中s
mLruCache.put(urls[0], bitmap);
}
} catch (IOException e) {
e.printStackTrace();
}
return bitmap;
}
//在onPostExecute方法中将Bitmap对象设置给ImageView
@Override
protected void onPostExecute(Bitmap bitmap) {
super.onPostExecute(bitmap);
if (mImageView.getTag().equals(mUrl))
mImageView.setImageBitmap(bitmap);
}
}
与Thread+Handler对比,AsyncTask有两个好处:
- 方便实现异步通信,不需使用 “任务线程(如继承Thread类) + Handler”的复杂组合
- 节省资源,采用线程池的缓存线程 + 复用线程,避免了频繁创建 & 销毁线程所带来的系统资源开销
进阶:异步加载图片的优化
问题1:
由于我们的模拟器网速很快,当我们加载上下滑动图片时不会发生什么问题,但是当我们网速很慢时(我们可以在获取图片时Sleep
一秒来模拟网速慢的情况),我们就会发现当item 1
,item 2
,item 3
都加载后我们加载item 4
时,并不会直接在item 4
上设置image 4
,而是先设置的image 1
,image 2
, image 3
,再设置的image 4
.
解决办法:为ImageView
设置Tag
1.为View对象设置属性时,以图片的url
为key
,设置为图片Tag
.
viewHolder.ivIcon.setTag(mList.get(i).getNewsIconUrl());
2.在获得图片(Bitmap
)后,设置给ImageView
时,先进行判断
if (mImageView.getTag().equals(mUrl))
mImageView.setImageBitmap(bitmap);
问题2:
我们每次滑动时,都是从网络重新获取图片,这对于用户来说,很耗流量.
解决办法:使用LruCache
1.新建LruCache
,并重写sizeof
方法,返回每次缓存的图片大小
mLruCache = new LruCache<String, Bitmap>(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap value) {
//在每次存入缓存中调用,告诉我们的系统存入的对象有多大
return value.getByteCount();
}
};
2.每次从网络上获取图片后,加入到缓存
//从网络上获取图片
bitmap = getBitmapFromUrl(urls[0]);
if (bitmap != null) {
//将下载好的图片保存到LruCache中s
mLruCache.put(urls[0], bitmap);
}
3.如果缓存中有就从缓存中取,缓存中没有再发送网络请求获取图片
Bitmap bitmap = null;
//从缓存中取出对应的图片,如果缓存中没有,我们就从网络中去下载
bitmap = mLruCache.get(urlString);
if (bitmap == null) {
new loadImageAsynTask(imageView, urlString).execute(urlString);
} else {
imageView.setImageBitmap(bitmap);
}
Android异步加载图片源码:https://github.com/wantao666/AndroidDemo/tree/master/SynTask