当在ListView或GridView中要加载很多图片时,很容易出现滑动时的卡顿现象,以及出现OOM导致FC(Force Close)。

会出现卡顿现象主要是因为加载数据慢,要等数据加载完才能显示出来。可以通过将数据分页显示,以及将耗时的图片加载用异步的方式和图片缓存,这样就可以解决卡顿的问题。

大部分开发者在ListView或GridView加载图片时,都会在getView方法里创建新的线程去异步加载图片。然而,当屏幕快速向下滑动时,每个划过的Item都会调用getView一次,即会创建出很多线程,同一时间存在的线程太多,内存不够用了,自然就会OOM了。要避免OOM,就得控制好线程的数量,所以加个线程池就非常有必要了。

另外,当向下快速滑动屏幕时,也没必要加载滑动过的所有图片,只要加载滑动停止后当前屏幕的就足够了。仔细观察像微博、facebook或其他优秀的app,滑动屏幕时未加载过的图片是不会被加载的,当滑动停止后,也只加载当前屏幕内的图片。

那么,接下来就讨论实现的问题了。首先,图片是需要缓存的,前一篇文章已经对图片缓存做了总结(Android技术积累:图片缓存管理),直接拿过来用就行。然后,线程池维护多少个线程比较合适呢?这个很难界定,线程太少CPU不能得到充分利用,线程太多会降低性能,也加大了OOM的风险。线程池的最佳大小取决于可用处理器的数目以及工作队列中的任务的性质。若在一个具有N个处理器的系统上只有一个工作队列,其中全部是计算性质的任务,在线程池具有N或N+1个线程时一般会获得最大的CPU利用率。

建立线程池的代码如下:

// 获取当前系统的CPU数目
int cpuNums = Runtime.getRuntime().availableProcessors();
//根据系统资源情况灵活定义线程池大小
ExecutorService executorService = Executors.newFixedThreadPool(cpuNums + 1);

从内存缓存读取图片是非常快的,如果内存缓存中有图片就可以直接获取,而不需要另起线程去异步加载,在内存缓存获取不到时才往线程池里添加新线程去加载图片。既然是异步的,那就要知道获取到的图片是要加载到哪个ImageView,可以将ImageView保存起来。另外,为了保证在整个应用中只有一个线程池,也不会出现多份缓存,图片加载的工具类最好用单例模式。ListView或GridView滑动时不加载图片,滑动停止后才加载图片,因此加一个是否允许加载图片的boolean变量。ListView或GridView初始化时是不滑动的,但也要加载图片,所以boolean值变量初始应该为true。

直接看图片加载的工具类ImageLoader的完整代码:

public class ImageLoader {

    private static ImageLoader instance;

    private ExecutorService executorService;   //线程池
private ImageMemoryCache memoryCache; //内存缓存
private ImageFileCache fileCache; //文件缓存
private Map<String, ImageView> taskMap; //存放任务
private boolean allowLoad = true; //是否允许加载图片 private ImageLoader(Context context) {
// 获取当前系统的CPU数目
int cpuNums = Runtime.getRuntime().availableProcessors();
//根据系统资源情况灵活定义线程池大小
this.executorService = Executors.newFixedThreadPool(cpuNums + 1); this.memoryCache = new ImageMemoryCache(context);
this.fileCache = new ImageFileCache();
this.taskMap = new HashMap<String, ImageView>();
} /**
* 使用单例,保证整个应用中只有一个线程池和一份内存缓存和文件缓存
*/
public static ImageLoader getInstance(Context context) {
if (instance == null)
instance = new ImageLoader(context);
return instance;
} /**
* 恢复为初始可加载图片的状态
*/
public void restore() {
this.allowLoad = true;
} /**
* 锁住时不允许加载图片
*/
public void lock() {
this.allowLoad = false;
} /**
* 解锁时加载图片
*/
public void unlock() {
this.allowLoad = true;
doTask();
} /**
* 添加任务
*/
public void addTask(String url, ImageView img) {
//先从内存缓存中获取,取到直接加载
Bitmap bitmap = memoryCache.getBitmapFromCache(url);
if (bitmap != null) {
img.setImageBitmap(bitmap);
} else {
synchronized (taskMap) {
/**
* 因为ListView或GridView的原理是用上面移出屏幕的item去填充下面新显示的item,
* 这里的img是item里的内容,所以这里的taskMap保存的始终是当前屏幕内的所有ImageView。
*/
img.setTag(url);
taskMap.put(Integer.toString(img.hashCode()), img);
}
if (allowLoad) {
doTask();
}
}
} /**
* 加载存放任务中的所有图片
*/
private void doTask() {
synchronized (taskMap) {
Collection<ImageView> con = taskMap.values();
for (ImageView i : con) {
if (i != null) {
if (i.getTag() != null) {
loadImage((String) i.getTag(), i);
}
}
}
taskMap.clear();
}
} private void loadImage(String url, ImageView img) {
this.executorService.submit(new TaskWithResult(new TaskHandler(url, img), url));
} /*** 获得一个图片,从三个地方获取,首先是内存缓存,然后是文件缓存,最后从网络获取 ***/
private Bitmap getBitmap(String url) {
// 从内存缓存中获取图片
Bitmap result = memoryCache.getBitmapFromCache(url);
if (result == null) {
// 文件缓存中获取
result = fileCache.getImage(url);
if (result == null) {
// 从网络获取
result = ImageGetFromHttp.downloadBitmap(url);
if (result != null) {
fileCache.saveBitmap(result, url);
memoryCache.addBitmapToCache(url, result);
}
} else {
// 添加到内存缓存
memoryCache.addBitmapToCache(url, result);
}
}
return result;
} /*** 子线程任务 ***/
private class TaskWithResult implements Callable<String> {
private String url;
private Handler handler; public TaskWithResult(Handler handler, String url) {
this.url = url;
this.handler = handler;
} @Override
public String call() throws Exception {
Message msg = new Message();
msg.obj = getBitmap(url);
if (msg.obj != null) {
handler.sendMessage(msg);
}
return url;
}
} /*** 完成消息 ***/
private class TaskHandler extends Handler {
String url;
ImageView img; public TaskHandler(String url, ImageView img) {
this.url = url;
this.img = img;
} @Override
public void handleMessage(Message msg) {
/*** 查看ImageView需要显示的图片是否被改变 ***/
if (img.getTag().equals(url)) {
if (msg.obj != null) {
Bitmap bitmap = (Bitmap) msg.obj;
img.setImageBitmap(bitmap);
}
}
}
} }

有一点需要注意,要保证taskMap保存的始终只是当前屏幕内的所有ImageView,在ImageAdapter的getView方法里必须使用ViewHolder模式,这样才能保证item被重用时相应的ImageView也被重用。getView代码类似如下:

@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null) {
convertView = mInflater.inflate(R.layout.list_item, null);
holder = new ViewHolder();
holder.text = (TextView) convertView.findViewById(R.id.text);
holder.image = (ImageView) convertView.findViewById(R.id.img);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
ListItem item = mItems.get(position); //ListView的Item
holder.text.setText(item.getText());
holder.image.setImageResource(R.drawable.default_img); //设置默认图片
mImageLoader.addTask(item.getImgUrl(), holder.image); //添加任务
return convertView;
} static class ViewHolder {
TextView text;
ImageView image;
}

ListView或GridView滑动时就需要锁住不允许加载图片,滑动停止后解锁加载图片。因此,给ListView或GridView添加一个OnScrollListener,代码如下:

mImageLoader = ImageLoader.getInstance(context);
mListView.setOnScrollListener(onScrollListener); OnScrollListener onScrollListener = new OnScrollListener() {
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
switch (scrollState) {
case OnScrollListener.SCROLL_STATE_FLING:
mImageLoader.lock();
break;
case OnScrollListener.SCROLL_STATE_IDLE:
mImageLoader.unlock();
break;
case OnScrollListener.SCROLL_STATE_TOUCH_SCROLL:
mImageLoader.lock();
break;
default:
break;
}
} @Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
}
};

至此,所有关键代码就全都列出来了。

异步加载图片,关键就在于三点:1、缓存;2、线程池;3、只加载当前屏幕

(转)Android技术积累:图片异步加载的更多相关文章

  1. Android图片异步加载之Android-Universal-Image-Loader

    将近一个月没有更新博客了,由于这段时间以来准备毕业论文等各种事务缠身,一直没有时间和精力沉下来继续学习和整理一些东西.最近刚刚恢复到正轨,正好这两天看了下Android上关于图片异步加载的开源项目,就 ...

  2. [置顶] Android图片异步加载之Android-Universal-Image-Loader

    将近一个月没有更新博客了,由于这段时间以来准备毕业论文等各种事务缠身,一直没有时间和精力沉下来继续学习和整理一些东西.最近刚刚恢复到正轨,正好这两天看了下Android上关于图片异步加载的开源项目,就 ...

  3. Android图片异步加载框架Android-Universal-Image-Loader

    版权声明:本文为博主原创文章,未经博主允许不得转载. Android-Universal-Image-Loader是一个图片异步加载,缓存和显示的框架.这个框架已经被很多开发者所使用,是最常用的几个 ...

  4. Android ListView 图片异步加载和图片内存缓存

    开发Android应用经常需要处理图片的加载问题.因为图片一般都是存放在服务器端,需要联网去加载,而这又是一个比较耗时的过程,所以Android中都是通过开启一个异步线程去加载.为了增加用户体验,给用 ...

  5. Android 图片异步加载的体会,SoftReference已经不再适用

      在网络上搜索Android图片异步加载的相关文章,目前大部分提到的解决方案,都是采用Map<String, SoftReference<Drawable>>  这样软引用的 ...

  6. Android图片异步加载之Android-Universal-Image-Loader(转)

    今天要介绍的是Github上一个使用非常广泛的图片异步加载库Android-Universal-Image-Loader,该项目的功能十分强大,可以说是我见过的目前功能最全.性能最优的图片异步加载解决 ...

  7. Android新浪微博客户端(七)——ListView中的图片异步加载、缓存

    原文出自:方杰|http://fangjie.info/?p=193转载请注明出处 最终效果演示:http://fangjie.sinaapp.com/?page_id=54 该项目代码已经放到git ...

  8. Android图片异步加载

    原:http://www.cnblogs.com/angeldevil/archive/2012/09/16/2687174.html 相关:https://github.com/nostra13/A ...

  9. 开源的Android开发框架-------PowerFramework使用心得(二)图片异步加载ImageTask

    图片异步加载.可以备注图片是否缓存.缓存状态. 1.缓存-SD卡,路径可设置 2.图片压缩 3.可加载本地和网络图片 4.url为本地视频文件可以显示缩略图 5.中文url图片地址FileNotFou ...

随机推荐

  1. MSSQL为单独数据库创建登录账户

    如果要为一个数据库创建一个独立的账号需要这个数据库为包含数据库 当前(非包含)的数据库所面临的问题在描述什么是包含数据库之前,先了解一下为什么会出现包含数据库.当前的数据库有一些问题,如下:1.在数据 ...

  2. node-java模块

    node-java模块 node-java使得开发人员,可以调用java优秀的jar包资源.有些方法逻辑,可能node不容易实现,但是java就可以很方便去做.这个时候,就可以使用node-java这 ...

  3. CentOS7单机部署lamp环境和apache虚拟主机

    (1)apache介绍 apache : httpd.apache.org 软件包:httpd 端口服务:80/tcp(http) 443/tcp(https,http+ssl) 配置文件: /etc ...

  4. mongo connect BI 连接至Power BI

    第一步:安装mongodb服务 官网地址:https://www.mongodb.com/download-center?jmp=nav#community mongodb的安装请参考:http:// ...

  5. TCP连接复用

    转自网络:看到一陌生名词,记录一下 TCP连接复用技术通过将前端多个客户的HTTP请求复用到后端与服务器建立的一个TCP连接上.这种技术能够大大减小服务器的性能负载,减少与服务器之间新建TCP连接所带 ...

  6. Mac OS X系统下的Android环境变量配置

    在Mac下开发Android,要想在终端利用命令行使用adb/android等命令时,需要配置一下环境变量. 步骤: 1.首先,假设你已经下载了Android SDK,解压后安装了adb.记住sdk文 ...

  7. 《深入理解Android2》读书笔记(二)

    接之前那篇<深入理解Android2>读书笔记(一) 下面几篇来分别详细分析 Binder类作为服务端的Bn的代表,BinderProxy类作为客户端的Bp的代表,BinderIntern ...

  8. 【转载】RecyclerView使用全解析

    崇拜下鸿洋大神,原文地址:http://blog.csdn.net/lmj623565791/article/details/45059587 概述 RecyclerView出现已经有一段时间了,相信 ...

  9. Eclipse内嵌的webservice客户端

    概述 Eclipse内嵌的webservice客户端,可用于发起请求,查看结果,展示请求和响应的报文. 详情 在Java EE视图,可以看到内嵌的webservice客户端浏览器登陆按钮 点击打开浏览 ...

  10. js中ajax的异步性

    最近项目里遇到ajax异步性的问题,简化后的代码如下: function ajaxGetEvents(calendarView, time) { var year = time.getFullYear ...