天天看点

json解析,异步下载(listview仅滑动时加载)Demo总结

异步加载的练习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数据如下

json解析,异步下载(listview仅滑动时加载)Demo总结

<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&lt;string, void, list&lt;newsbean&gt;&gt; {  

        // 通过获取newsbean集合得到数据传递到adapter中,这样可以显示出每个json数据  

        @override  

        protected list&lt;newsbean&gt; doinbackground(string... params) {  

            return getjsondata(params[0]);// 这里的参数只有一个url网址  

        // 将生成的nesbean设置给listview  

        protected void onpostexecute(list&lt;newsbean&gt; newsbeans) {  

            super.onpostexecute(newsbeans);  

            newsadapter adapter = new newsadapter(mainactivity.this, newsbeans,  

                    mlistview);  

            mlistview.setadapter(adapter);  

4 baseadapter的“优雅”使用

主要包含以下几点

1、自定义adpter继承baseadpter;

2、定义变量:list&lt;newsbean&gt;;layoutinflater;

3、重写构造函数newsadpter(context context, list&lt;newsbean&gt; 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 &amp;&amp; visibleitemcount &gt; 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&lt;imageloadr.imageloaderasyntask&gt;();  

        // 获取最大可使用内存  

        int maxmemory = (int) runtime.getruntime().maxmemory();  

        // 设置所需缓存大小  

        int cachesize = maxmemory / 4;  

        mcaches = new lrucache&lt;string, bitmap&gt;(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 &lt; 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&lt;string, void, bitmap&gt; {  

    // 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 &amp;&amp; bitmap != null) {  

        // 设置完bitmap之后表明该task已经失去作用,需要从集合中移除  

        mtask.remove(this);  

}  

转载:http://blog.csdn.net/xsf50717/article/details/49024075