用异步的方式去加载数据
为什么要使用异步加载?
1.提高用户的体验,用户在加载数据的时候不会感到明显的卡顿
2.Android UI单线程模型,所有的耗时操作会另起线程,否则会阻塞UI线程
异步加载最常用的两种方式:
1.基于多线程,通过多线程或者线程池进行异步加载;
2.通过Android 封装好的AsyncTask,AsyncTask的底层是基于线程池实现的
本文主要介绍异步加载网络图片
1.获取Json数据,将url对应的Json数据转换成NewsBean对象
2.实现网络的异步访问,在onPostExecute中设置数据源
/**
* 获取Json数据,将url对应的Json数据转换成NewsBean对象
* @param url
* @return
*/
private List<NewsBean> getJsonData(String url) {
List<NewsBean> newsBeanList=new ArrayList<NewsBean>();
try {
String jsonString=readStream(new URL(url).openStream());
JSONObject jsonObject;
NewsBean newsBean;
try {
jsonObject=new JSONObject(jsonString);
JSONArray jsonArray=jsonObject.getJSONArray("data");
for(int i=0;i<jsonArray.length();i++){
jsonObject=jsonArray.getJSONObject(i);
newsBean=new NewsBean();
newsBean.newsIconUrl=jsonObject.getString("picSmall");
newsBean.newsTitle=jsonObject.getString("name");
newsBean.newsContent=jsonObject.getString("description");
newsBeanList.add(newsBean);
}
} catch (JSONException e) {
e.printStackTrace();
}
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return newsBeanList;
}
将url转换成bitmap
public Bitmap getBitmapFromUrl(String urlString){
Bitmap bitmap = null;
InputStream is = null;
try {
URL url=new URL(urlString);
HttpURLConnection connection=(HttpURLConnection) url.openConnection();
is=new BufferedInputStream(connection.getInputStream());
bitmap=BitmapFactory.decodeStream(is);
connection.disconnect();
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally{
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return bitmap;
}
在异步加载中通常使用两种方式,避免ListView缓存特性造成ListView item的错乱或者错位:
1通过在BaseAdapter中设置对应的tag将url或者身份验证信息,将item绑定,在真正加载的时候去判断url或者身份验证信息是否正确,只有在正确的时候才去加载相关数据
设置tag:viewHolder.ivIcon.setTag(url);//解决convertView重用,先缓存图片,再加载目标图片,刷新的效果
判断tag://解决convertView重用,先缓存图片,再加载目标图片,刷新的效果
if(mImageView.getTag().equals(mUrl)){
mImageView.setImageBitmap((Bitmap)msg.obj);
}
2.使用成员变量,将对应item数据进行缓存,从而避免由于网络下载时间不确定导致时序上的混乱;
线程加载网络图片:
/**
* 子线程不能更新UI,通过Handler发送并处理消息,更新UI
*/
private Handler handler=new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
//解决convertView重用,先缓存图片,再加载目标图片,刷新的效果
if(mImageView.getTag().equals(mUrl)){
mImageView.setImageBitmap((Bitmap)msg.obj);
}
}
};
/**
* 通过开启线程加载显示网络图片
* @param image
* @param url
*/
public void showImageByThread(ImageView image,final String url){
mImageView=image;//未初始化,空指针异常,传递一个ImageView
mUrl=url;
new Thread(){
@Override
public void run() {
super.run();
Bitmap bitmap=getBitmapFromUrl(url);
Message message=Message.obtain();
message.obj=bitmap;
handler.sendMessage(message);
}
}.start();
}
AsyncTask异步加载显示网络图片:
/**
* 通过异步加载显示网络图片
* @param image
* @param url
*/
public void showImageByAsyncTask(ImageView image,String url){
new NewsImageAsyncTask(image,url).execute(url);
}
private class NewsImageAsyncTask extends AsyncTask<String,Void,Bitmap>{
/**
* 传递一个ImageView,url
*/
public NewsImageAsyncTask(ImageView iamgview,String url){
mImageView=iamgview;
mUrl=url;
}
/**
* doInBackground运行在子线程中,不可以更新UI
* 解析url转换成bitmap
*/
@Override
protected Bitmap doInBackground(String... params) {
return getBitmapFromUrl(params[0]);
}
/**
* onPostExecute运行在主线程中,直接加载显示网络图片
*/
@Override
protected void onPostExecute(Bitmap result) {
super.onPostExecute(result);
//解决convertView重用,先缓存图片,再加载目标图片,刷新的效果
if(mImageView.getTag().equals(mUrl)){
mImageView.setImageBitmap(result);
}
}
}
上面实现了线程和异步加载两种方式,加载网络图片,可是每次都要重新加载,很消耗流量,用户体验不好,于是需要使用缓存,缓存主要使用Lru算法,Android提供了LruCache类实现这个缓存算法。
LruCache将内容保存在内存中,并以一定的方法去管理内容,从而做到在一定的阈值之内保证能够缓存所有的内容数据,超出一定的范围之后,将近期最少使用的内容寄宿出去,从而实现缓存管理。
/**
* 初始化LruCache
*/
public ImageLoader(){
//获取最大的可用内存
int maxMemroy=(int) Runtime.getRuntime().maxMemory();
//设置缓存大小
int cacheSize=maxMemroy / 4;
//初始化LruCache
mCaches=new LruCache<String, Bitmap>(cacheSize){
/**
* 默认返回元素个数,重写获取每个存储对象的大小
*/
@Override
protected int sizeOf(String key, Bitmap value) {
//在每次存入缓存的调用,返回bitmap的大小
return value.getByteCount();
}
};
}
/**
* 将图片存入缓存
* 首先判断内存中是否已经缓存了图片
* @param url
* @param bitmap
*/
public void addBitmapToCache(String url, Bitmap bitmap){
//判断内存中是否已经缓存了图片
if(getBitmapFromCache(url)==null){
mCaches.put(url, bitmap);
}
}
/**
* 从缓存中获取图片
* @param url
* @return
*/
public Bitmap getBitmapFromCache(String url){
return mCaches.get(url);
}
我在写代码的时候重用了Imageview和url以至于一直得不到加载的效果
private class NewsImageAsyncTask extends AsyncTask<String,Void,Bitmap>{
/**
* 传递一个ImageView,url
*/
private ImageView mImageView;
private String mUrl;
public NewsImageAsyncTask(ImageView iamgview,String url){
mImageView=iamgview;
mUrl=url;
}
/**
* doInBackground运行在子线程中,不可以更新UI
* 解析url转换成bitmap,将下载的bitmap存到缓存区域的内存中
*/
@Override
protected Bitmap doInBackground(String... params) {
String url=params[0];
//从网络获取图片
Bitmap bitmap=getBitmapFromUrl(url);
//将不在缓存区域的图片加入缓存
addBitmapToCache(url,bitmap);
return bitmap;
}
/**
* onPostExecute运行在主线程中,直接加载显示网络图片
*/
@Override
protected void onPostExecute(Bitmap result) {
super.onPostExecute(result);
//解决convertView重用,先缓存图片,再加载目标图片,刷新的效果
if(mImageView.getTag().equals(mUrl)){
mImageView.setImageBitmap(result);
}
}
}
我把mImageView和mUrl定义成了全局变量,以至于图片的顺序显示不正常,真是郁闷呀
当ListView的item比较复杂的时候,我们上述的做法会显的比较卡顿,需要进一步优化
优化方案:
A.ListView滑动停止后才加载可预见项
B.ListView滑动时,取消所有加载项
总结:
通过异步加载,避免阻塞UI线程
通过LruCache,将已下载图片放到内存中
通过判断ListView滑动状态,决定何时加载图片
除了ListView,其他控件(GridView)也可以使用异步加载
异步加载
/**
* 加载可见的item
* @param start
* @param end
*/
public void loadImage(int start ,int end){
for(int i=start;i<end;i++){
String url=NewsAdapter.Urls[i];
//从缓存中取出对应的图片
Bitmap bitmap =getBitmapFromCache(url);
//如果缓存中没有图片,就去加载图片
if(bitmap==null){
NewsImageAsyncTask task=new NewsImageAsyncTask(url);
task.execute(url);
mTask.add(task);
}else{
//解决convertView重用,先缓存图片,再加载目标图片,刷新的效果
ImageView mImageView =(ImageView) mListView.findViewWithTag(url);
if(mImageView!=null && bitmap!=null){
mImageView.setImageBitmap(bitmap) ;
}
}
}
}
/**
* 取消task中的所以任务
*/
public void cancelAllTasks(){
if(mTask!=null){
for(NewsImageAsyncTask task:mTask){
task.cancel(false);
}
}
}
}
线程加载:
/**
* 加载可见的item
* @param start
* @param end
*/
public void loadImage(int start ,int end,final ImageCallBack mCallBack){
for(int i=start;i<end;i++){
final String url=NewsAdapter.Urls[i];
//从缓存中获取图片对象
Bitmap bitmap=getBitmapFromCache(url);
/**
* 子线程不能更新UI,通过Handler发送并处理消息,更新UI
*/
final Handler handler=new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
mCallBack.onImageLoader((Bitmap)msg.obj,url);
}
};
if(bitmap==null){
new Thread(){
@Override
public void run() {
super.run();
Bitmap bitmap=getBitmapFromUrl(url);
Message message=Message.obtain();
message.obj=bitmap;
handler.sendMessage(message);
//将图片存储到缓存区域
addBitmapToCache(url,bitmap);
}
}.start();
}else {
mCallBack.onImageLoader(bitmap,url);
}
}
}
Android异步加载网络图片(线程):http://pan.baidu.com/s/1bn2VVOz
Android异步加载网络图片(AsyncTask):http://pan.baidu.com/s/1ntEMQ1b
Android异步加载网络图片,listview滑动优化(线程):http://pan.baidu.com/s/1hZpvC
Android异步加载网络图片,listview滑动优化(AsyncTask):http://pan.baidu.com/s/1pJiUHB1