论坛首页 移动开发技术论坛

ListView异步加载网络图片之三

浏览 10032 次
精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
作者 正文
   发表时间:2012-10-07  
ListView异步加载网络图片之完美解决方案

问题描述:上一篇文章中解决了一个图片显示混乱的bug,但是还遗留下来一个更严重的bug,那就是当我们猛地拖动列表的时候,会感觉非常的卡顿,并且继续不顾一切的拖动程序就会崩溃,看一下抛出的异常是RejectedExecutionException


错误的原因:由于异步加载图片用的是AsyncTask(异步任务),AsyncTask的内容实现是采用的一个线程池,池子的的最大容量是128,然而连续的滚动ListView触发了getView的调用,在getView中又去创建并执行了异步任务,所以就会导致线程池满了,看一下AsyncTask的源码
//池子的最大容量
private static final int MAXIMUM_POOL_SIZE = 128;
//池子
private static final ThreadPoolExecutor sExecutor = new ThreadPoolExecutor(
			CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE, TimeUnit.SECONDS,
			sWorkQueue, sThreadFactory);

从AysncTask的execute方法中调用线程数的方法sExecutor.execute(mFuture),下面就是线程池的execute方法,在这个方法中由于线程池满了,所以命名会被拒绝执行也就会调用reject(command) 最终将会抛出RejectedExecutionException异常。
 public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        int c = ctl.get();
        if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        if (isRunning(c) && workQueue.offer(command)) {
            int recheck = ctl.get();
            if (! isRunning(recheck) && remove(command))
                reject(command);
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
        else if (!addWorker(command, false))
            reject(command);
    }

追踪到抛出异常的代码是在AbortPolicy类,AbortPolicy实现了RejectedExecutionHandler接口因此实现了rejectedExecution方法
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            // BEGIN android-changed
            //     provide diagnostic messaging for a common exception
            // a message is helpful even if it isn't created atomically
            int queueSize = e.getQueue().size();
            int remainingCapacity = e.getQueue().remainingCapacity();
            String message = "pool=" + e.getPoolSize() + "/" + e.maximumPoolSize
                    + ", queue=" + queueSize;
            if (remainingCapacity != Integer.MAX_VALUE) {
                message += "/" + (queueSize + remainingCapacity);
            }
            throw new RejectedExecutionException(message);

            // END android-changed
        }

解决的方法:产生问题的原因是执行了太多的异步任务,所以解决问题的方法就是,减少异步任务的创建,那么如何减少呢?我们知道ListView在的滚动有3个状态
  • SCROLL_STATE_FLING(惯性滚动)
  • SCROLL_STATE_IDLE(空闲)
  • SCROLL_STATE_TOUCH_SCROLL(拖动)
当ListView在处于SCROLL_STATE_FLING状态的时候由于是快速的从屏幕上面飞过,其实没有必要创建异步任务下载图片,只有当ListView处于低速的拖动状态或者是空闲状态的时候才去创建异步任务,这样就会大大的降低异步任务的数量。
那么可以在adapter中通过一个变量mBusy来控制adapter的状态当mBusy==false的时候才去创建异步任务,并且我们给ListView设置一个setOnScrollListener,在监听器的回调函数中改变adapte的mBusy的值,达到控制是否创建异步任务的作用

OnScrollListener mScrollListener = new OnScrollListener() {

		@Override
		public void onScrollStateChanged(AbsListView view, int scrollState) {
			switch (scrollState) {
			case OnScrollListener.SCROLL_STATE_FLING:
				adapter.setFlagBusy(true);
				break;
			case OnScrollListener.SCROLL_STATE_IDLE:
				adapter.setFlagBusy(false);
				break;
			case OnScrollListener.SCROLL_STATE_TOUCH_SCROLL:
				adapter.setFlagBusy(false);
				break;
			default:
				break;
			}
			adapter.notifyDataSetChanged();
		}

		@Override
		public void onScroll(AbsListView view, int firstVisibleItem,
				int visibleItemCount, int totalItemCount) {

		}
	};

@Override
	public View getView(int position, View convertView, ViewGroup parent) {
		Log.d(TAG, "position=" + position + ",convertView=" + convertView);
		ViewHolder viewHolder = null;
		if (convertView == null) {
			convertView = LayoutInflater.from(mContext).inflate(
					R.layout.list_item, null);// 这个过程相当耗时间
			viewHolder = new ViewHolder();
			viewHolder.mTextView = (TextView) convertView
					.findViewById(R.id.tv_tips);
			viewHolder.mImageView = (ImageView) convertView
					.findViewById(R.id.iv_image);
			convertView.setTag(viewHolder);
		} else {
			viewHolder = (ViewHolder) convertView.getTag();
		}
		String url = "";
		url = URLS[position % URLS.length];
		if (!mBusy) {
			mImageLoader.loadImage(url, this, viewHolder);
			viewHolder.mTextView.setText("--" + position
					+ "--IDLE ||TOUCH_SCROLL");
		} else {
			Bitmap bitmap = mImageLoader.getBitmapFromCache(url);
			if (bitmap != null) {
				viewHolder.mImageView.setImageBitmap(bitmap);
			} else {
				viewHolder.mImageView.setImageResource(R.drawable.ic_launcher);
			}
			viewHolder.mTextView.setText("--" + position + "--FLING");
		}
		return convertView;
	}

总结:本文对前一个demo遗留的bug做了讲解并解决,在此基础上还对程序做了进一步的优化,这次的demo通过双缓冲技术对数据进行缓存,代码已经上传,同学们可以自己下载下来研究一下双缓冲技术。我在下一篇博客中会对双缓冲的代码进行详细的分析,谢谢您看到了博客的结尾
  • 大小: 10.4 KB
   发表时间:2012-10-09  
滑动的时候也最好不要异步加载
0 请登录后投票
   发表时间:2012-10-10  
clarkhuang 写道
滑动的时候也最好不要异步加载

在下一篇文章中已经这样做了,只有ListView是idle的时候才异步请求的嘿嘿,我写博客是循序渐进的,卖关子了
0 请登录后投票
   发表时间:2012-12-25  
下篇文章的链接应该给一下
0 请登录后投票
   发表时间:2012-12-26  
楼主,下一篇博客的链接地址请给一下!谢谢
0 请登录后投票
   发表时间:2012-12-29  
xzch 写道
楼主,下一篇博客的链接地址请给一下!谢谢

  你看看这个:http://heisedeyueya.iteye.com/blog/1757010
0 请登录后投票
论坛首页 移动开发技术版

跳转论坛:
Global site tag (gtag.js) - Google Analytics