研究了一天,终于达到了自己想要学习的目的:缓存图片+异步加载,
搜了大量资料见的大多避免oom的方法有压缩和缓存,这里也采用这2种方法吧。
压缩就不用我说了,缓存图片我用的LruCache这个类,本身已经实现了同步,这里就不再多说什么了,不知道的同学可以去研究下,这里主要想讲的的异步加载的时机。在这里写下也只是分享下我的体验,欢迎拍砖~~~
大家做过这个的都碰到过,快速滑动时由于大量异步加载和message消息的等待排队,当快速滑动停止时要等好一会才能轮到当前可视item图片的加载显示,如何避免这个问题?
那就从问题来源入手:快速滑过的那些item图片可以先暂不加载,等到用户正常划过时再去加载不迟~~~
我的代码思路: 在getView方法里面判断Listview的滑动状态,如果正在滑动,则不加载,但要保存此时的图片信息,等到适当机会再去加载,否则异步加载图片~~~
实现Listview状态的标识,代码:
[mw_shl_code=java,true]listView.setOnScrollListener(new OnScrollListener() {
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
//标识正在滑动中
if(scrollState==OnScrollListener.SCROLL_STATE_FLING){
isBusy=true;
}else {
isBusy=false;
asyncLoading();
}
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
firstItem=firstVisibleItem;
bottmItem=firstItem+visibleItemCount;
}
});
}[/mw_shl_code]
isBusy用来标识Listview是否处在滑动状态,asyncLoading()方法下面会说到
现在知道了当前listview的滚动状态,那么现在就开始在getview里面加载资源吧:
[mw_shl_code=java,true]
Bitmap bitmap=cache.get(""+position);//取出缓存中的bitmap
final ViewHolder holder;
if(convertView==null){
holder=new ViewHolder();
convertView=getLayoutInflater().inflate(R.layout.item, null);
holder.txt=(TextView) convertView.findViewById(R.id.txt);
holder.img=(ImageView) convertView.findViewById(R.id.img);
convertView.setTag(holder);
}else {
holder=(ViewHolder) convertView.getTag();
}
if(bitmap==null){//如果没有缓存
if(!isBusy){//判断当前listview不在滑动状态
Log.d("==========", "正常加载当前位置图片:"+position);
executor.execute(new Runnable() {//异步加载图片
@Override
public void run() {
Bitmap bitmap=DownLoadImage();//模拟下载图片
if(bitmap!=null){
cache.put(""+position, bitmap);
Message message=handler.obtainMessage();
message.obj=holder.img;
message.arg1=position;
message.what=LOADIMAGE;
handler.sendMessage(message);//图片下载完成后通知更新
}
}
});
}
else {//如果当前listview在快速滑动状态
Message message=handler.obtainMessage();
message.obj=holder.img;
message.arg1=position;
message.what=LOADIMAGE;
messages.add(message);///记录当前getview的图片资源信息,保存在message中,放入到集合等待加载
}
holder.img.setImageBitmap(defaultBitmap);//设置默认图片,等图片资源下载完毕后在更新
}else {
holder.img.setImageBitmap(bitmap);
}
holder.txt.setText(position+"");
return convertView;
[/mw_shl_code]
代码的注释很清楚了,我在这里再说下,如果当前listview不在滑动状态时,正常加载图片,否则就记录当前的图片信息,存放到一个集合中,等待适当的机会去加载,什么时候适当呢?那就是listview不在滑动的时候, 即isbusy=false时。
那回过头来看上面出现过的asyncLoading()这个方法吧
[mw_shl_code=java,true]private void asyncLoading(){
int size=messages.size();
for(int i=size-1;i>=0;i--){//从集合的末尾开始遍历
final Message msg=messages.get(i);
if(msg.arg1>=firstItem-1&&msg.arg1<bottmItem+1){//取出当前的position(也就是msg.arg1),判断该position是否在当前可视位置,(如果不要这个判断的话,则加载所有先前滑动过去的item的图片)
Log.e("==========", "滑动结束,手动加载当前位置图片:"+msg.arg1);
cache.put(msg.arg1+"", defaultBitmap);//这边一定要将position事先存放进去,占个位置,表明该位置已经有图片在下载了,不然可能会出现重复下载的情况
FILLINGexecutor.execute(new Runnable() {
@Override
public void run() {
Bitmap bitmap=DownLoadImage();
if(bitmap!=null){
cache.put(msg.arg1+"", bitmap);//下载完成后,再将defaultBitmap图片覆盖
handler.sendMessageAtFrontOfQueue(msg);//放到消息队前,因为要最先加载当前可视的item图片资源
}
}
});
}else {
break;//跳出循环(只要当前不满足,之后的也不会满足)
}
}
messages.clear();//清空消息
}[/mw_shl_code]
注释很清楚了,也就不再说了。
总之,demo实现了这样一个效果, 每个item的图片都属于自己,没有重用(因为同一张图片每次都是重新加载到内存的),当正常滑动listview时,则正常加载图片,当快速滑动时,只加载listview停下后可视item的图片,这就避免了等待啦,因为之前被滑过item的图片没有被异步加载哦~~欢迎提出意见,大家一起学习~~~
当然,真正的应用是不只在getview方法里面去加载的,因为滑动过后才会去加载,体验效果会很差,这里只是学习用
存在错位bug:
你会发现有时同一张图片会连续变动1、2次,甚至更多次,那是因为convertView缓存的缘故,
比如消息队列中有消息要通知刷新position=10,16,22这3个图片的位置,而这三个item恰好使用同一个convertView,所以他们也是使用的同一个ImageVIew
此时position=22的item是可视的
那么这个ImageVIew.setImageBitmap()的方法会被连续调用3次
导致你们会看到图片连续变动
这个bug在本demo中很好解决,2个方法:
1:就是判断当前可视的item位置再去设
2:在手动发送message之前清空消息队列(这种方法适合于一定要是非常快速滑动的那种,因为listview稍微滑下就会处于busy状态,此时正常加载的position还是可视的)
提出问题:如果一开始就去加载全部图片后缓存,而不是在getview里面开线程去加载图片,那么如何通知到对应的图片UI更新以及错位呢?这过程会发生什么问题?你可以亲自动手实现下,提醒下加载图片是耗时的哦~~~
搜了大量资料见的大多避免oom的方法有压缩和缓存,这里也采用这2种方法吧。
压缩就不用我说了,缓存图片我用的LruCache这个类,本身已经实现了同步,这里就不再多说什么了,不知道的同学可以去研究下,这里主要想讲的的异步加载的时机。在这里写下也只是分享下我的体验,欢迎拍砖~~~
大家做过这个的都碰到过,快速滑动时由于大量异步加载和message消息的等待排队,当快速滑动停止时要等好一会才能轮到当前可视item图片的加载显示,如何避免这个问题?
那就从问题来源入手:快速滑过的那些item图片可以先暂不加载,等到用户正常划过时再去加载不迟~~~
我的代码思路: 在getView方法里面判断Listview的滑动状态,如果正在滑动,则不加载,但要保存此时的图片信息,等到适当机会再去加载,否则异步加载图片~~~
实现Listview状态的标识,代码:
[mw_shl_code=java,true]listView.setOnScrollListener(new OnScrollListener() {
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
//标识正在滑动中
if(scrollState==OnScrollListener.SCROLL_STATE_FLING){
isBusy=true;
}else {
isBusy=false;
asyncLoading();
}
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
firstItem=firstVisibleItem;
bottmItem=firstItem+visibleItemCount;
}
});
}[/mw_shl_code]
isBusy用来标识Listview是否处在滑动状态,asyncLoading()方法下面会说到
现在知道了当前listview的滚动状态,那么现在就开始在getview里面加载资源吧:
[mw_shl_code=java,true]
Bitmap bitmap=cache.get(""+position);//取出缓存中的bitmap
final ViewHolder holder;
if(convertView==null){
holder=new ViewHolder();
convertView=getLayoutInflater().inflate(R.layout.item, null);
holder.txt=(TextView) convertView.findViewById(R.id.txt);
holder.img=(ImageView) convertView.findViewById(R.id.img);
convertView.setTag(holder);
}else {
holder=(ViewHolder) convertView.getTag();
}
if(bitmap==null){//如果没有缓存
if(!isBusy){//判断当前listview不在滑动状态
Log.d("==========", "正常加载当前位置图片:"+position);
executor.execute(new Runnable() {//异步加载图片
@Override
public void run() {
Bitmap bitmap=DownLoadImage();//模拟下载图片
if(bitmap!=null){
cache.put(""+position, bitmap);
Message message=handler.obtainMessage();
message.obj=holder.img;
message.arg1=position;
message.what=LOADIMAGE;
handler.sendMessage(message);//图片下载完成后通知更新
}
}
});
}
else {//如果当前listview在快速滑动状态
Message message=handler.obtainMessage();
message.obj=holder.img;
message.arg1=position;
message.what=LOADIMAGE;
messages.add(message);///记录当前getview的图片资源信息,保存在message中,放入到集合等待加载
}
holder.img.setImageBitmap(defaultBitmap);//设置默认图片,等图片资源下载完毕后在更新
}else {
holder.img.setImageBitmap(bitmap);
}
holder.txt.setText(position+"");
return convertView;
[/mw_shl_code]
代码的注释很清楚了,我在这里再说下,如果当前listview不在滑动状态时,正常加载图片,否则就记录当前的图片信息,存放到一个集合中,等待适当的机会去加载,什么时候适当呢?那就是listview不在滑动的时候, 即isbusy=false时。
那回过头来看上面出现过的asyncLoading()这个方法吧
[mw_shl_code=java,true]private void asyncLoading(){
int size=messages.size();
for(int i=size-1;i>=0;i--){//从集合的末尾开始遍历
final Message msg=messages.get(i);
if(msg.arg1>=firstItem-1&&msg.arg1<bottmItem+1){//取出当前的position(也就是msg.arg1),判断该position是否在当前可视位置,(如果不要这个判断的话,则加载所有先前滑动过去的item的图片)
Log.e("==========", "滑动结束,手动加载当前位置图片:"+msg.arg1);
cache.put(msg.arg1+"", defaultBitmap);//这边一定要将position事先存放进去,占个位置,表明该位置已经有图片在下载了,不然可能会出现重复下载的情况
FILLINGexecutor.execute(new Runnable() {
@Override
public void run() {
Bitmap bitmap=DownLoadImage();
if(bitmap!=null){
cache.put(msg.arg1+"", bitmap);//下载完成后,再将defaultBitmap图片覆盖
handler.sendMessageAtFrontOfQueue(msg);//放到消息队前,因为要最先加载当前可视的item图片资源
}
}
});
}else {
break;//跳出循环(只要当前不满足,之后的也不会满足)
}
}
messages.clear();//清空消息
}[/mw_shl_code]
注释很清楚了,也就不再说了。
总之,demo实现了这样一个效果, 每个item的图片都属于自己,没有重用(因为同一张图片每次都是重新加载到内存的),当正常滑动listview时,则正常加载图片,当快速滑动时,只加载listview停下后可视item的图片,这就避免了等待啦,因为之前被滑过item的图片没有被异步加载哦~~欢迎提出意见,大家一起学习~~~
当然,真正的应用是不只在getview方法里面去加载的,因为滑动过后才会去加载,体验效果会很差,这里只是学习用
存在错位bug:
你会发现有时同一张图片会连续变动1、2次,甚至更多次,那是因为convertView缓存的缘故,
比如消息队列中有消息要通知刷新position=10,16,22这3个图片的位置,而这三个item恰好使用同一个convertView,所以他们也是使用的同一个ImageVIew
此时position=22的item是可视的
那么这个ImageVIew.setImageBitmap()的方法会被连续调用3次
导致你们会看到图片连续变动
这个bug在本demo中很好解决,2个方法:
1:就是判断当前可视的item位置再去设
2:在手动发送message之前清空消息队列(这种方法适合于一定要是非常快速滑动的那种,因为listview稍微滑下就会处于busy状态,此时正常加载的position还是可视的)
提出问题:如果一开始就去加载全部图片后缓存,而不是在getview里面开线程去加载图片,那么如何通知到对应的图片UI更新以及错位呢?这过程会发生什么问题?你可以亲自动手实现下,提醒下加载图片是耗时的哦~~~