什么是异步加载:
就是用异步的方式去加载数据
使用异步加载的原因
1.android是单线程模型
2.耗时操作都必须放在单独的线程中去做,而不能去阻塞UI线程
异步加载最常用的两种方式
1.多线程\线程池
2.AsyncTask(底层也是基于线程池来实现的)
实现ListView图文混排
创建一个Item_layout布局,即ListView每个项的布局
有如下总结:
textview中可以设置Maxline属性设置文字的行数,
garivity设置该view的内容相对于该view的位置,
layout_garivity属性的意思是设置该view相对于父容器的位置。
拿到Json格式的数据
慕课Json格式的API网址:
private static final String urlPath = "http://www.imooc.com/api/teacher?type=4&num=30";
创建一个NewsBean对象来与Item_Layout文件中的数据形成映射
NewsBean.java
public class NewsBean
{
public String newsIconUrl;
public String newsTitle;
public String newsContent;
}
创建一个AsyncTask来完成异步任务
1、其中第1泛型参数传入一个URL对象,第三个泛型参数返回一个元素为NewsBean的List集合(将传到BaseAdapter中为其NewsBean设值)
在doInBackground方法的返回值为
return getJsonData(params[0]);
2、在getJsonData中向readStream方法传入一个字节流对象is,返回Json格式数据的字符流数据。
源码如下:
private String readStream(InputStream is) {
InputStreamReader isr;
String result = "";
try {
isr = new InputStreamReader(is, "utf-8");
BufferedReader br = new BufferedReader(isr);
String line = "";
while ((line = br.readLine()) != null) {
result += line;
}
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return result;
}
3、而getJsonData方法拿到字符流后,将其转化成一个元素为NewsBean的List集合newsBeamsList。
private List<NewsBean> getJsonData(String url) {
List<NewsBean> newsBeamsList = new ArrayList<NewsBean>();
try {
String jsonString = readStream(new URL(urlPath).openStream());
JSONObject jsonObject;
NewsBean newsBean;
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");
newsBeamsList.add(newsBean);
}
} catch (IOException e) {
e.printStackTrace();
} catch (JSONException e) {
e.printStackTrace();
}
return newsBeamsList;
}
4、在onPostExecute方法中这个集合将作为参数传递给BaseAdapter。
图片异步加载
上一个例子中用默认图片给icon赋值:
viewHolder.icon.setImageResource(R.mipmap.ic_launcher);
解析到的JSON格式的数据传给NewsBean.newsIconUrl为图片的URL,
接下来用异步加载的方式来加载图片:
多线程的方式进行异步加载:
新建类ImageLoader.java
1、实现getBitmapFromURL方法:
public Bitmap getBitmapFromURL(String urlString) {
Bitmap bitmap = null;
InputStream is = null;
try {
URL url = new URL(urlString);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
is = new BufferedInputStream(conn.getInputStream());
bitmap = BitmapFactory.decodeStream(is);
conn.disconnect();
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
is.close();
} catch (Exception e) {
e.printStackTrace();
}
}
return bitmap;
}
2、showImgByThread方法
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if (mImageView.getTag().equals(mUrl)) {
mImageView.setImageBitmap((Bitmap) msg.obj);
}
}
};
---------------------------------------------------
public void showImgByThread(ImageView imageView, final String url) {
mImageView = imageView;
this.mUrl = url;
new Thread() {
public void run() {
super.run();
Bitmap bitmap = getBitmapFromURL(url);
Message message = Message.obtain();
message.obj = bitmap;
mHandler.sendMessage(message);
}
}.start();
}
使用AsyncTask进行异步加载:
private class NewsAynsTask extends AsyncTask<String, Void, Bitmap> {
private ImageView mImageView;
private String mUrl;
public NewsAynsTask(String url) {
mImageView = imageView;
mUrl = url;
}
@Override
protected Bitmap doInBackground(String... params) {
String url = params[0];
Bitmap bitmap = getBitmapFromURL(url); return bitmap;
}
@Override
protected void onPostExecute(Bitmap bitmap) {
super.onPostExecute(bitmap);
if (imageView.getTag().equals(mUrl) && bitmap != null) {
imageView.setImageBitmap(bitmap);
}
}
}
-----------------------------------------------
//在showImgByAysncTask方法中调用如下:
new NewsAsyncTask(imageView, url).execute(url);
使用LURCache进行优化
由于在滑动的过程中要重复的从网络下载图片,还有手机网速和用户流量的因素,我们引入LURCache进行优化。
LRU算法:Least Recently Used 近期最少使用算法
Android提供了LRUCache类来实现这个缓存算法
(暂且将LRUCache认为成一个Map集合即可)
为了不让重复创建LRUCache需要在BaseAdapter中初始化一个
ImageLoader类的全局对象:private ImageLoader mImgLoader;
千万不要在getView方法中创建ImageLoader对象;
ImageLoader构造函数的修改
在ImageLoader类构造函数中初始化以下数据。还要实现get、put方法
//获取最大可用内存
int maxMemory = (int) Runtime.getRuntime().maxMemory();
int cacheSize = maxMemory / 4;
mCache = new LruCache<String, Bitmap>(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap value) {
// 在每次存入缓存时调用(value所占空间的大小)
// 默认返回value的个数
return value.getByteCount();
}
};
--------------------------------------------------------
//以get、put方法
public void addBitmapToCache(String url, Bitmap bitmap) {
if (getBitmapFromCache(url) == null) {
mCache.put(url, bitmap);
}
}
public Bitmap getBitmapFromCache(String url) {
return mCache.get(url);
}
修改showImgByThread方法
......
new Thread() {
public void run() {
super.run();
Message message = null;
//---->获取cache中的值<----
Bitmap bitmap = getBitmapFromCache(url);
//---->判断是否需要下载<----
if (bitmap == null) {
bitmap = getBitmapFromURL(url);
}
message = Message.obtain();
message.obj = bitmap;
mHandler.sendMessage(message);
}
}.start();
......
修改showImgByAysncTask方法
public void showImgByAysncTask(ImageView imageView, String url) {
//---->获取cache中的值<----
Bitmap bitmap = getBitmapFromCache(url);
//---->判断是否需要下载<----
if (bitmap == null) {
new NewsAsyncTask(imageView, url).execute(url);
} else {
imageView.setImageBitmap(bitmap);
}
}
修改NewsAynsTask类的doInBackground方法
protected Bitmap doInBackground(String... params) {
String url = params[0];
Bitmap bitmap = getBitmapFromURL(url);
if (bitmap != null) {
//把图片加入缓存
addBitmapToCache(url, bitmap);
}
return bitmap;
}
滚动时的高效优化
对于一个listItem很复杂的app滑动过程中有时画面会显得很卡顿,是由于滑动过程中数据还在加载引起的
解决办法:
ListView滑动停止后才加载可见项 和
ListView滑动时,取消所有的加载项
在适配器类中的滑动监听器
在BaseAdapter构造函数中增加了一个listView参数,ImageLoader类要用
public class NewsAdapter extends BaseAdapter implements AbsListView.OnScrollListener{
public static String[] URLS; ...
public NewsAdapter(Context context, List<NewsBean> data, ListView listView)
{
mList = data;
mInflater = LayoutInflater.from(context);
mImgLoader = new ImageLoader(listView);
//将所有图片的URL存入到URLS数组中
URLS = new String[data.size()];
for(int i = 0; i < data.size(); i++)
{
URLS[i] = mList.get(i).newsIconUrl;
}
//为ListView设置监听器
listView.setOnScrollListener(this);
//设置标志位判断ListView是否第一次显示界面
mFirstIn = true;
}
......
--------------------------------------------------------
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
//处于停止状态
if (scrollState == SCROLL_STATE_IDLE) {
//加载可见项
mImgLoader.loadImages(mStart, mEnd);
} else {
//停止加载
mImgLoader.cancelAllTasks();
}
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
mStart = firstVisibleItem;
mEnd = firstVisibleItem + visibleItemCount;
//首次加载时使用
if (mFirstIn == true && visibleItemCount > 0) {
mImgLoader.loadImages(mStart, mEnd);
mFirstIn = false;
}
}
}
ImageLoader构造方法的修改
private ListView mListView;
private Set< NewsAynsTask > mtask;
//传一个ListView
public ImageLoader(ListView listView){…}
ImageLoader类方法修改
1、增加loadImages方法
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) {
NewsAynsTask task = new NewsAynsTask(url);
task.execute(url);
mtask.add(task);
} else
{
ImageView imageView = (ImageView) mListView.findViewWithTag(url);
imageView.setImageBitmap(bitmap);
}
}
}
2、修改AsyncTask类传入的值(无必要)
AsyncTask类构造函数修改如下:
public NewsAynsTask(ImageView imageView, String url)
—>public NewsAynsTask(String url)
老师讲的时候如上修改了传入的参数,我认为没有必要:
1、最主要一点,降低了NewsAynsTask类的复用性
2、在onPostExecute方法中用
ImageView imageView = (ImageView) mListView.findViewWithTag(url);
找到需要更新的ImageView对象:
而在ImageLoader中同样可以做到。
3、修改showImgByAysncTask方法(可去掉)
public void showImgByAysncTask(ImageView imageView, String url) {
imageView.setImageBitmap(R.mipmap.ic_launcher);
}
由于将加载图片的权利移交给了loadImages方法,而且loadImages方
法也有从cache中去除图片的操作,故这里的showImgByAysncTask方法
只能是在ListView第一次显示并且onScroll没有加载出来的时候加载默认的图片的作用了(并且在getView方法中已经将图片设置为默认了)
4、增加cancelAllTasks方法
public void cancelAllTasks() {
if (mtask != null) {
for (NewsAynsTask task : mtask) {
task.cancel(false);
}
}
}
onScrollStateChanged方法中调用,用来取消线程池中的异步任务
总结
1、通过异步加载,避免阻塞UI线程
2、通过LruCache,将已下载的图片放到内存中
3、通过判断ListView的滑动状态,决定何时加载图片
4、不仅仅是ListView,任何控件都可以使用异步加载