您的位置:首页 > 理论基础 > 计算机网络

Android实现异步从网络加载图片列表

2012-09-19 15:44 495 查看
有时会有在加载ListView的时候,包含用户头像或其他需要到网络获取的图片信息,这时如果等待全部获取完成再显示会比较慢,很影响用户体验,所以这时就需要利用到异步加载图片的方法。

今天整理的方法,是用Thread来进行加载,没有利用ThreadPool的方法,后面的方法以后再慢慢学一下吧,先把学会的这个记下来。

具体的效果是,加入每个ListView的项只需要显示一个图片,每张图片都是本地没有的,则先把他们都显示成一张默认的图片(我用的是程序图标),然后开启后台线程去网上取图片,最后再一个一个加载出来。

下面是具体的步骤:

步骤一:写一个异步加载类,我的叫AsyncImageLoader

package com.carter.asynchronousimage;

import java.io.InputStream;
import java.lang.ref.SoftReference;
import java.net.URL;
import java.util.HashMap;

import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Handler;
import android.os.Message;
import android.util.Log;

/**
* 异步加载图片类,内部有缓存,可以通过后台线程获取网络图片。首先生成一个实例,并调用loadDrawableByTag方法来获取一个Drawable对象
*/
public class AsyncImageLoader {

/**
* 使用软引用SoftReference,可以由系统在恰当的时候更容易的回收
*/
private HashMap<String, SoftReference<Drawable>> imageCache;

public AsyncImageLoader(){
imageCache = new HashMap<String, SoftReference<Drawable>>();
}

/**
* 通过传入的TagInfo来获取一个网络上的图片
* @param tag TagInfo对象,保存了position、url和一个待获取的Drawable对象
* @param callback ImageCallBack对象,用于在获取到图片后供调用侧进行下一步的处理
* @return drawable 从网络或缓存中得到的Drawable对象,可为null,调用侧需判断
*/
public Drawable loadDrawableByTag(final TagInfo tag, final ImageCallBack callback){
Drawable drawable;

/**
* 先在缓存中找,如果通过URL地址可以找到,则直接返回该对象
*/
if(imageCache.containsKey(tag.getUrl())){
drawable = imageCache.get(tag.getUrl()).get();
if(null!=drawable){
return drawable;
}
}

/**
* 用于在获取到网络图片后,保存图片到缓存,并触发调用侧的处理
*/
final Handler handler = new Handler(){

@Override
public void handleMessage(Message msg) {

TagInfo info = (TagInfo)msg.obj;
imageCache.put(info.url, new SoftReference<Drawable>(info.drawable));
callback.obtainImage(info);

super.handleMessage(msg);
}

};

/**
* 如果在缓存中没有找到,则开启一个线程来进行网络请求
*/
new Thread(new Runnable() {

@Override
public void run() {

TagInfo info = getDrawableIntoTag(tag);
Message msg = new Message();
msg.what = 0;
msg.obj = info;
handler.sendMessage(msg);
}
}).start();

return null;
}

/**
* 通过传入的TagInfo对象,利用其URL属性,到网络请求图片,获取到图片后保存在TagInfo的Drawable属性中,并返回该TagInfo
* @param info TagInfo对象,需要利用里面的url属性
* @return TagInfo 传入的TagInfo对象,增加了Drawable属性后返回
*/
public TagInfo getDrawableIntoTag(TagInfo info){
URL request;
InputStream input;
Drawable drawable = null;

try{
request = new URL(info.getUrl());
input = (InputStream)request.getContent();
drawable = Drawable.createFromStream(input, "src"); // 第二个属性可为空,为DEBUG下使用,网上的说明
}
catch(Exception e){
e.printStackTrace();
}

info.drawable = drawable;
return info;
}

/**
* 获取图片的回调接口,里面的obtainImage方法在获取到图片后进行调用
*/
interface ImageCallBack{
/**
* 获取到图片后在调用侧执行具体的细节
* @param info TagInfo对象,传入的info经过处理,增加Drawable属性,并返回给传入者
*/
public void obtainImage(TagInfo info);
}

}


里面基本每个重要的地方都在我理解的情况下加了注释,应该很多人都能看懂的。

步骤二:写一个Activity用于展示这些内容,我的是AsynImageActivity

package com.carter.asynchronousimage;

import java.util.HashMap;
import java.util.List;
import java.util.ArrayList;

import com.carter.asynchronousimage.AsyncImageLoader.ImageCallBack;

import android.app.Activity;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.ListView;

public class AsynImageActivity extends Activity {

Context context;
ListView list_lv;
List<ImageEntry> mList;
MyAdapter adapter;

// 测试数据,网上找的图片
String[] urls = new String[]{
"http://pic2.sc.chinaz.com/Files/pic/icons128/2328/%E7%BB%8F%E5%85%B8%E7%94%B5%E8%84%91%E6%A1%8C%E9%9D%A2%E5%9B%BE%E6%A0%87%E4%B8%8B%E8%BD%BD9.png",
"http://pic1.sc.chinaz.com/Files/pic/icons128/2328/%E7%BB%8F%E5%85%B8%E7%94%B5%E8%84%91%E6%A1%8C%E9%9D%A2%E5%9B%BE%E6%A0%87%E4%B8%8B%E8%BD%BD0.png",
"http://pic2.sc.chinaz.com/Files/pic/icons128/2328/%E7%BB%8F%E5%85%B8%E7%94%B5%E8%84%91%E6%A1%8C%E9%9D%A2%E5%9B%BE%E6%A0%87%E4%B8%8B%E8%BD%BD7.png",
"http://pic2.sc.chinaz.com/Files/pic/icons128/2328/%E7%BB%8F%E5%85%B8%E7%94%B5%E8%84%91%E6%A1%8C%E9%9D%A2%E5%9B%BE%E6%A0%87%E4%B8%8B%E8%BD%BD6.png",
"http://pic2.sc.chinaz.com/Files/pic/icons128/2328/%E7%BB%8F%E5%85%B8%E7%94%B5%E8%84%91%E6%A1%8C%E9%9D%A2%E5%9B%BE%E6%A0%87%E4%B8%8B%E8%BD%BD5.png",
"http://pic.sc.chinaz.com/Files/pic/icons128/2328/%E7%BB%8F%E5%85%B8%E7%94%B5%E8%84%91%E6%A1%8C%E9%9D%A2%E5%9B%BE%E6%A0%87%E4%B8%8B%E8%BD%BD4.png",
"http://pic.sc.chinaz.com/Files/pic/icons128/2328/%E7%BB%8F%E5%85%B8%E7%94%B5%E8%84%91%E6%A1%8C%E9%9D%A2%E5%9B%BE%E6%A0%87%E4%B8%8B%E8%BD%BD3.png",
"http://pic2.sc.chinaz.com/Files/pic/icons128/2328/%E7%BB%8F%E5%85%B8%E7%94%B5%E8%84%91%E6%A1%8C%E9%9D%A2%E5%9B%BE%E6%A0%87%E4%B8%8B%E8%BD%BD7.png",
"http://pic.sc.chinaz.com/Files/pic/icons128/2328/%E7%BB%8F%E5%85%B8%E7%94%B5%E8%84%91%E6%A1%8C%E9%9D%A2%E5%9B%BE%E6%A0%87%E4%B8%8B%E8%BD%BD14.png",
"http://pic.sc.chinaz.com/Files/pic/icons128/2328/%E7%BB%8F%E5%85%B8%E7%94%B5%E8%84%91%E6%A1%8C%E9%9D%A2%E5%9B%BE%E6%A0%87%E4%B8%8B%E8%BD%BD13.png",
"http://pic.sc.chinaz.com/Files/pic/icons128/2328/%E7%BB%8F%E5%85%B8%E7%94%B5%E8%84%91%E6%A1%8C%E9%9D%A2%E5%9B%BE%E6%A0%87%E4%B8%8B%E8%BD%BD12.png",
"http://pic.sc.chinaz.com/Files/pic/icons128/2328/%E7%BB%8F%E5%85%B8%E7%94%B5%E8%84%91%E6%A1%8C%E9%9D%A2%E5%9B%BE%E6%A0%87%E4%B8%8B%E8%BD%BD11.png",
"http://pic2.sc.chinaz.com/Files/pic/icons128/2328/%E7%BB%8F%E5%85%B8%E7%94%B5%E8%84%91%E6%A1%8C%E9%9D%A2%E5%9B%BE%E6%A0%87%E4%B8%8B%E8%BD%BD5.png",
"http://pic.sc.chinaz.com/Files/pic/icons128/2328/%E7%BB%8F%E5%85%B8%E7%94%B5%E8%84%91%E6%A1%8C%E9%9D%A2%E5%9B%BE%E6%A0%87%E4%B8%8B%E8%BD%BD1.png",
"http://pic1.sc.chinaz.com/Files/pic/icons128/2328/%E7%BB%8F%E5%85%B8%E7%94%B5%E8%84%91%E6%A1%8C%E9%9D%A2%E5%9B%BE%E6%A0%87%E4%B8%8B%E8%BD%BD0.png"

};

/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);

context = this;
list_lv = (ListView) findViewById(R.id.list_lv);

mList = new ArrayList<ImageEntry>();

for(int i=0; i<urls.length; i++){
ImageEntry entry = new ImageEntry();
entry.setUrl(urls[i]);
mList.add(entry);
}

adapter = new MyAdapter(context, mList);
list_lv.setAdapter(adapter);
}

/**
* 一个ImageEntry代表了一个带有图片地址url等其他属性的实例,为提高可扩展性,封装了一个对象。目前只包含url这一个属性。
*/
class ImageEntry{
String url;

public String getUrl(){
return this.url;
}

public void setUrl(String url){
this.url = url;
}
}

/**
* 重写的Adapter
*/
class MyAdapter extends BaseAdapter{

Context context;
List<ImageEntry> mList;

HashMap<String, Drawable> imgCache;     // 图片缓存
HashMap<Integer, TagInfo> tag_map;      // TagInfo缓存
AsyncImageLoader loader;                // 异步加载图片类

/**
* 构造函数
* @param context 上下文
* @param list 包含了所有要显示的图片的ImageEntry对象的列表
*/
public MyAdapter(Context context, List<ImageEntry> list){
this.context = context;
this.mList = list;
imgCache = new HashMap<String, Drawable>();
loader = new AsyncImageLoader();
tag_map = new HashMap<Integer, TagInfo>();
}

@Override
public int getCount() {
// TODO Auto-generated method stub
return mList.size();
}

@Override
public Object getItem(int position) {
// TODO Auto-generated method stub
return mList.get(position);
}

@Override
public long getItemId(int position) {
// TODO Auto-generated method stub
return position;
}

@Override
public View getView(int position, View convertView, ViewGroup parent) {
// TODO Auto-generated method stub
ViewHolder holder = null;

if(null==convertView){
convertView = LayoutInflater.from(context).inflate(R.layout.adapter_item, null, false);
holder = new ViewHolder();
holder.img = (ImageView) convertView.findViewById(R.id.img);
convertView.setTag(holder);
}else{
holder = (ViewHolder) convertView.getTag();
}

String imgurl = mList.get(position).getUrl();   // 得到该项所代表的url地址
Drawable drawable = imgCache.get(imgurl);       // 先去缓存中找

TagInfo tag = new TagInfo();
tag.setPosition(position);  // 保存了当前在adapter中的位置
tag.setUrl(imgurl);         // 保存当前项所要加载的url
holder.img.setTag(tag);     // 为ImageView设置Tag,为以后再获取图片后好能找到它
tag_map.put(position, tag); // 把该TagInfo对应position放入Tag缓存中

if(null!=drawable){                         // 找到了直接设置为图像
holder.img.setImageDrawable(drawable);
}else{                                      // 没找到则开启异步线程
drawable = loader.loadDrawableByTag(tag, new ImageCallBack() {

@Override
public void obtainImage(TagInfo ret_info) {

imgCache.put(ret_info.getUrl(), ret_info.getDrawable());    // 首先把获取的图片放入到缓存中

// 通过返回的TagInfo去Tag缓存中找,然后再通过找到的Tag来获取到所对应的ImageView
ImageView tag_view = (ImageView) list_lv.findViewWithTag(tag_map.get(ret_info.getPosition()));
Log.i("carter", "tag_view: " + tag_view + " position: " + ret_info.getPosition());
if(null!=tag_view)
tag_view.setImageDrawable(ret_info.getDrawable());
}
});

if(null==drawable){ // 如果获取的图片为空,则默认显示一个图片
holder.img.setImageResource(R.drawable.ic_launcher);
}
}

return convertView;
}

class ViewHolder{
ImageView img;
}

}
}


在Activity中需要用到一个类叫做TagInfo,这个是作为一个Tag,保存一些例如位置、地址和图像等的信息

package com.carter.asynchronousimage;

import android.graphics.drawable.Drawable;

public class TagInfo {
String url;
int position;
Drawable drawable;

public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public int getPosition() {
return position;
}
public void setPosition(int position) {
this.position = position;
}
public Drawable getDrawable() {
return drawable;
}
public void setDrawable(Drawable drawable) {
this.drawable = drawable;
}

}


步骤三:只要在Adapter的getView方法中调用loadDrawableByTag方法就可以了。

工程需要两个布局文件,一个是主界面main.xml,一个是每项所显示的布局文件adapter_item.xml

main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >

<ListView
android:id="@+id/list_lv"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
/>

</LinearLayout>


adapter_item.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >

<ImageView
android:id="@+id/img"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>

</LinearLayout>


布局很简单,不难懂。

其他问题:

在adapter中使用了一个HashMap叫做tag_map,它的作用是保存每个position位置的TagInfo。其实这样做在显示很少项的时候还好,但是如果有几千几万个项需要显示,则会造成大量的内存浪费。

不过使用这个方法实在是逼不得已,原来的版本,是在obtainImage方法中利用url的值来找到ImageView的,例如:

holder.img.setTag(imgurl);

public void obtainImage(){
// ...
ImageView tag_view = (ImageView) list_lv.findViewWithTag(imgurl);
// ...
}

这样做有一个问题,例如我有两个项利用的是一个url,则在findViewWithTag(imgurl)时只会找到一个ImageView,则另外一个就没有处理了,所以会造成只有一个显示了正确的图片,另外一个显示默认的。

所以我进行了另外一个尝试,利用ListView的getChildAt方法得到那个位置的View,再通过getTag方法得到ViewHolder,然后再设置图片。但有时通过getChildAt()方法得到的View为null,如果不处理就崩了,就算处理了肯定也得不到预先想要的结果。查了一下网上的解释,getChildAt()方法返回的只是当前显示出来的View,对于已经隐藏掉的也就只能返回null了。所以这个方法也废了。

所以我又进行了另外一个尝试,还是利用Tag,但是利用position这个固定的值。后来还是报了NullPointerException,原因是setTag方法传入的参数是一个Object类型,把position这个int类型传入,会自动生成Integer对象。在findViewWithTag的时候,由于convertView的复用机制,有时会使原来设置该Tag的项,动态的设置成了一个新的Tag,这样就找不到了,所以返回的View也是空的。

没办法了,我只能先这样进行处理,以后再进一步解决这个问题。如果有朋友有方法,请告知,不胜感激。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: