Fork me on GitHub

Android 异步加载

newsBean类:

1
2
3
4
5
6
7
public class NewsBean {

public String newsIconUrl;
public String newsTitle;
public String newsContent;

}

NewsAdapter适配器:

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
158
159
160
161
162
163
164
165
166
167
168
169
170
  /**
Android异步加载
总结
通过异步加载,避免阻塞UI线程
通过LruCache,将已经下载的图片放到内存中=========》也叫一级缓存
通过判断ListView滑动状态,决定何时加载图片
不仅仅是ListView,任何控件都可以使用异步加载
*/

/**
适配器实现 AbsListView.OnScrollListener接口,监听listView的滚动

让ListView滑动时取消所有加载项,滑动停止后才加载所有可见项,以此来提高listView的加载效率
*/
public class NewsAdapter extends BaseAdapter implements AbsListView.OnScrollListener{

private List mList;

private LayoutInflater mInflater;

private Context context;

/**
记录LitView的可见item的起始项下标和可见终止项下标
*/ private int mStart; private int mEnd;

//保存当前获得的图片的URL地址 public static String[] URLS;
/**
判断是不是第一次打开应用 */ private boolean mFirstIn;

//用于使用缓存加载图片的ImageLoader,只能new一次
private ImageLoader mImageLoader;

public NewsAdapter(Context context, List mList, ListView listView){
this.context=context;
this.mList=mList;
mInflater=LayoutInflater.from(context);
mImageLoader=new ImageLoader(listView);

URLS=new String[mList.size()];
for (int i = 0; i <mList.size();i++) {
URLS[i]=mList.get(i).newsIconUrl;
}

/**
* true代表第一次启动
*/
mFirstIn=true;

/**
* 给listView绑定滚动监听事件
*/
listView.setOnScrollListener(this);

}

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

/**

在这里ListView存在缓存机制,可能导致图片加载混乱、跳动,而加载之前缓存在contentView里的图片

解决方法:让URL与指定的ImageView绑定

@param position

@param convertView

@param parent

@return */
@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.mImageView=convertView.findViewById(R.id.image_view);
viewHolder.mTitle=convertView.findViewById(R.id.news_title);
viewHolder.mContent=convertView.findViewById(R.id.news_content);

convertView.setTag(viewHolder);

}else{ viewHolder=(ViewHolder)convertView.getTag(); } viewHolder.mImageView.setImageResource(R.mipmap.ic_launcher);

/**
让URL与指定的ImageView绑定,防止因为convertView的缓存而造成的图片加载混乱 */ String url=mList.get(position).newsIconUrl;

viewHolder.mImageView.setTag(url);

/** *========>用多线程的方法加载图片

这里没有用缓存 / //new ImageLoader().showImageByThread(viewHolder.mImageView, url); /*

<=========通过AsyncTask的方法加载图片

==== 这里用了缓存

=====》用了缓存,ImageLoader不能每次都new一个,这样每次都会new一个LruCache对象,达不到缓存效果 */ mImageLoader.showImageByAsyncTask(viewHolder.mImageView,url);

viewHolder.mTitle.setText(mList.get(position).newsTitle); viewHolder.mContent.setText(mList.get(position).newsContent);

return convertView; }

class ViewHolder{

public TextView mTitle;

public TextView mContent;

public ImageView mImageView;

}

/**
listView状态切换时调用
@param view
@param scrollState 当前ListView的滚动状态 *scrollState:即滑动的状态。分为三种 0,1,2

=0 表示停止滑动的状态 SCROLL_STATE_IDLE

=1表示正在滚动,用户手指在屏幕上 SCROLL_STATE_TOUCH_SCROLL

=2表示正在滑动。用户手指已经离开屏幕 SCROLL_STATE_FLING
scrollState 回调顺序如下:

手指触屏拉动准备滚动,只触发一次 顺序: 1 scrollState = SCROLL_STATE_TOUCH_SCROLL(1):表示正在滚动。当屏幕滚动且用户使用的触碰或手指还在屏幕上时为1

持续滚动开始,只触发一次 顺序: 2 scrollState =SCROLL_STATE_FLING(2) :表示手指做了抛的动作(手指离开屏幕前,用力滑了一下,屏幕产生惯性滑动)。

整个滚动事件结束,只触发一次 顺序: 4 scrollState =SCROLL_STATE_IDLE(0) :表示屏幕已停止。屏幕停止滚动时为0。

*/ //onScrollStateChanged方法在初始化时不会被调用,所以第一次打开应用, //不会加载网络图片,只会加载默认本地图片,所以设置一个tag,判断是否是第一次访问,mFirst,如果是就在onScroll方法中加载网络数据 //第一次打开,listView默认状态没有改变 @Override public void onScrollStateChanged(AbsListView view, int scrollState) { //当前listView处于停止状态 idle:闲置的 if(scrollState==SCROLL_STATE_IDLE){

/**
* 把图片从网络的加载控制权从getView方法转移到这里
*/
mImageLoader.loadImages(mStart,mEnd);
//加载数据可见项
}else {
//处于其他状态,停止任务
mImageLoader.cancelAllTasks();
}

}

/**

listView的整个滑动过程中都会调用

@param view

@param firstVisibleItem 当前窗口中能看见的第一个列表项ID

@param visibleItemCount 当前窗口中能看见的列表项的个数(小半个也算)

@param totalItemCount 列表项的总数 */ //一直在滚动中,多次触发 顺序: 3 //这个方法一直会调用,打开listView就会调用 @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {

mStart=firstVisibleItem; mEnd=firstVisibleItem+visibleItemCount;

/**

判断是不是第一次启动,并且item绘制完成,即可见item大于0,就加载网络数据 */ if (mFirstIn && visibleItemCount>0){

mImageLoader.loadImages(mStart,mEnd); //设置为false,表示不是第一次访问 mFirstIn=false; }

} }

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
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
 public class ImageLoader {

private ImageView mImageView;

private String mUrl;

/**
*使用缓存,增加用户体验,缓解网络加载压力,以内存换效率
* key-value的形式
*
* key:缓存对象的名字
* value:缓存的对象
*
* LruCache底层通过LinkHashMap实现
*/
//创建caches
private LruCache<String,Bitmap> mCaches;

private ListView mListView;
private Set<NewsAsyncTask> mTask;

public ImageLoader(ListView listView){

this.mListView=listView;
this.mTask=new HashSet<>();

/**
* 获取当前可用的最大内存
*/
int maxMemory=(int)Runtime.getRuntime().maxMemory();
/**
* 缓存的大小
*/
int cacheSize=maxMemory/4;

mCaches=new LruCache<String,Bitmap>(cacheSize){
/**
* 这个方法默认返回元素的个数
* 这里要返回每个存进去的对象的大小
* @param key
* @param value
* @return
*/
//这个方法在每次存入内存缓存时调用
@Override
protected int sizeOf(String key, Bitmap value) {
/**
* 返回每个存入内存缓存的bitmap的大小
*/
return value.getByteCount();
}
};
}

/**
* 把Bitmap加入缓存
* @param url
* @param bitmap
*/
public void addBitmapToCache(String url,Bitmap bitmap){

if(getBitmapFromCache(url)==null){
mCaches.put(url,bitmap);
}

}

/**
* 把Bitmap从缓存中取出来
*/
public Bitmap getBitmapFromCache(String url){
return mCaches.get(url);
}


private Handler handler=new Handler(){

@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
Bitmap bitmap=(Bitmap)msg.obj;

/**
* 让URL与指定的ImageView绑定,防止因为convertView的缓存而造成的图片加载混乱
*/
if(mImageView.getTag().equals(mUrl)){

mImageView.setImageBitmap(bitmap);

}
}

};

/**
*===下载图片的方式一====>用多线程的方法加载图片
* >>>>>>>>这个方法没用缓存<<<<<<<<<<
* @param imageView
* @param urlIcon
*/
public void showImageByThread(ImageView imageView,String urlIcon){
mImageView=imageView;
mUrl=urlIcon;
new Thread(new Runnable() {

@Override
public void run() {
Bitmap bitmap=getBitmapFromURL(mUrl);

/**
* 通过这种方式可以获得已经回收的Message,提高Message的使用效率
*/
Message message=Message.obtain();
message.obj=bitmap;
handler.sendMessage(message);
}
}).start();

}

/**
* 获取图片从URL中
* @param urlIcon
* @return
*/
public Bitmap getBitmapFromURL(String urlIcon){
Bitmap bitmap;
InputStream is=null;
BufferedInputStream bis=null;
try {
URL url=new URL(urlIcon);

HttpURLConnection connection=
(HttpURLConnection)url.openConnection();

is=connection.getInputStream();
bis=new BufferedInputStream(is);
bitmap= BitmapFactory.decodeStream(bis);

connection.disconnect();

Thread.sleep(1000);

is.close();
bis.close();

return bitmap;
}catch (MalformedURLException e){
e.printStackTrace();
}catch(IOException e){

}catch(InterruptedException e){

}
return null;
}

/**
*===下载图片的方式二=====>通过AsyncTask的方法加载图片
* 》》》》》》这个方法用了缓存《《《《《《《
* @param imageView
* @param url
*/
public void showImageByAsyncTask(ImageView imageView,String url){

/**
* =====判断缓存中是否有bitmap,有就直接使用,设置给imageView,没有再去下载
*/
//从缓存中取出对应哪个图片
Bitmap bitmap=getBitmapFromCache(url);
if (bitmap==null){
//如果缓存中没有对应图片,就去下载
//new NewsAsyncTask(url).execute(url);
imageView.setImageResource(R.mipmap.ic_launcher);
}else {
imageView.setImageBitmap(bitmap);
}
}

private class NewsAsyncTask extends AsyncTask<String,String,Bitmap>{

//private ImageView mImageView;
private String url;

public NewsAsyncTask(String url){
//this.mImageView=imageView;
this.url=url;
}

@Override
protected Bitmap doInBackground(String... strings) {
/**
* 第一次下载后就把Bitmap保存到缓存中
*/
String url=strings[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(url);

if(imageView!=null&&bitmap!=null){
imageView.setImageBitmap(bitmap);
}
/**
* 移除自己,因为任务已经执行完了
*/
mTask.remove(this);

}
}

/**
* 加载listView停止滚动时的 可见起始项 到 可见终止项 之间的所有item
* @param start
* @param end
*/
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){
//如果缓存中没有对应图片,就去下载
NewsAsyncTask task=new NewsAsyncTask(url);
task.execute(url);
mTask.add(task);
}else {
ImageView imageView=(ImageView)mListView.findViewWithTag(url);
imageView.setImageBitmap(bitmap);
}
}
}

/**
* 取消所有任务
*/
public void cancelAllTasks(){
if (mTask!=null){
for (NewsAsyncTask task:mTask) {
/**
* cancel(true),还是 cancel(false),调用之后 isCancelled() 都返回 true
* AsyncTask 的 cancel 方法需要一个布尔值的参数,参数名为 mayInterruptIfRunning,
* 意思是如果正在执行是否可以打断,如果这个值设置为 true ,
* 表示这个任务可以被打断,否则,正在执行的程序会继续执行直到完成。
* 如果在 doInBackground()方法中有一个循环操作,
* 我们应该在循环中使用 isCancelled()来判断,如果返回为 true ,我们应该避免执行后续无用的循环操作。
*/
task.cancel(false);
}
}
}

}