第一句子网 - 唯美句子、句子迷、好句子大全
第一句子网 > Android实现异步从网络加载图片列表

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

时间:2024-03-14 21:48:57

相关推荐

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



博文出处:/carterjin/article/details/7995935

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

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

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

下面是具体的步骤:

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

[java]view plaincopypackagecom.carter.asynchronousimage; importjava.io.InputStream; importjava.lang.ref.SoftReference; .URL; importjava.util.HashMap; importandroid.graphics.drawable.BitmapDrawable; importandroid.graphics.drawable.Drawable; .Uri; importandroid.os.Handler; importandroid.os.Message; importandroid.util.Log; /** *异步加载图片类,内部有缓存,可以通过后台线程获取网络图片。首先生成一个实例,并调用loadDrawableByTag方法来获取一个Drawable对象 */ publicclassAsyncImageLoader{ /** *使用软引用SoftReference,可以由系统在恰当的时候更容易的回收 */ privateHashMap<String,SoftReference<Drawable>>imageCache; publicAsyncImageLoader(){ imageCache=newHashMap<String,SoftReference<Drawable>>(); }/** *通过传入的TagInfo来获取一个网络上的图片 *@paramtagTagInfo对象,保存了position、url和一个待获取的Drawable对象 *@paramcallbackImageCallBack对象,用于在获取到图片后供调用侧进行下一步的处理 *@returndrawable从网络或缓存中得到的Drawable对象,可为null,调用侧需判断 */ publicDrawableloadDrawableByTag(finalTagInfotag,finalImageCallBackcallback){ Drawabledrawable; /** *先在缓存中找,如果通过URL地址可以找到,则直接返回该对象 */ if(imageCache.containsKey(tag.getUrl())){ drawable=imageCache.get(tag.getUrl()).get(); if(null!=drawable){ returndrawable; } } /** *用于在获取到网络图片后,保存图片到缓存,并触发调用侧的处理 */ finalHandlerhandler=newHandler(){ @Override publicvoidhandleMessage(Messagemsg){ TagInfoinfo=(TagInfo)msg.obj; imageCache.put(info.url,newSoftReference<Drawable>(info.drawable)); callback.obtainImage(info); super.handleMessage(msg); } }; /** *如果在缓存中没有找到,则开启一个线程来进行网络请求 */ newThread(newRunnable(){ @Override publicvoidrun(){ TagInfoinfo=getDrawableIntoTag(tag); Messagemsg=newMessage(); msg.what=0; msg.obj=info; handler.sendMessage(msg); } }).start(); returnnull; } /** *通过传入的TagInfo对象,利用其URL属性,到网络请求图片,获取到图片后保存在TagInfo的Drawable属性中,并返回该TagInfo *@paraminfoTagInfo对象,需要利用里面的url属性 *@returnTagInfo传入的TagInfo对象,增加了Drawable属性后返回 */ publicTagInfogetDrawableIntoTag(TagInfoinfo){ URLrequest; InputStreaminput; Drawabledrawable=null; try{ request=newURL(info.getUrl()); input=(InputStream)request.getContent(); drawable=Drawable.createFromStream(input,"src");//第二个属性可为空,为DEBUG下使用,网上的说明 } catch(Exceptione){ e.printStackTrace(); } info.drawable=drawable; returninfo; } /** *获取图片的回调接口,里面的obtainImage方法在获取到图片后进行调用 */ interfaceImageCallBack{ /** *获取到图片后在调用侧执行具体的细节 *@paraminfoTagInfo对象,传入的info经过处理,增加Drawable属性,并返回给传入者 */ publicvoidobtainImage(TagInfoinfo); } }

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

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

[java]view plaincopypackagecom.carter.asynchronousimage; importjava.util.HashMap; importjava.util.List; importjava.util.ArrayList; importcom.carter.asynchronousimage.AsyncImageLoader.ImageCallBack; importandroid.app.Activity; importandroid.content.Context; importandroid.graphics.drawable.Drawable; importandroid.os.Bundle; importandroid.util.Log; importandroid.view.LayoutInflater; importandroid.view.View; importandroid.view.ViewGroup; importandroid.widget.BaseAdapter; importandroid.widget.ImageView; importandroid.widget.ListView; publicclassAsynImageActivityextendsActivity{ Contextcontext; ListViewlist_lv; List<ImageEntry>mList; MyAdapteradapter; //测试数据,网上找的图片 String[]urls=newString[]{ "http://pic2./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./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./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./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./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./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./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./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./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./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./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./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./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./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./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" }; /**Calledwhentheactivityisfirstcreated.*/ @Override publicvoidonCreate(BundlesavedInstanceState){ super.onCreate(savedInstanceState); setContentView(R.layout.main); context=this; list_lv=(ListView)findViewById(R.id.list_lv); mList=newArrayList<ImageEntry>(); for(inti=0;i<urls.length;i++){ ImageEntryentry=newImageEntry(); entry.setUrl(urls[i]); mList.add(entry); } adapter=newMyAdapter(context,mList); list_lv.setAdapter(adapter); } /** *一个ImageEntry代表了一个带有图片地址url等其他属性的实例,为提高可扩展性,封装了一个对象。目前只包含url这一个属性。 */ classImageEntry{ Stringurl; publicStringgetUrl(){ returnthis.url; } publicvoidsetUrl(Stringurl){ this.url=url; } } /** *重写的Adapter */ classMyAdapterextendsBaseAdapter{ Contextcontext; List<ImageEntry>mList; HashMap<String,Drawable>imgCache;//图片缓存 HashMap<Integer,TagInfo>tag_map;//TagInfo缓存 AsyncImageLoaderloader;//异步加载图片类 /** *构造函数 *@paramcontext上下文 *@paramlist包含了所有要显示的图片的ImageEntry对象的列表 */ publicMyAdapter(Contextcontext,List<ImageEntry>list){ this.context=context; this.mList=list; imgCache=newHashMap<String,Drawable>(); loader=newAsyncImageLoader(); tag_map=newHashMap<Integer,TagInfo>(); } @Override publicintgetCount(){ //TODOAuto-generatedmethodstub returnmList.size(); } @Override publicObjectgetItem(intposition){ //TODOAuto-generatedmethodstub returnmList.get(position); } @Override publiclonggetItemId(intposition){ //TODOAuto-generatedmethodstub returnposition; } @Override publicViewgetView(intposition,ViewconvertView,ViewGroupparent){ //TODOAuto-generatedmethodstub ViewHolderholder=null; if(null==convertView){ convertView=LayoutInflater.from(context).inflate(R.layout.adapter_item,null,false); holder=newViewHolder(); holder.img=(ImageView)convertView.findViewById(R.id.img); convertView.setTag(holder); }else{ holder=(ViewHolder)convertView.getTag(); } Stringimgurl=mList.get(position).getUrl();//得到该项所代表的url地址 Drawabledrawable=imgCache.get(imgurl);//先去缓存中找 TagInfotag=newTagInfo(); 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,newImageCallBack(){ @Override publicvoidobtainImage(TagInforet_info){ imgCache.put(ret_info.getUrl(),ret_info.getDrawable());//首先把获取的图片放入到缓存中 //通过返回的TagInfo去Tag缓存中找,然后再通过找到的Tag来获取到所对应的ImageView ImageViewtag_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); } } returnconvertView; } classViewHolder{ ImageViewimg; } } }

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

[java]view plaincopypackagecom.carter.asynchronousimage; importandroid.graphics.drawable.Drawable; publicclassTagInfo{ Stringurl; intposition; Drawabledrawable; publicStringgetUrl(){ returnurl; } publicvoidsetUrl(Stringurl){ this.url=url; } publicintgetPosition(){ returnposition; } publicvoidsetPosition(intposition){ this.position=position; } publicDrawablegetDrawable(){ returndrawable; } publicvoidsetDrawable(Drawabledrawable){ this.drawable=drawable; } }

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

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

main.xml

[html]view plaincopy<?xmlversion="1.0"encoding="utf-8"?> <LinearLayoutxmlns:android="/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

[html]view plaincopy<?xmlversion="1.0"encoding="utf-8"?> <LinearLayoutxmlns:android="/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的,例如:

[java]view plaincopyholder.img.setTag(imgurl); publicvoidobtainImage(){ //... ImageViewtag_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也是空的。

没办法了,我只能先这样进行处理,以后再进一步解决这个问题。如果有朋友有方法,请告知,不胜感激。

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。