异步加载的练习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数据如下
![](http://img.blog.csdn.net/20151010153759013?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center&x-oss-process=image/resize,w_1400/format,webp)
这里我们在listview中仅需要name picSmall picBig三个内容即可,在解析之前我们需要javaBean来存储这三个数据,因而需要先定义一个javaBean,NewsBean.Java文件代码如下:
- <span style="font-family:Microsoft YaHei;">
-
-
-
-
-
- public class NewsBean {
- public String newsIconUrl;
- public String newsTitle;
- public String newsContent;
-
- }</span>
下面就开始解析json,并将解析的数据放到List<NewsBean>,代码片段在MainActivity.java中
- <span style="font-family:Microsoft YaHei;">
-
-
-
- private List<NewsBean> getJsonData(String URL) {
- List<NewsBean> newsBeanList = new ArrayList<NewsBean>();
- try {
- String jsonString = readStream(new URL(URL).openStream());
- JSONObject jsonObject;
- NewsBean newsBean;
-
-
- try {
- jsonObject = new JSONObject(jsonString);
- JSONArray jsonArray = jsonObject.getJSONArray("data");
-
- 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");
- 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");
- BufferedReader br = new BufferedReader(isr);
- 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);
-
- mListview = (ListView) findViewById(R.id.tv_main);
-
- new NewsAsynTask().execute(URL);
-
- }
-
-
-
-
-
-
- class NewsAsynTask extends AsyncTask<String, Void, List<NewsBean>> {
-
-
- @Override
- protected List<NewsBean> doInBackground(String... params) {
-
- return getJsonData(params[0]);
- }
-
-
- @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 = null;
- if (convertView == null) {
- viewholder = new ViewHolder();
- convertView = mInflater.inflate(R.layout.item_layout, null);
-
- 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);
-
-
-
- String url = mList.get(position).newsIconUrl;
- viewholder.ivIcon.setTag(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);
然后调用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) {
-
-
- 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) {
-
- 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掉
-
- public void loadImages(int start, int end) {
- for (int i = start; i < end; i++) {
- String url = NewsAdapter.URLS[i];
- Bitmap bitmap = getBitmapFromCache(url);
- if (bitmap == null) {
-
-
- ImageLoaderAsynTask task = new ImageLoaderAsynTask(url);
- task.execute(url);
- mTask.add(task);
-
- } else {
-
-
-
-
- ImageView imageView = (ImageView) mListView
- .findViewWithTag(url);
- imageView.setImageBitmap(bitmap);
- }
- }
- }
-
- private class ImageLoaderAsynTask extends AsyncTask<String, Void, Bitmap> {
-
-
-
- private String mUrl;
-
-
-
-
-
- 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);
-
-
-
-
-
- ImageView imageView = (ImageView) mListView.findViewWithTag(mUrl);
- if (imageView != null && bitmap != null) {
- imageView.setImageBitmap(bitmap);
- }
-
- mTask.remove(this);
- }
-
- }
转载:http://blog.csdn.net/xsf50717/article/details/49024075