alighters

程序、写作、人生

ListView图片加载讲解

| Comments

在ListView加载图片的时候,我们就会关于这里的图片加载产生一些问题。另外,我们知道针对ListView加载图片常用的优化方案之一就是,在列表的滑动过程中,暂停图片的加载任务,在滑动结束之后,则继续之前的任务。所以我们针对这些问题进行一些探讨研究(以UIL图片加载为例)。

  • 知识点1:加载图片的线程的控制; 我们知道在ListView的adapter的getView方法中,进行图片的获取,这里为执行LoadAndDisplayImageTask,这是一个单独的线程去加载图片。但我们不会对每个View加载图片都去执行一个新的线程,所以这里采用的是一个线程池来控制线程的数目,来避免频繁地创建线程。

  • 知识点2:在ListView的滑动过程中,是如何暂停当前的图片下载的任务的; 由于这里我们采用的是单独的线程来控制图片的加载显示的,针对线程,我们使用的是加锁来使线程阻塞,当ListView滑动结束,则使其恢复。 在UIL中,锁声明的代码如下:

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
   private final AtomicBoolean paused = new AtomicBoolean(false);

   private final Object pauseLock = new Object();

  /**
  * Pauses engine. All new "load&display" tasks won't be executed until ImageLoader is {@link #resume() resumed}.Already running tasks are not paused.
  */
  void pause() {
      paused.set(true);
  }

  /** Resumes engine work. Paused "load&display" tasks will continue its work. */
  void resume() {
      paused.set(false);
      synchronized (pauseLock) {
          pauseLock.notifyAll();
      }
  }

  AtomicBoolean getPause() {
      return paused;
  }

  Object getPauseLock() {
      return pauseLock;
  }

在LoadAndDisplayImageTask的方法中,执行图片的加载的时候,会进行pause的判断。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
     /** @return <b>true</b> - if task should be interrupted; <b>false</b> - otherwise */
  private boolean waitIfPaused() {
      AtomicBoolean pause = engine.getPause();
      if (pause.get()) {
          synchronized (engine.getPauseLock()) {
              if (pause.get()) {
                  L.d(LOG_WAITING_FOR_RESUME, memoryCacheKey);
                  try {
                      engine.getPauseLock().wait();
                  } catch (InterruptedException e) {
                      L.e(LOG_TASK_INTERRUPTED, memoryCacheKey);
                      return true;
                  }
                  L.d(LOG_RESUME_AFTER_PAUSE, memoryCacheKey);
              }
          }
      }
      return isTaskNotActual();
  }

另外,pause跟resume的调用,则是在UIL的PauseOnScrollListener进行滑动判断的调用。所以可以明显的看出是通过object的wait跟notifyAll实现线程同步,来达到暂停图片加载的任务的。

  • 知识点3:当一个Item的view滑出屏幕外,应该怎么处理其对应的图片加载任务: 我们先要判断的是怎么确定一个view已经滑出屏幕之外了呢?这里有个取巧的办法就是判断view是否重用了,因为ListView显示界面所需要的ItemView是固定的(确保当前ListView是采用convertView的缓存机制),而在实现图片显示加载的时候,每个view是要绑定当前加载的图片的链接的,如果当前任务(是指当前View显示加载图片的线程)的图片链接与View要显示的图片链接不一致,则可以判定当前view已经重用了。所以则可以结束当前的显示图片的任务了,具体判断View重用的代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
   /** @return <b>true</b> - if current ImageAware is reused for displaying another image; <b>false</b> - otherwise */
  private boolean isViewReused() {
      String currentCacheKey = engine.getLoadingUriForView(imageAware);
      // Check whether memory cache key (image URI) for current ImageAware is actual.
      // If ImageAware is reused for another task then current task should be cancelled.
      boolean imageAwareWasReused = !memoryCacheKey.equals(currentCacheKey);
      if (imageAwareWasReused) {
          L.d(LOG_TASK_CANCELLED_IMAGEAWARE_REUSED, memoryCacheKey);
          return true;
      }
      return false;
  }

而在调用isViewReused的checkViewReused的如下,

1
2
3
4
5
6
7
   /** @throws TaskCancelledException if target ImageAware is collected by GC */
  private void checkViewReused() throws TaskCancelledException {
      if (isViewReused()) {
          throw new TaskCancelledException();
      }
  }

最后,在线程的主体run方法中,通过调用checkTaskNotActual()判断view重用或者被回收,抛出TaskCancelledException,来结束当前的线程,并回调图片加载的cancel方法。

最后,我感兴趣的几个问题记录如上,有疑问,大家不吝赐教,可以一起探讨。

PS:转载请注明来自原文链接。

版权归作者所有,转载请注明原文链接:/blog/2015/10/19/ListView-scroll-analyze/

给 Ta 个打赏吧...

Comments