异步加载的练习demo
主要涉及知识点:
1.解析json格式数据,主要包括图片,文本
2.使用asyntask异步方式从网络下载图片
3.baseadapter的“优雅”使用
4.使用lru缓存算法
5.改进加载:仅在listview滑动停止后才加载可见项,滑动中不加载
涉及到的知识点如上,这里做一个小结,仅对一些代码片段分析
1、异步加载
主要有俩个原因
【2】耗时操作阻塞ui线程(网络下载等 )
常用的俩种方式
【1】多线程/线程池
【2】asyntask (其实它底层也是线程池 核心线程数5,最大线程数128)
2、json数组解析
采用的是某网站的提供的接口(http://www.imooc.com/api/teacher?type=4&num=30)返回的json数据如下
<span style="font-family:microsoft yahei;">/*
* json数据bean,这里获取三个属性
* 图片
* 标题
* 内容
*/
public class newsbean {
public string newsiconurl;// 图片的网址
public string newstitle;
public string newscontent;
}</span>
下面就开始解析json,并将解析的数据放到list<newsbean>,代码片段在mainactivity.java中
* 将url对应的json格式数据转化为所封装的newsbean
*/
// 获取json返回格式数据
private list<newsbean> getjsondata(string url) {
list<newsbean> newsbeanlist = new arraylist<newsbean>();
try {
string jsonstring = readstream(new url(url).openstream());// 直接根据url获取网络数据返回inputstream类型
jsonobject jsonobject;
newsbean newsbean;
// log.d("xsf", jsonstring); //打印测试
// 将json数据放入jsonobject中,然后通过jsonarray获取所需要的数据集合
try {
jsonobject = new jsonobject(jsonstring);
jsonarray jsonarray = jsonobject.getjsonarray("data");
// 通过for循环取出jsonarray每个值,放到newsbean的集合中去
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");// 获取title
newsbean.newscontent = jsonobject.getstring("description");// 获取内容
newsbeanlist.add(newsbean);
}
} catch (jsonexception e) {
e.printstacktrace();
}
} catch (ioexception e) {
e.printstacktrace();
}
return newsbeanlist;
}
</span>
整个逻辑在 代码注释中很清楚,通过url下载数据位string,先获取最外一级jsonobj,然后获取内部jsonarray数组,在for循环中子jsonobj,通过getstring获取每个子jsonobj中的标签对应的内容。(结合前面的json图来看)最后统一放到newslist中,作为后面listview适配器apapter的数据源
这里还涉及到java io流的操string jsonstring = readstream(new url(url).openstream());,主要是吧new url(url).openstream()得到的字节流蹭蹭封装成buffer(核心依然是装饰者模式),然后拼接成string形式返回,代码片段在mainactivity.java中
<span style="font-family:microsoft yahei;">// 字节流转字符流,读取函数,解析网页返回的数组
private string readstream(inputstream is) {
inputstreamreader isr;
string result = "";
string line = "";
isr = new inputstreamreader(is, "utf-8");// 字节流封装成字符流并且指定为utf-8格式
bufferedreader br = new bufferedreader(isr);// 将字符流通过buffer形式读取出来
while ((line = br.readline()) != null) {
result += line;
} catch (unsupportedencodingexception e) {
return result;
}</span>
3、asyntask异步加载
先来看看asynctask的定义:
public abstract class asynctask<params, progress, result> { }三种泛型类型分别代表“启动任务执行的输入参数”、“后台任务执行的进度”、“后台计算结果的类型”。
在特定场合下,并不是所有类型都被使用,如果没有被使用,可以用java.lang.void类型代替。一个异步任务的执行一般包括以下几个步骤:
【1】.execute(params... params),执行一个异步任务,需要我们在代码中调用此方法,触发异步任务的执行。(在ui线程中执行,不可混淆)
以下几个是asyntask继承时可以重写的函数
【2】.onpreexecute(),在execute(params... params)被调用后立即执行,一般用来在执行后台任务前对ui做一些标记。
【3】.doinbackground(params... params),在onpreexecute()完成后立即执行,用于执行较为费时的操作,此方法将接收输入参数和返回计算结果。在执行过程中可以调用publishprogress(progress... values)来更新进度信息。
【4】.onprogressupdate(progress... values),在调用publishprogress(progress... values)时,此方法被执行,直接将进度信息更新到ui组件上。
【5】.onpostexecute(result result),当后台操作结束时,此方法将会被调用,计算结果将做为参数传递到此方法中,直接将结果显示到ui组件上。
在使用的时候,有几点需要格外注意:
【1】.异步任务的实例必须在ui线程中创建。
【2】.execute(params... params)方法必须在ui线程中调用。
【3】.不要手动调用onpreexecute(),doinbackground(params... params),onprogressupdate(progress... values),onpostexecute(result result)这几个方法。
【4】.不能在doinbackground(params... params)中更改ui组件的信息。
【5】.一个任务实例只能执行一次,如果执行第二次将会抛出异常。
在mainactivity.java中,通过该方式进行异步加载json数据
详细见代码注释,逻辑比较简单
在主线程中通过execute启动asyntask
<code><code></code></code>
@override
protected void oncreate(bundle savedinstancestate) {
super.oncreate(savedinstancestate);
setcontentview(r.layout.activity_main);
// 获取listview控件
mlistview = (listview) findviewbyid(r.id.tv_main);
// 在主线程中启动asyntask
new newsasyntask().execute(url);
// 实现网络的异步访问
/*
* asyntask 输入三个参数 params,这里需要传入url网址,因此为string类型 progress 这里不需要返回进度显示
* 因此为void result 为 json解析之后的数据bean的集合
class newsasyntask extends asynctask<string, void, list<newsbean>> {
// 通过获取newsbean集合得到数据传递到adapter中,这样可以显示出每个json数据
@override
protected list<newsbean> doinbackground(string... params) {
return getjsondata(params[0]);// 这里的参数只有一个url网址
// 将生成的nesbean设置给listview
protected void onpostexecute(list<newsbean> newsbeans) {
super.onpostexecute(newsbeans);
newsadapter adapter = new newsadapter(mainactivity.this, newsbeans,
mlistview);
mlistview.setadapter(adapter);
4 baseadapter的“优雅”使用
主要包含以下几点
1、自定义adpter继承baseadpter;
2、定义变量:list<newsbean>;layoutinflater;
3、重写构造函数newsadpter(context context, list<newsbean> data)。
4、文艺方式重写getview()方法。
5、自定义类viewholder,映射相关的view对象
(适配器是架起数据到界面显示的一座桥梁,普通listview的核心就是在适配器上下功夫)
在getview中通过viewholder保存数据,结合settag来给viewholder打标签,来解决listview滑动图片加载错位的问题,代码片段在newsadapter.java中
public view getview(int position, view convertview, viewgroup parent) {
// viewholder方式
viewholder viewholder = null;
if (convertview == null) {
viewholder = new viewholder();
convertview = minflater.inflate(r.layout.item_layout, null);
// 对viewholder元素进行初始化
viewholder.ivicon = (imageview) convertview
.findviewbyid(r.id.tv_icon);
viewholder.tvtitle = (textview) convertview
.findviewbyid(r.id.tv_title);
viewholder.tvcontent = (textview) convertview
.findviewbyid(r.id.tv_content);
convertview.settag(viewholder);
} else {
viewholder = (viewholder) convertview.gettag();
viewholder.ivicon.setimageresource(r.drawable.ic_launcher);
/*
* 防止listview加载图片出现错位,非常重要 以下俩行代码很重要,因此在imageloader获取图片需要进行判断
*/
string url = mlist.get(position).newsiconurl;
viewholder.ivicon.settag(url);// 将图片和对应的url进行绑定
// 使用多线程方式加载实际图片
* new imageloadr().showimagebythread(viewholder.ivicon, url);
// 使用asynctask加载实际图片
// new imageloadr().showimagebyasynctask(viewholder.ivicon, url);
mimageloader.showimagebyasynctask(viewholder.ivicon, url);
viewholder.tvtitle.settext(mlist.get(position).newstitle);
viewholder.tvcontent.settext(mlist.get(position).newscontent);
return convertview;
class viewholder {
public textview tvtitle, tvcontent;
public imageview ivicon;
这里通过
string url = mlist.get(position).newsiconurl;
con.settag(url);// 将图片和对应的url进行绑定
然后调用asyntask异步方式,开始加载图片,关于加载图片这里采用了lru算法,下面会分析。
5、优化的listview图片加载
通常在listview网络加载图片时,我们通常会做这样的处理: 仅在listview滑动停止后加载图片或者文字,这样可以减少卡顿
实现逻辑:在listview的adapetr中实现onscrolllistener接口,需要重写俩个函数
public void onscrollstatechanged(abslistview view, int scrollstate) 滚动状态改变触发,在这里可以判断滚动状态从而确定是否需要加载
public void onscroll(abslistview view, int firstvisibleitem, int visibleitemcount, int totalitemcount) 一直都会触发,可以记录此时滚动的在屏幕中起始位置,便于加载处理,代码片段在newsadapter.java中
public void onscrollstatechanged(abslistview view, int scrollstate) {
// listview滑动状态切换的时候才调用
// 判断listview滚动状态
if (scrollstate == scroll_state_idle) {
// 滚动停止状态,加载可见项
mimageloader.loadimages(mstart, mend);
// 停止任务
mimageloader.cancelalltask();
@override
public void onscroll(abslistview view, int firstvisibleitem,
int visibleitemcount, int totalitemcount) {
// 整个滑动过程都会调用,不断获取当前可见项目和最后一个可见项目
mstart = firstvisibleitem;
mend = firstvisibleitem + visibleitemcount;
if (mfirrstin && visibleitemcount > 0) {
// 手动加载第一屏
mfirrstin = false;
6、 图片下载 核心代码 imageloader.java
有以下几个看点
对于从网络上获取图片这种需求,我们都要使用cache来将我们的图片缓存起来,尤其是对于listview这种,不能每次我们滑动listview就重新从网上下载图片,这样会很浪费资源而且浪费手机的流量。在android中,已经为我们提供了一个用于缓存的类lrucache。我们可以使用这个类来实现我们对于图片资源的缓存。(lrucache是将图片缓存在内存中,而还有个第三方的类disklrucache来将图片缓存到手机的disk上,而我们大型的app,一般都是将lrucache和disklrucache结合起来使用,形成一个memory hierarchy。)
【1】需要预设缓存占sd卡的大小 代码片段在imageloader.java中
【2】添加到缓存 (可以通过url和bitmap的键值对方式关联)
【3】从缓存获取图片
lru本质就是linkhashmap,所以具备put get操作,lru这里就不扩展开了,代码片段如下
public imageloadr(listview listview) {
mlistview = listview;
mtask = new hashset<imageloadr.imageloaderasyntask>();
// 获取最大可使用内存
int maxmemory = (int) runtime.getruntime().maxmemory();
// 设置所需缓存大小
int cachesize = maxmemory / 4;
mcaches = new lrucache<string, bitmap>(cachesize) {
@override
protected int sizeof(string key, bitmap value) {
// 在每次存入缓存时候调用,告诉系统传入对象的大小
return value.getbytecount();
};
// 增加到缓存
public void addbitmaptocache(string url, bitmap bitmap) {
if (getbitmapfromcache(url) == null) {
// 判断当前是否存在url所指定的图片
mcaches.put(url, bitmap);
// 从缓存中获取数据
public bitmap getbitmapfromcache(string url) {
return mcaches.get(url);
6.2、加载图片
使用了loadimages(start,end)该函数主要用来加载当前显示listview从start到end的图片用来配合listview仅在活动停止后加载,假设此时滑动停止屏幕listview在12-20行之间则只加载该区间的图片文本
原理:
【1】将start,end作为for循环,由于在newsadapter.java中已经记录了所有的urls,因而string url = newsadapter.urls[i] 并且i在[start,end]之间,这样就将url和start,end对应起来
【2】这样同样使用asyntask来加载图片,这里使用一个asyntask集合来管理,当开始下载时加入集合,下载完成回调时在onpostexecute中将该asyntask从中remove掉
// 用来加载从start到end的所有图片
public void loadimages(int start, int end) {
for (int i = start; i < end; i++) {
string url = newsadapter.urls[i];// 获取到了从start开始到end所有rul
bitmap bitmap = getbitmapfromcache(url);
if (bitmap == null) {
// 缓存中没有该图片则直接加载
// new imageloaderasyntask(, url).execute(url);
imageloaderasyntask task = new imageloaderasyntask(url);
task.execute(url);
mtask.add(task);// 将该task保存到当前活动task集合中
/*
* // 缓存中有该图片直接加载 // imageview.setimagebitmap(bitmap);
*/
// 通过tag找到imageview
imageview imageview = (imageview) mlistview
.findviewwithtag(url);
imageview.setimagebitmap(bitmap);
}
private class imageloaderasyntask extends asynctask<string, void, bitmap> {
// private imageview
// mimageview;不再需要,可以通过listview的findviewwithtag(url)找到imageview
// 防止listview图片加载错位做的处理
private string murl;
* public imageloaderasyntask(imageview imageview, string url) {
* mimageview = imageview; murl = url; }
public imageloaderasyntask(string url) {
murl = url;
protected bitmap doinbackground(string... params) {
string url = params[0];
// 从网络中获取图片
bitmap bitmap = getbitmapfromurl(url);
if (bitmap != null) {
addbitmaptocache(url, bitmap);
return bitmap;
protected void onpostexecute(bitmap bitmap) {
super.onpostexecute(bitmap);
// 设置图片时增加判断防止listview加载图片错位
* if (mimageview.gettag().equals(murl)) {
* mimageview.setimagebitmap(bitmap); }
imageview imageview = (imageview) mlistview.findviewwithtag(murl);
if (imageview != null && bitmap != null) {
// 设置完bitmap之后表明该task已经失去作用,需要从集合中移除
mtask.remove(this);
}
转载:http://blog.csdn.net/xsf50717/article/details/49024075