在ListView加载图片的时候,我们就会关于这里的图片加载产生一些问题。另外,我们知道针对ListView加载图片常用的优化方案之一就是,在列表的滑动过程中,暂停图片的加载任务,在滑动结束之后,则继续之前的任务。所以我们针对这些问题进行一些探讨研究(以UIL图片加载为例)。
知识点1:加载图片的线程的控制;
我们知道在ListView的adapter的getView方法中,进行图片的获取,这里为执行LoadAndDisplayImageTask,这是一个单独的线程去加载图片。但我们不会对每个View加载图片都去执行一个新的线程,所以这里采用的是一个线程池来控制线程的数目,来避免频繁地创建线程。-
知识点2:在ListView的滑动过程中,是如何暂停当前的图片下载的任务的;
由于这里我们采用的是单独的线程来控制图片的加载显示的,针对线程,我们使用的是加锁来使线程阻塞,当ListView滑动结束,则使其恢复。
在UIL中,锁声明的代码如下:
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的判断。
```java
/** @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重用的代码如下:
```java
/** @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的如下,
```java
/** @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: 原文链接