- 通过这个Demo的练习,我对AsyncTask不再像之前那样陌生了。而且通过这个Demo,我对ListView/GridView去加载多图片这种需求有了更好的方式,也就是接下来这个Demo想要展示的内容。
- 之前对List去加载多图片这样的需求,要么是子线程去搞一下,要么直接在主线程搞。当然,在主线程搞的前提是,是加载本地图片,不是网络图片。没有去使用什么Lrucache,也没有使用滑动时停止加载,不滑动时进行数据加载的策略。糟糕的用户体验,糟糕应用。不得不说,不去看别人的优秀代码,永远不知道自己的代码写的有多烂。
- 这次,同样是根据慕课网的讲解,来写的一个Demo。内容上,可能会有一点点小的偏差。但是不影响整体效果。
- ok,不多说,还是上代码吧。
- 这个Demo只有一个页面,这个页面就是一页新闻的页面。里面显示的就是一条条的新闻。每条新闻有一个标题,和一个内容描述,以及一个小图片。
- 对于新闻内容的获取,使用了一个
AsyncTask
去加载。然后先给每个条目只显示一张默认图片+新闻标题和内容。这样,也算是基本完成了新闻页面的展示。只是每个新闻的图标没有去展示。 - 然后是对新闻的每个条目的图片的获取,还是通过
AsyncTask
去获取的。不过和对新闻内容的获取的区别是:新闻内容是一次性获取下来,也就是说,对于新闻内容的获取,只需要调用一次AsyncTask
的execute
方法;但是对于新闻的每个条目的图片的获取,需要多次去执行AsyncTask
的execute
方法,每次执行,获取一个条目的图片。 - 当然,对于新闻条目的图片,如果仅仅是这样的多次获取,还是不够好的,因为这样以来,每次去滑动,都会去加载屏幕上显示的条目的图片。而且,已经加载过的图片,仍然会去加载,这样就很浪费流量了。于是,就做了一个缓存策略。通过
Lrucache
,去缓存加载过的图片,然后,在去网络加载图片之前,先看看缓存中有没有该图片,如果有了,就不去加载了,以提高用户体验。 - 但是这样还是不够的。因为当ListView的布局比较复杂,需要显示的内容比较多的时候,当用户去滑动屏幕,而这时候,如果有图片加载完成,会去显示到屏幕上,就是这样的一个过程,可能会导致屏幕的卡顿。这样也不是好的用户体验。于是,就做了一个滑动策略:也就是,当用户去滑动屏幕的时候,并不去加载图片资源,就让他看看新闻的标题和内容就好。当用户停止滑动的时候,就去加载对应的图片资源,显示正确的图片。(当然,如果是已经加载好的图片,会在滑动的同时,直接显示出来,这样也不会导致滑动的卡顿)。并且,由于第一次打开页面的时候,不会触发滑动停止的状态,于是,在第一次打开页面的时候,去主动加载一个屏幕需要显示的图片内容,后面的,就是根据滑动停止状态才去加载,并且在滑动的过程中,会去取消掉,所有的,准备加载的任务。这样,就可以在滑动的时候开心的滑,在加载的时候,安心的加载。
- 对于新闻内容的获取,使用了一个
以上,就是对这个Demo的比较详细的介绍。
代码区:
- xml布局:
- a.
Actiivty
的布局:
- a.
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context=".MoocActivity" >
<ListView
android:id="@+id/listview"
android:layout_width="match_parent"
android:layout_height="match_parent" >
</ListView>
</RelativeLayout>
* b `listview`的Item的布局:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<ImageView
android:id="@+id/imageview"
android:layout_width="64dp"
android:layout_height="64dp"
android:contentDescription="@string/action_settings"
android:src="@drawable/ic_launcher" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_toRightOf="@id/imageview"
android:orientation="vertical"
android:paddingLeft="4dp" >
<TextView
android:id="@+id/tv_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:maxLines="1"
android:text="Title"
android:textSize="16sp" />
<TextView
android:id="@+id/tv_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:maxLines="3"
android:text="Content"
android:textSize="12sp" />
</LinearLayout>
</RelativeLayout>
java代码:
Activity
的代码:
package com.duck.moocAsyncTask; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.net.URL; import java.net.URLConnection; import java.util.ArrayList; import java.util.List; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import android.app.Activity; import android.os.AsyncTask; import android.os.Bundle; import android.widget.ListView; public class MoocActivity extends Activity { private static final String URL = "http://www.imooc.com/api/teacher?type=4&num=30"; private ListView mListView; private NewsAdapter adapter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_mooc); mListView = (ListView) findViewById(R.id.listview); new NewsAsyncTask().execute(URL); } class NewsAsyncTask extends AsyncTask<String, Void, List<NewsBean>> { private static final boolean DEBUG = false; @Override protected void onPostExecute(List<NewsBean> result) { super.onPostExecute(result); adapter = new NewsAdapter(result, MoocActivity.this,mListView); mListView.setAdapter(adapter); } @Override protected List<NewsBean> doInBackground(String... params) { String url = params[0]; return getDatasFromUrl(url); } private List<NewsBean> getDatasFromUrl(String url) { try { URLConnection connection = new URL(url).openConnection(); InputStream is = connection.getInputStream(); String jsonString = getStringFromStream(is); return getNewsFromJson(jsonString); } catch (Exception e) { e.printStackTrace(); } return null; } private List<NewsBean> getNewsFromJson(String jsonString) { List<NewsBean> newsBeanList = new ArrayList<NewsBean>(); try { JSONObject jsonObject = new JSONObject(jsonString); JSONArray jsonArray = jsonObject.getJSONArray("data"); for (int i = 0; i < jsonArray.length(); i++) { JSONObject object = jsonArray.getJSONObject(i); String url = object.getString("picSmall"); String title = object.getString("name"); String content = object.getString("description"); NewsBean bean = new NewsBean(); bean.iconUrl = url; bean.title = title; bean.content = content; newsBeanList.add(bean); } } catch (JSONException e) { e.printStackTrace(); } if (DEBUG) { // 数据获取OK! System.out.println("newsBeanList: " + newsBeanList); System.out.println("\n \n newsBeanList.SIZE: " + newsBeanList.size()); System.out.println("\n \n newsBeanList.3: " + newsBeanList.get(3).title); } return newsBeanList; } private String getStringFromStream(InputStream is) { String json = ""; BufferedReader br = new BufferedReader(new InputStreamReader(is)); String line; try { while ((line = br.readLine()) != null) { json += line; } } catch (IOException e) { e.printStackTrace(); } finally { try { br.close(); } catch (IOException e) { e.printStackTrace(); } try { is.close(); } catch (IOException e) { e.printStackTrace(); } } return json; } } }
Adapter
代码:package com.duck.moocAsyncTask; import java.util.List; import android.content.Context; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.AbsListView; import android.widget.AbsListView.OnScrollListener; import android.widget.BaseAdapter; import android.widget.ImageView; import android.widget.ListView; import android.widget.TextView; public class NewsAdapter extends BaseAdapter implements OnScrollListener { private static boolean firstIn; private List<NewsBean> mList; private LayoutInflater mInflater; private ImageLoader loader; private int start; private int end; public static String[] URLS; public NewsAdapter(List<NewsBean> newsBeans, Context context, ListView listView) { firstIn = true; URLS = new String[newsBeans.size()]; for (int i = 0; i < newsBeans.size(); i++) { URLS[i] = newsBeans.get(i).iconUrl; } mList = newsBeans; mInflater = LayoutInflater.from(context); loader = new ImageLoader(); listView.setOnScrollListener(this); } @Override public int getCount() { return mList.size(); } @Override public NewsBean getItem(int position) { return mList.get(position); } @Override public long getItemId(int position) { return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { ViewHolder holder; if (convertView == null) { holder = new ViewHolder(); convertView = mInflater.inflate(R.layout.item, null); holder.ivIcon = (ImageView) convertView .findViewById(R.id.imageview); holder.tvTitle = (TextView) convertView.findViewById(R.id.tv_title); holder.tvContent = (TextView) convertView .findViewById(R.id.tv_content); convertView.setTag(holder); } else { holder = (ViewHolder) convertView.getTag(); } // show data NewsBean bean = getItem(position); holder.ivIcon.setImageResource(R.drawable.ic_launcher); holder.ivIcon.setTag(bean.iconUrl);// 绑定tag // new ImageLoader(holder.ivIcon, bean.iconUrl) // .disPlayByThread(bean.iconUrl); loader.disPlayByAsyncTask(holder.ivIcon, bean.iconUrl); holder.tvTitle.setText(bean.title); holder.tvContent.setText(bean.content); return convertView; } class ViewHolder { ImageView ivIcon; TextView tvTitle; TextView tvContent; } @Override public void onScrollStateChanged(AbsListView view, int scrollState) { /** * <ul> * <li>如果当前是空闲状态,就去加载图片;</li> * <li>如果当前是滑动状态,就停止所有的加载任务</li> * </ul> */ if (scrollState == SCROLL_STATE_IDLE) { loader.loadImages(start, end, view); } else { loader.cancelAllTasks(); } } @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { start = firstVisibleItem; end = firstVisibleItem + visibleItemCount; if (firstIn && visibleItemCount > 0) { // TODO:visibleItemCount > 0 这个条件不能少,不然预加载没效果!!! // 首次进入页面,并且界面上面的Item已经加载出来了,才去加载一个屏幕Item的的图片 loader.loadImages(start, end, view); firstIn = false; } } }
Bean
的代码:package com.duck.moocAsyncTask; public class NewsBean { public String iconUrl; public String title; public String content; }
ImageLoader
的代码:
“`
package com.duck.moocAsyncTask; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.net.URLConnection; import java.util.HashSet; import java.util.Set; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.os.AsyncTask; import android.os.Handler; import android.os.Message; import android.util.LruCache; import android.widget.AbsListView; import android.widget.ImageView; public class ImageLoader { private ImageView mImageView; private String mUrl; private LruCache<String, Bitmap> mCache; private Set<LoadImageAsyncTask> mTasks; public ImageLoader() { mTasks = new HashSet<ImageLoader.LoadImageAsyncTask>(); long maxMemory = Runtime.getRuntime().maxMemory(); int maxSize = (int) (maxMemory / 4); mCache = new LruCache<String, Bitmap>(maxSize) { @Override protected int sizeOf(String key, Bitmap value) { return value.getByteCount(); } }; } public void addBitmapToCache(String url, Bitmap bitmap) { mCache.put(url, bitmap); } public Bitmap getBitmapFromCache(String url) { return mCache.get(url); } private Handler handler = new Handler() { public void handleMessage(android.os.Message msg) { if (mImageView.getTag().equals(mUrl)) { mImageView.setImageBitmap((Bitmap) msg.obj); } } }; private AbsListView mListview; protected Bitmap getBitmapFromUrl(String urlString) { URLConnection connection = null; InputStream is = null; try { connection = new URL(urlString).openConnection(); is = connection.getInputStream(); Bitmap bitmap = BitmapFactory.decodeStream(is); return bitmap; } catch (Exception e) { e.printStackTrace(); } finally { try { is.close(); } catch (IOException e) { e.printStackTrace(); } } return null; } public void disPlayByThread(ImageView imageView, final String url) { mImageView = imageView; mUrl = url; new Thread(new Runnable() { @Override public void run() { Bitmap bitmap = getBitmapFromUrl(url); Message message = Message.obtain(); message.obj = bitmap; handler.sendMessage(message); } }).start(); } public void disPlayByAsyncTask(ImageView imageView, String url) { Bitmap bitmap = getBitmapFromCache(url); if (bitmap != null) { imageView.setImageBitmap(bitmap); } else { // new LoadImageAsyncTask(imageView, url).execute(url); // /加载图片,不再使用该方法。该方法仅仅用来显示图片 imageView.setImageResource(R.drawable.ic_launcher); } } class LoadImageAsyncTask extends AsyncTask<String, Void, Bitmap> { private ImageView mImageView; private String mUrl; // public LoadImageAsyncTask(ImageView imageView, String url) { // mImageView = imageView; // mUrl = url; // } public LoadImageAsyncTask(String url) { mUrl = url; } @Override protected Bitmap doInBackground(String... params) { String url = params[0]; Bitmap bitmap = getBitmapFromUrl(url); if (getBitmapFromCache(url) == null) { addBitmapToCache(url, bitmap); } return bitmap; } @Override protected void onPostExecute(Bitmap result) { super.onPostExecute(result); // if (mImageView.getTag().equals(mUrl)) { // mImageView.setImageBitmap(result); // } ImageView imageView = (ImageView) mListview.findViewWithTag(mUrl); if (imageView != null && result != null){ imageView.setImageBitmap(result); } //task执行结束了 mTasks.remove(this);//TODO: 容易忘记的一点 } } /** * 加载从start 到end的所有图片 * * @param start * @param end */ public void loadImages(int start, int end, AbsListView listView) { this.mListview = listView; for (int i = start; i < end; i++) { String url = NewsAdapter.URLS[i]; Bitmap bitmap = getBitmapFromCache(url); if (bitmap != null) { ImageView imageView = (ImageView) listView.findViewWithTag(url); imageView.setImageBitmap(bitmap); } else { LoadImageAsyncTask task = new LoadImageAsyncTask(url); task.execute(url); mTasks.add(task); } } } public void cancelAllTasks() { if(mTasks!=null){ for (LoadImageAsyncTask task :mTasks) { task.cancel(false); } } } }
简单介绍一个以上代码分别表达的意思:
Activity
就不用说了,就是显示ListView,获取数据,将数据放进Adapter
,让ListView
去显示获取到的网络数据。Adapter
也没什么说的,就是将传递过来的数据集合List
里面的数据放到每个Item
里面去展示。不过需要注意的是:1.在getView
方法里面去调用了ImageLoader
的显示图片的方法,这个方法的作用就是将加载完成的图片显示出来,不涉及异步任务的操作。2.在listView
的OnScrollListener
的重写方法中,做了两个操作:一,.在滑动的状态,取消所有的,正在执行的异步任务,在非滑动状态,去加载当前屏幕所需要显示的图片。二,在首次加载出页面上ListView的条目的时候,去加载当前屏幕的这个条目需要的图片。ImageLoader
的作用,就有点重要了。首先是里面有一个Lrucache
,每次在加载到图片的时候,都会去将图片存放到Lrucache
中;然后,ImageLoader
中有一个loadImages(int start, int end, AbsListView listView)
的方法,这个方法的左右就是去加载当前屏幕上,需要展示的所有图片。而加载图片是一个网络的操作,于是这里面的内部类AsyncTask
的作用就是去网络获取图片。然后,为了不去加载已经加载过的图片,在loadImages
里面会有一个判断,如果当前图片已经存在,就直接去显示到ListView
对应的ImageView
上面,如果,当前图片不在缓存中,就去开启异步任务去加载,顺便,将开启的任务放到Task集合
中去(为什么要搞这么一个集合呢?因为在滑动的时候,需求停止所有的加载任务,就需要使用到这个集合)。最后,在这个内部类AsyncTask
中,除了根据url
去加载网络图片,将图片放入缓存之外,在onPostExecute
方法中,还有去将图片显示到ListView
对应的ImageView
中去。并且,去Task集合
中去移除当前的Task
.
大体上,关于这个Demo的内容就这些了。完整项目下载
- 有不足之处,欢迎指正。