Android异步加载
Android异步加载
一、为什么要使用异步加载?
1.Android是单线程模型
2.耗时操作阻碍UI线程
二、异步加载最常用的两种方式
1.多线程、线程池
2.AsyncTask
三、实现ListView图文混排
3-1 实现读取网页中的json数据到ListView中 (图片首先为默认图片)
3.1.1:主布局只有一个ListView和一个listView_item的布局
3.1.2:网页json数据的链接(http://www.imooc.com/api/teacher?type=4&num=30),打开后为json数据,开发者可以通过使用Json格式化工具进行清楚的查看json数据的信息
3.1.3:书写解析网页JSON数据的代码
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109package
cn.edu.bzu.async_listview;
import
java.io.BufferedReader;
import
java.io.IOException;
import
java.io.InputStream;
import
java.io.InputStreamReader;
import
java.io.UnsupportedEncodingException;
import
java.util.ArrayList;
import
java.util.List;
import
java.net.MalformedURLException;
import
java.net.URL;
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.util.Log;
import
android.widget.ListView;
public
class
MainActivity
extends
Activity {
private
ListView mListView;
@Override
protected
void
onCreate(Bundle savedInstanceState) {
super
.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mListView=(ListView) findViewById(R.id.lv_main);
new
NewAsyncTask().execute(URL);
}
/**
*实现网络的异步访问
*/
class
NewAsyncTask
extends
AsyncTask<String, Void, List<NewsBean> >{
//参数:params:传入值 progress :进程 Result:返回值
@Override
protected
List<NewsBean> doInBackground(String... params) {
return
getJsonData(params[
0
]);
//得到从url读取的JSON数据
}
@Override
protected
void
onPostExecute(List<NewsBean> newsBean) {
// 将生成的newsBean设置给ListView
super
.onPostExecute(newsBean);
NewsAdapter adapter=
new
NewsAdapter(MainActivity.
this
, newsBean);
//创建适配器对象
mListView.setAdapter(adapter);
}
}
/**
* 将url对应的json数据转换为我们所封装的NewsBean对象
* @param url
* @return newsList
*/
private
List<NewsBean> getJsonData(String url) {
List<NewsBean> newsBeanList=
new
ArrayList<NewsBean>();
try
{
String jsonString=readStream(
new
URL(url).openStream());
//此句功能与url.openConnection().getInputStream()相同,可根据URL直接联网获取数据,返回值类型 InputStream;
//Log.d("json",jsonString ); 打印读取的json信息
//解析json数据
JSONObject jsonObject;
NewsBean newsBean;
//用于封装jsonObject
jsonObject=
new
JSONObject(jsonString);
//json数据添加到jsonObject中
JSONArray jsonArray=jsonObject.getJSONArray(
"data"
);
//取出json中的data数据,data为一个数组类型
for
(
int
i=
0
;i<jsonArray.length();i++){
//取出data中的数据
jsonObject=jsonArray.getJSONObject(i);
newsBean=
new
NewsBean();
newsBean.imgIconUrl=jsonObject.getString(
"picSmall"
);
newsBean.newsTitle=jsonObject.getString(
"name"
);
newsBean.newsContent=jsonObject.getString(
"description"
);
newsBeanList.add(newsBean);
}
}
catch
(IOException e) {
e.printStackTrace();
}
catch
(JSONException e) {
e.printStackTrace();
}
return
newsBeanList;
}
/**
* 通过inputStream解析网页所返回的数据
* @param is
* @return result
*/
private
String readStream(InputStream is){
InputStreamReader isr;
String result=
""
;
try
{
String line=
""
;
//每行的数据
isr=
new
InputStreamReader(is,
"utf-8"
);
//字节流转换为字符流
BufferedReader br=
new
BufferedReader(isr);
//将字符流以buffer的形式读取出来
while
((line=br.readLine())!=
null
){
result+=line;
//拼接到result中
}
}
catch
(UnsupportedEncodingException e) {
e.printStackTrace();
}
catch
(IOException e) {
e.printStackTrace();
}
return
result;
}
}
思路解析:
1) 一个内部类继承AsyncTask,书写未实现的方法,其中在方法中有一个doBackground()方法,在其方法中书写得到Json数据的方法
2) 书写通过inputStream解析网页所返回的数据,只有拿到网页中的json数据才能实现解析的操作
3)将url对应的json数据转换为我们所封装的NewsBean对象,在这个方法中,我们通过第二步拿到了json数据,然后进行json数据的解析,并且封装到实体类对象中,这样你的实体类中就有解析的json数据了
4)创建ListView的适配器
5)在继承AsyncTask的类中书写onPostExecute()方法,在这个方法中,实现绑定适配器,加载数据源的操作
6)在onCreate方法中执行这个异步操作:new NewAsyncTask().execute(URL);并且传入url地址
7)在清单文件中添加联网权限 <use-permission android:name="android.permission.INTERNET"/>
3-2 实现在ListView中添加网络中图片
&由于加载图片是一种耗时操作,所以我们可以通过新建Thread的方法或者继承AsyncTask来实现
3.2.1:我们通过新线程的方式来实现加载网络图片,新建ImageLoader.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
|
package cn.edu.bzu.async_listview; import java.io.BufferedInputStream; import java.io.IOException; import java.io.InputStream; import java.net.HttpURLConnection; import java.net.URL; import android.os.Message; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.os.Handler; import android.widget.ImageView; /** * 用于处理图片的加载 * @author monster * */ public class ImageLoader { private ImageView mImageView; private String mUrl; /** * UI主线程 */ private Handler mHandler= new Handler(){ public void handleMessage(Message msg) { super .handleMessage(msg); //通过设置tag属性避免缓存图片对正确图片的影响 if (mImageView.getTag().equals(mUrl)){ mImageView.setImageBitmap((Bitmap) msg.obj); } }; }; /** * 通过多线程的方式加载图片 * @param imageView * @param url */ public void showImageByThread(ImageView imageView, final String url){ mImageView=imageView; //将ImageView保存进成员变量中 mUrl=url; new Thread(){ @Override public void run() { super .run(); Bitmap bitmap=getBitmapFromURL(url); Message message=Message.obtain(); message.obj=bitmap; mHandler.sendMessage(message); //将内容发送到Handle线程中 } }.start(); } /** * 通过url得到bitmap * @param urlString * @return bitmap */ public Bitmap getBitmapFromURL(String urlString){ Bitmap bitmap; InputStream is = null ; try { URL url= new URL(urlString); HttpURLConnection connection=(HttpURLConnection) url.openConnection(); //打开链接 //注意是:HttpURLConnection而不是HttpsURLConnection is= new BufferedInputStream(connection.getInputStream()); bitmap=BitmapFactory.decodeStream(is); //将这个流转换为bitmap connection.disconnect(); //资源释放 return bitmap; } catch (java.io.IOException e) { e.printStackTrace(); } finally { try { is.close(); } catch (IOException e) { e.printStackTrace(); } } return null ; } } |
思路解析:
1)新建一个线程来实现加载图片
2)创建加载图片的方法,方法的参数为图片的url,这个url可以通过解析刚才的json数据得到
1HttpURLConnection connection=(HttpURLConnection) url.openConnection();
//打开链接 //注意是:HttpURLConnection而不是HttpsURLConnection
通过这条语句,将连接转化成流,然后得到流,最后将流转换为bitmap对象
3)得到bitmap对象后,我们新建Handler线程,在这个线程中进行图片的更换,由于bitmap在我们的新线程中,所以我们通过handler的消息传递进行将bitmap对象传入到主线程中去
4)由于ListView的缓存机制,所以我们通过在适配器为图片设置tag的方法从而实现图片的正确加载,避免导致图片的来回替换
在Handler中,我们通过通过设置判断tag属性,来判断图片的url是否相等
附录:ListView适配器的代码:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263package
cn.edu.bzu.async_listview;
import
java.util.List;
import
android.content.Context;
import
android.view.LayoutInflater;
import
android.view.View;
import
android.view.ViewGroup;
import
android.widget.BaseAdapter;
import
android.widget.ImageView;
import
android.widget.TextView;
public
class
NewsAdapter
extends
BaseAdapter {
private
List<NewsBean> mList;
private
LayoutInflater mInflater;
public
NewsAdapter(Context context,List<NewsBean> data){
mList=data;
mInflater=LayoutInflater.from(context);
}
@Override
public
int
getCount() {
return
mList.size();
}
@Override
public
Object getItem(
int
position) {
return
mList.get(position);
}
@Override
public
long
getItemId(
int
position) {
return
position;
}
@Override
public
View getView(
int
positon, View convertView, ViewGroup parent) {
ViewHolder viewHolder=
null
;
if
(convertView==
null
){
viewHolder=
new
ViewHolder();
convertView=mInflater.inflate(R.layout.listview_item,
null
);
//布局转化为视图
viewHolder.ivIcon=(ImageView) convertView.findViewById(R.id.iv_icon);
viewHolder.tvTitle=(TextView) convertView.findViewById(R.id.tv_title);
viewHolder.tv_Content=(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(positon).imgIconUrl;
viewHolder.ivIcon.setTag(url);
new
ImageLoader().showImageByThread(viewHolder.ivIcon,url);
//图片id,图片的链接
viewHolder.tvTitle.setText(mList.get(positon).newsTitle);
viewHolder.tv_Content.setText(mList.get(positon).newsContent);
return
convertView;
}
class
ViewHolder{
public
TextView tvTitle,tv_Content;
public
ImageView ivIcon;
}
}
3.2.2 :我们通过使用继承AsyncTask的方法来实现图片的异步加载
首先我们需要创建一个方法:showImageByAsyncTask,并且传入值为ImageView以及图片的url
其次新建一个类继承AsyncTask,这个类为匿名内部类,为ImageLoder中的类:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
private class NewsAsyncTask extends AsyncTask<String, Void, Bitmap>{ private ImageView mImageView; private String mUrl; public NewsAsyncTask(ImageView imageView,String url){ mImageView=imageView; mUrl=url; } @Override protected Bitmap doInBackground(String... params) { return getBitmapFromURL(params[ 0 ]); } @Override protected void onPostExecute(Bitmap bitmap) { super .onPostExecute(bitmap); if (mImageView.getTag().equals(mUrl)){ mImageView.setImageBitmap(bitmap); } } } |
然后仅仅需要修改NewsAdapter中为图片控件赋值的代码即可:
1
2
|
//new ImageLoader().showImageByThread(viewHolder.ivIcon,url); //图片id,图片的链接 --->>使用多线程的方法 new ImageLoader().showImageByAsyncTask(viewHolder.ivIcon,url); //使用继承AsyncTask的方式实现图片的异步加载 |
至此,我们为控件赋值以及异步加载数据的功能已经实现,我们来看下效果:
四、LruCache缓存机制(使用内存空间换取效率)
问题分析:上述的程序中存在一个问题,问题是当用户每次刷新的时候,都需要从网络中读取数据并且进行加载,所以这样增加了用户的流量费用,不利于用户的使用
那么如何提高用户的体验??---->>>>使用缓存
Lru算法:
Lru:Least Recently Used 近期最少使用算法
Android提供了LruCache类来实现这个缓存算法
思路:1.声明LruCache对象
2.在构造方法中实现创建LruCache对象,在这里需要实现它的一个内部方法,用于告诉系统图片的大小,在此之前,你需要得到运行的最大内存,然后用最大内存的一部分
3.添加两个方法:一个将图片加入到缓存,一个从缓存中读取图片
---->>将图片加入到缓存,从网络中读取数据,如果图片不为空,则加入到缓存
----->>从缓存中读取图片,通过键值对的形式读取图片
4.在异步加载图片的使用,首先需要从缓存中读取图片,如果图片为空的话,加载图片,加载出来的图片添加到缓存中
5.在adapter中创建ImageLoader对象,实现添加适配器的时候自动加载缓存机制
附:代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
|
package cn.edu.bzu.async_listview; import java.io.BufferedInputStream; import java.io.IOException; import java.io.InputStream; import java.net.HttpURLConnection; import java.net.URL; import android.os.AsyncTask; import android.os.Message; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.os.Handler; import android.util.LruCache; import android.widget.ImageView; /** * 用于处理图片的加载 * @author monster * */ public class ImageLoader { private ImageView mImageView; private String mUrl; private LruCache<String, Bitmap> mCaches ; //用户图片的缓存 public ImageLoader(){ 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(); //告诉系统,存入的图片的大小 } }; } /** * 把bitmap加入到缓存中 * @param url * @param bitmap */ public void addBitmapToCache(String url,Bitmap bitmap){ if (getBitmapFromCache(url)== null ){ mCaches.put(url, bitmap); } } /** * 把图片从缓存中取出来 * @param url * @return bitmap */ public Bitmap getBitmapFromCache(String url){ return mCaches.get(url); } /** * UI主线程 */ private Handler mHandler= new Handler(){ public void handleMessage(Message msg) { super .handleMessage(msg); //通过设置tag属性避免缓存图片对正确图片的影响 if (mImageView.getTag().equals(mUrl)){ mImageView.setImageBitmap((Bitmap) msg.obj); } }; }; /** * 通过多线程的方式加载图片 * @param imageView * @param url */ public void showImageByThread(ImageView imageView, final String url){ mImageView=imageView; //将ImageView保存进成员变量中 mUrl=url; new Thread(){ @Override public void run() { super .run(); Bitmap bitmap=getBitmapFromURL(url); Message message=Message.obtain(); message.obj=bitmap; mHandler.sendMessage(message); //将内容发送到Handle线程中 } }.start(); } /** * 通过url得到bitmap * @param urlString * @return bitmap */ public Bitmap getBitmapFromURL(String urlString){ Bitmap bitmap; InputStream is = null ; try { URL url= new URL(urlString); HttpURLConnection connection=(HttpURLConnection) url.openConnection(); //打开链接 //注意是:HttpURLConnection而不是HttpsURLConnection is= new BufferedInputStream(connection.getInputStream()); bitmap=BitmapFactory.decodeStream(is); //将这个流转换为bitmap connection.disconnect(); //资源释放 return bitmap; } catch (java.io.IOException e) { e.printStackTrace(); } finally { try { is.close(); } catch (IOException e) { e.printStackTrace(); } } return null ; } /** * 通过AsyncTask的方式异步加载图片 * @param imageView * @param url */ public void showImageByAsyncTask(ImageView imageView,String url){ Bitmap bitmap=getBitmapFromCache(url); //从缓存中取出图片 if (bitmap== null ){ new NewsAsyncTask(imageView,url).execute(url); } else { imageView.setImageBitmap(bitmap); } } private class NewsAsyncTask extends AsyncTask<String, Void, Bitmap>{ private ImageView mImageView; private String mUrl; public NewsAsyncTask(ImageView imageView,String url){ mImageView=imageView; 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); if (mImageView.getTag().equals(mUrl)){ mImageView.setImageBitmap(bitmap); } } } } |
五:源代码下载: