且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

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

更新时间:2022-06-15 11:12:07

异步加载的练习demo
主要涉及知识点:
1.解析json格式数据,主要包括图片,文本
2.使用AsynTask异步方式从网络下载图片
3.BaseAdapter的“优雅”使用
4.使用Lru缓存算法

5.改进加载:仅在listview滑动停止后才加载可见项,滑动中不加载

具体代码可以参看http://download.csdn.net/detail/xsf50717/9169621

涉及到的知识点如上,这里做一个小结,仅对一些代码片段分析


1、异步加载

主要有俩个原因

【1】Android单线程模型

【2】耗时操作阻塞UI线程(网络下载等 )

常用的俩种方式

【1】多线程/线程池

【2】AsynTask (其实它底层也是线程池 核心线程数5,最大线程数128)

2、JSON数组解析

采用的是某网站的提供的接口(http://www.imooc.com/api/teacher?type=4&num=30)返回的json数据如下

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

这里我们在listview中仅需要name picSmall picBig三个内容即可,在解析之前我们需要javaBean来存储这三个数据,因而需要先定义一个javaBean,NewsBean.Java文件代码如下:

[java] view plain copy
  1. <span style="font-family:Microsoft YaHei;">/* 
  2.  * json数据bean,这里获取三个属性 
  3.  * 图片 
  4.  * 标题 
  5.  * 内容 
  6.  */  
  7. public class NewsBean {  
  8.     public String newsIconUrl;// 图片的网址  
  9.     public String newsTitle;  
  10.     public String newsContent;  
  11.   
  12. }</span>  


下面就开始解析json,并将解析的数据放到List<NewsBean>,代码片段在MainActivity.java中

[java] view plain copy
  1. <span style="font-family:Microsoft YaHei;">/* 
  2.      * 将URL对应的json格式数据转化为所封装的newsBean 
  3.      */  
  4.     // 获取json返回格式数据  
  5.     private List<NewsBean> getJsonData(String URL) {  
  6.         List<NewsBean> newsBeanList = new ArrayList<NewsBean>();  
  7.         try {  
  8.             String jsonString = readStream(new URL(URL).openStream());// 直接根据url获取网络数据返回inputstream类型  
  9.             JSONObject jsonObject;  
  10.             NewsBean newsBean;  
  11.             // Log.d("xsf", jsonString); //打印测试  
  12.             // 将json数据放入jsonobject中,然后通过jsonarray获取所需要的数据集合  
  13.             try {  
  14.                 jsonObject = new JSONObject(jsonString);  
  15.                 JSONArray jsonArray = jsonObject.getJSONArray("data");  
  16.                 // 通过for循环取出jsonarray每个值,放到newsBean的集合中去  
  17.                 for (int i = 0; i < jsonArray.length(); i++) {  
  18.                     jsonObject = jsonArray.getJSONObject(i);  
  19.                     newsBean = new NewsBean();  
  20.                     newsBean.newsIconUrl = jsonObject.getString("picSmall");// 获取小图片  
  21.                     newsBean.newsTitle = jsonObject.getString("name");// 获取title  
  22.                     newsBean.newsContent = jsonObject.getString("description");// 获取内容  
  23.                     newsBeanList.add(newsBean);  
  24.                 }  
  25.   
  26.             } catch (JSONException e) {  
  27.   
  28.                 e.printStackTrace();  
  29.             }  
  30.   
  31.         } catch (IOException e) {  
  32.   
  33.             e.printStackTrace();  
  34.         }  
  35.   
  36.         return newsBeanList;  
  37.     }  
  38. </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中

[java] view plain copy
  1. <span style="font-family:Microsoft YaHei;">// 字节流转字符流,读取函数,解析网页返回的数组  
  2.     private String readStream(InputStream is) {  
  3.         InputStreamReader isr;  
  4.         String result = "";  
  5.         try {  
  6.             String line = "";  
  7.             isr = new InputStreamReader(is, "utf-8");// 字节流封装成字符流并且指定为utf-8格式  
  8.             BufferedReader br = new BufferedReader(isr);// 将字符流通过buffer形式读取出来  
  9.             while ((line = br.readLine()) != null) {  
  10.                 result += line;  
  11.             }  
  12.   
  13.         } catch (UnsupportedEncodingException e) {  
  14.             e.printStackTrace();  
  15.         } catch (IOException e) {  
  16.             e.printStackTrace();  
  17.         }  
  18.         return result;  
  19.     }</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

[java] view plain copy
  1. @Override  
  2.     protected void onCreate(Bundle savedInstanceState) {  
  3.         super.onCreate(savedInstanceState);  
  4.         setContentView(R.layout.activity_main);  
  5.         // 获取listview控件  
  6.         mListview = (ListView) findViewById(R.id.tv_main);  
  7.         // 在主线程中启动AsynTask  
  8.         new NewsAsynTask().execute(URL);  
  9.   
  10.     }  
  11.   
  12.     // 实现网络的异步访问  
  13.     /* 
  14.      * AsynTask 输入三个参数 params,这里需要传入url网址,因此为string类型 progress 这里不需要返回进度显示 
  15.      * 因此为void result 为 json解析之后的数据bean的集合 
  16.      */  
  17.     class NewsAsynTask extends AsyncTask<String, Void, List<NewsBean>> {  
  18.         // 通过获取newsbean集合得到数据传递到adapter中,这样可以显示出每个json数据  
  19.   
  20.         @Override  
  21.         protected List<NewsBean> doInBackground(String... params) {  
  22.   
  23.             return getJsonData(params[0]);// 这里的参数只有一个url网址  
  24.         }  
  25.   
  26.         // 将生成的nesBean设置给listview  
  27.         @Override  
  28.         protected void onPostExecute(List<NewsBean> newsBeans) {  
  29.   
  30.             super.onPostExecute(newsBeans);  
  31.             NewsAdapter adapter = new NewsAdapter(MainActivity.this, newsBeans,  
  32.                     mListview);  
  33.             mListview.setAdapter(adapter);  
  34.         }  
  35.   
  36.     }  





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中

[java] view plain copy
  1. @Override  
  2.     public View getView(int position, View convertView, ViewGroup parent) {  
  3.         // viewholder方式  
  4.         ViewHolder viewholder = null;  
  5.         if (convertView == null) {  
  6.             viewholder = new ViewHolder();  
  7.             convertView = mInflater.inflate(R.layout.item_layout, null);  
  8.             // 对viewholder元素进行初始化  
  9.             viewholder.ivIcon = (ImageView) convertView  
  10.                     .findViewById(R.id.tv_icon);  
  11.             viewholder.tvTitle = (TextView) convertView  
  12.                     .findViewById(R.id.tv_title);  
  13.             viewholder.tvContent = (TextView) convertView  
  14.                     .findViewById(R.id.tv_content);  
  15.             convertView.setTag(viewholder);  
  16.   
  17.         } else {  
  18.             viewholder = (ViewHolder) convertView.getTag();  
  19.         }  
  20.         viewholder.ivIcon.setImageResource(R.drawable.ic_launcher);  
  21.         /* 
  22.          * 防止listview加载图片出现错位,非常重要 以下俩行代码很重要,因此在imageLoader获取图片需要进行判断 
  23.          */  
  24.         String url = mList.get(position).newsIconUrl;  
  25.         viewholder.ivIcon.setTag(url);// 将图片和对应的url进行绑定  
  26.   
  27.         // 使用多线程方式加载实际图片  
  28.         /* 
  29.          * new ImageLoadr().showImageByThread(viewholder.ivIcon, url); 
  30.          */  
  31.   
  32.         // 使用AsyncTask加载实际图片  
  33.         // new ImageLoadr().showImageByAsyncTask(viewholder.ivIcon, url);  
  34.         mImageloader.showImageByAsyncTask(viewholder.ivIcon, url);  
  35.   
  36.         viewholder.tvTitle.setText(mList.get(position).newsTitle);  
  37.         viewholder.tvContent.setText(mList.get(position).newsContent);  
  38.         return convertView;  
  39.     }  
  40.   
  41.     class ViewHolder {  
  42.         public TextView tvTitle, tvContent;  
  43.         public ImageView ivIcon;  
  44.   
  45.     }  


这里通过

[java] view plain copy
  1. String url = mList.get(position).newsIconUrl;  
  2. 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中

[java] view plain copy
  1. @Override  
  2.     public void onScrollStateChanged(AbsListView view, int scrollState) {  
  3.         // listView滑动状态切换的时候才调用  
  4.         // 判断listview滚动状态  
  5.         if (scrollState == SCROLL_STATE_IDLE) {  
  6.             // 滚动停止状态,加载可见项  
  7.             mImageloader.loadImages(mStart, mEnd);  
  8.   
  9.         } else {  
  10.             // 停止任务  
  11.             mImageloader.cancelAllTask();  
  12.         }  
  13.     }  
  14.   
  15.     @Override  
  16.     public void onScroll(AbsListView view, int firstVisibleItem,  
  17.             int visibleItemCount, int totalItemCount) {  
  18.         // 整个滑动过程都会调用,不断获取当前可见项目和最后一个可见项目  
  19.         mStart = firstVisibleItem;  
  20.         mEnd = firstVisibleItem + visibleItemCount;  
  21.         if (mFirrstIn && visibleItemCount > 0) {  
  22.             // 手动加载第一屏  
  23.             mImageloader.loadImages(mStart, mEnd);  
  24.             mFirrstIn = false;  
  25.         }  
  26.   
  27.     }  



6、 图片下载 核心代码 ImageLoader.java


有以下几个看点

6.1、Lru缓存算法

对于从网络上获取图片这种需求,我们都要使用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这里就不扩展开了,代码片段如下

[java] view plain copy
  1. public ImageLoadr(ListView listview) {  
  2.         mListView = listview;  
  3.         mTask = new HashSet<ImageLoadr.ImageLoaderAsynTask>();  
  4.         // 获取最大可使用内存  
  5.         int MaxMemory = (int) Runtime.getRuntime().maxMemory();  
  6.         // 设置所需缓存大小  
  7.         int cacheSize = MaxMemory / 4;  
  8.         mCaches = new LruCache<String, Bitmap>(cacheSize) {  
  9.             @Override  
  10.             protected int sizeOf(String key, Bitmap value) {  
  11.                 // 在每次存入缓存时候调用,告诉系统传入对象的大小  
  12.                 return value.getByteCount();  
  13.             }  
  14.         };  
  15.     }  
  16.   
  17.     // 增加到缓存  
  18.     public void addBitmapTocache(String url, Bitmap bitmap) {  
  19.         if (getBitmapFromCache(url) == null) {  
  20.             // 判断当前是否存在url所指定的图片  
  21.             mCaches.put(url, bitmap);  
  22.         }  
  23.     }  
  24.   
  25.     // 从缓存中获取数据  
  26.     public Bitmap getBitmapFromCache(String url) {  
  27.         return mCaches.get(url);  
  28.   
  29.     }  


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掉

[java] view plain copy
  1. // 用来加载从start到end的所有图片  
  2. public void loadImages(int start, int end) {  
  3.     for (int i = start; i < end; i++) {  
  4.         String url = NewsAdapter.URLS[i];// 获取到了从start开始到end所有rul  
  5.         Bitmap bitmap = getBitmapFromCache(url);  
  6.         if (bitmap == null) {  
  7.             // 缓存中没有该图片则直接加载  
  8.             // new ImageLoaderAsynTask(, url).execute(url);  
  9.             ImageLoaderAsynTask task = new ImageLoaderAsynTask(url);  
  10.             task.execute(url);  
  11.             mTask.add(task);// 将该task保存到当前活动task集合中  
  12.   
  13.         } else {  
  14.             /* 
  15.              * // 缓存中有该图片直接加载 // imageView.setImageBitmap(bitmap); 
  16.              */  
  17.             // 通过tag找到imageView  
  18.             ImageView imageView = (ImageView) mListView  
  19.                     .findViewWithTag(url);  
  20.             imageView.setImageBitmap(bitmap);  
  21.         }  
  22.     }  
  23. }  
  24.   
  25. private class ImageLoaderAsynTask extends AsyncTask<String, Void, Bitmap> {  
  26.     // private ImageView  
  27.     // mImageView;不再需要,可以通过listview的findViewWithTag(url)找到imageView  
  28.     // 防止listview图片加载错位做的处理  
  29.     private String mUrl;  
  30.   
  31.     /* 
  32.      * public ImageLoaderAsynTask(ImageView imageView, String url) { 
  33.      * mImageView = imageView; mUrl = url; } 
  34.      */  
  35.     public ImageLoaderAsynTask(String url) {  
  36.         mUrl = url;  
  37.     }  
  38.   
  39.     @Override  
  40.     protected Bitmap doInBackground(String... params) {  
  41.         String url = params[0];  
  42.         // 从网络中获取图片  
  43.         Bitmap bitmap = getBitMapFromURL(url);  
  44.         if (bitmap != null) {  
  45.             addBitmapTocache(url, bitmap);  
  46.         }  
  47.         return bitmap;  
  48.     }  
  49.   
  50.     @Override  
  51.     protected void onPostExecute(Bitmap bitmap) {  
  52.   
  53.         super.onPostExecute(bitmap);  
  54.         // 设置图片时增加判断防止listview加载图片错位  
  55.         /* 
  56.          * if (mImageView.getTag().equals(mUrl)) { 
  57.          * mImageView.setImageBitmap(bitmap); } 
  58.          */  
  59.         ImageView imageView = (ImageView) mListView.findViewWithTag(mUrl);  
  60.         if (imageView != null && bitmap != null) {  
  61.             imageView.setImageBitmap(bitmap);  
  62.         }  
  63.         // 设置完bitmap之后表明该task已经失去作用,需要从集合中移除  
  64.         mTask.remove(this);  
  65.     }  
  66.   
  67. }  


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