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

这里我们在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>

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

<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>

整个逻辑在 代码注释中很清楚,通过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 = "";
		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异步加载

先来看看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

@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);

	}

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集合中

			} 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);
		}

	}

json解析,异步下载(listview仅滑动时加载)Demo总结的更多相关文章

  1. Android ListView只加载当前屏幕内的图片(解决list滑动时加载卡顿)

    最近在做ListView分页显示,其中包括图片 和文字(先下载解析文字内容,再异步加载图片)发现每次点击下一页后,文字内容加载完毕,马上向下滑动,由于这时后台在用线程池异步下载图片,我每页有20条,也 ...

  2. Android RecyclerView使用 及 滑动时加载图片优化方案

    1.控制线程数量 + 数据分页加载2.重写onScrollStateChanged方法 这个我们后面再谈,下面先来看看RecyclerView控件的使用及我们为什么选择使用它 RecyclerView ...

  3. Android开源代码解读のOnScrollListener实现ListView滚屏时不加载数据

    使用ListView过程中,如果滚动加载数据的操作比较费时,很容易在滚屏时出现屏幕卡住的现象,一个解决的办法就是不要在滚动时加载数据,而是等到滚动停止后再进行数据的加载.这同样要实现OnScrollL ...

  4. android 在自定义的listview(有刷新加载项)列表中,数据过少时不能铺满整个屏幕时,header和footer同时显示问题

    android  在自定义的listview(有刷新加载项)列表中,数据过少时,当刷新时,加载项也会显示,这是很头疼的一个问题,查阅了一些资料,总结了一个比较不错的方法: 原来代码: @Overrid ...

  5. 【FastDev4Android框架开发】RecyclerView完全解析之下拉刷新与上拉加载SwipeRefreshLayout(三十一)

    转载请标明出处: http://blog.csdn.net/developer_jiangqq/article/details/49992269 本文出自:[江清清的博客] (一).前言: [好消息] ...

  6. 【Android】首次进入应用时加载引导界面

    参考文章: [1]http://blog.csdn.net/wsscy2004/article/details/7611529 [2]http://www.androidlearner.net/and ...

  7. Android 首次进入应用时加载引导界面

    功能需求:首次进入应用时加载引导界面 思路: 1.首次进入,怎么判断?查看SharedPreferences中某个字段 2.基本上每个应用都有个进入实际功能是的动画加载页面,我们可以在该Activit ...

  8. Uboot 引导内核时加载地址与入口地址问题

    如果使用 mkimage 生成内核镜像文件的话,会在内核的前头加上了 64 bytes 的信息头,供建立 tag 之用.bootm 命令会首先判断 bootm xxx 这个指定的地址 xxx 与 -a ...

  9. FMX StringGrid向上滑动自动加载记录(二)

    写完FMX StringGrid向上滑动自动加载记录(一)自己也觉得不理想,实现的别扭与复杂,现在找到更好的实现方法,原来,StringGrid从基类TCustomPresentedScrollBox ...

随机推荐

  1. Java中的Lock锁

    Lock锁介绍: 在java中可以使用 synchronized 来实现多线程下对象的同步访问,为了获得更加灵活使用场景.高效的性能,java还提供了Lock接口及其实现类ReentrantLock和 ...

  2. Touch 方法&属性 映射工具

    Touch 方法&属性 映射工具(0.5 版本) 标签 : github 线上后门与接口调试: 原先需要测试一个接口(如Dubbo.DAO), 或为线上留后门, 需要写大量的Web层(Api. ...

  3. nginx 日志分析工具goaccess

    参考:https://www.goaccess.io/download 安装 $ wget http://tar.goaccess.io/goaccess-1.1.1.tar.gz $ tar -xz ...

  4. 安卓高级6 SnackBar

    引言 文/李牧羊(简书作者) 原文链接:http://www.jianshu.com/p/2654e6bda3b1 著作权归作者所有,转载请联系作者获得授权,并标注"简书作者". ...

  5. Dynamics CRM Entity Relationship Many to Many (N:N)

    该博客对N:N的关系的查询列出了两种方式,一种RetrieveMultipleRequest,一种Fetch XML ,有谁对N:N关系的查询了解不是很深的可以学习下. http://andreasw ...

  6. Hadoop就业面试题

    ----------------------------------------------------------------------------- [申明:资料来源于互联网] 本文链接:htt ...

  7. Hive的HQL语句及数据倾斜解决方案

    [版权申明:本文系作者原创,转载请注明出处] 文章出处:http://blog.csdn.net/sdksdk0/article/details/51675005 作者: 朱培          ID ...

  8. Swift:Minimizing Annotation with Type Inference

    许多程序猿更喜欢比如Python和Javascript这样的动态语言,因为这些语言并不要求程序猿为每个变量声明和管理它们的类型. 在大多数动态类型的语言里,变量可以是任何类型,而类型声明是可选的或者根 ...

  9. EJB_开发单表映射的实体bean

    开发单表映射的实体bean 实体bean 它属于java持久化规范(JPA)里的技术,实体bean通过元数据在Javabean和数据库表之间建立起映射关系,然后Java程序员就可以随心所欲的使用面向对 ...

  10. Strom数据流分组解析

    本文可作为 <<Storm-分布式实时计算模式>>一书1.5节的读书笔记 数据流分组定义了一个数据流中的tuple如何分发给topology中不同bolt的task. Shuf ...