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数据如下
这里我们在listview中仅需要name picSmall picBig三个内容即可,在解析之前我们需要javaBean来存储这三个数据,因而需要先定义一个javaBean,NewsBean.java文件代码如下:
<span style="font-family:Microsoft YaHei;">/* * json数据bean,这里获取三个属性 * 图片 * 标题 * 内容 */ public class NewsBean { public String newsIconUrl;// 图片的网址 public String newsTitle; public String newsContent; }</span>
<span style="font-family:Microsoft YaHei;">/* * 将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>
这里还涉及到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 = ""; try { 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) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } return result; }</span>
3、AsynTask异步加载
@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 @Override 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中
@Override 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; viewholder.ivIcon.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中
@Override public void onScrollStateChanged(AbsListView view, int scrollState) { // listView滑动状态切换的时候才调用 // 判断listview滚动状态 if (scrollState == SCROLL_STATE_IDLE) { // 滚动停止状态,加载可见项 mImageloader.loadImages(mStart, mEnd); } else { // 停止任务 mImageloader.cancelAllTask(); } } @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { // 整个滑动过程都会调用,不断获取当前可见项目和最后一个可见项目 mStart = firstVisibleItem; mEnd = firstVisibleItem + visibleItemCount; if (mFirrstIn && visibleItemCount > 0) { // 手动加载第一屏 mImageloader.loadImages(mStart, mEnd); mFirrstIn = false; } }
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这里就不扩展开了,代码片段如下
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); }
使用了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集合中 } else { /* * // 缓存中有该图片直接加载 // 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; } @Override protected Bitmap doInBackground(String... params) { String url = params[0]; // 从网络中获取图片 Bitmap bitmap = getBitMapFromURL(url); if (bitmap != null) { addBitmapTocache(url, bitmap); } return bitmap; } @Override 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) { imageView.setImageBitmap(bitmap); } // 设置完bitmap之后表明该task已经失去作用,需要从集合中移除 mTask.remove(this); } }