加载图片以及多线程

 

来了公司几个月,做了两个项目,一直没总结,现在终于有时间了。
先看一下第二个项目酷运动商城
在这个项目里面,最大的突破在于在于
android的列表去加载大量图片
以及多线程的应用
activityGroup的应用,并且控制其跳转
共享数据,数据库的应用,文件缓存
对布局的进一步的熟悉,以及突破。
这个项目获益良多,慢慢总结。
本篇说明一下,列表加载大量的图片,以及多线程在这边的应用。
这里主要是几个问题会非常麻烦,需要我们去处理
加载图片完成后即时刷新
加载了大量的图片,却不会发生内存的溢出,OutOfMemory
多级的缓存机制
多线程的应用,提高图片的加载速度
线程池的运用,以防止开启过多的线程,使cpu的压力骤增。
加载图片并且即时刷新
这里运用到了一个懒加载 (lazy load)的技术。
也就是在图片读取完整之后,再在主的绘图线程里去设置该图片,就达到了这个效果。
view plain
// ===========================================================
// 这段代码有很多地方参考了国外一位大大的代码,不是原创。
// ===========================================================
// 这个是图片的缓存
static HashMap<String, SoftReference> cache = new HashMap<String, SoftReference>();

// 这里一段不完整的代码,主要用以展示整个过程
public void DisplayImage(String imageUrl, ImageView imageView) {
// 首先尝试从内存里读取缓存,或许有重用的图片被储存在内存里了
Bitmap bitmap = null;
SoftReference sf = cache.get(key);

if(sf != null) {
bitmap = sf.get();
}
// 如果内存里有则直接设置
if(bitmap != null) {
imageView.setImageBitmap(bitmap);
}
// 如果内存里面没有缓存则加入工作队列中并且暂时让其不显示
else {
queuePhoto(imageUrl, imageView);
// 这边要先设置其目前的画面为空,否则可能会产生图像的混乱
imageView.setImageDrawable(null);
}
}

// 然后对处理那些加入工作队列的对象
public void queuePhoto(String url, ImageView imageView) {
// 这个imageView可能之前用到过,所以也许会有旧的任务
// 正在运行,所以先清除重复的
photosQueue.Clean(imageView);
// 这个对象只是用来保存url,和imageView
PhotoToLoad p = new PhotoToLoad(url, imageView);
synchronized(photosQueue.photosToLoad) {
photosQueue.photosToLoad.add(p);
// 唤起等待的线程,具体可以看下面的Thread类
photosQueue.photosToLoad.notifyAll();
}

// 这里开启了一个线程去负责加载图片
if(photoLoaderThread.getStatus() == Thread.State.NEW) {
photoLoaderThread.start();
}
}

// 然后来看看这个一直在跑的线程
class PhotosLoaderThread extends Thread {
public void run() {
try {
while(true) {
// 如果队列的长度为0,则等待
if(photosQueue.photosToLoad.size() == 0) {
synchronzied(photosQueue.photosToLoad) {
photosQueue.phototsToLoad.wait();
}
}

if(photosQueue.photosToLoad.size() != 0) {
PhotoToLoad photoToLoad;
synchronzied(photosQueue.photosToLoad) {
photoToLoad = photosQueue.phototsToLoad.remove(0);
}

// 然后去获取Bitmap
Bitmap bitmap = getBitmap(url);
if(bitmap != null) {
BitmapDisplayer bd = BitmapDisplayer(photoToLoad.url, photoToLoad.imageView);
Activity a = (Activity) photoToLoad.imageView.getContext();
a.runOnUiThread(bd);
}
}
}
}
}
}

class BitmapDisplayer implements Runnable {
Bitmap bitmap;
ImageView imageView;

public BitmapDisplayer(Bitmap b, ImageView i) {
this.bitmap = b;
this.imageView = i;
}

public void run() {
if (bitmap != null){
this.imageView.setImageBitmap(bitmap);
}
}
}

上面的代码展示了如何去懒加载图片,我在这边做了一点改进,就是加了一个全局的简单的线程池去维护这些线程。
为什么这么做呢?
以前我加载一张网络图片,http的握手超时时间设成10秒,如果对方服务器比较烂,那可能会变成如下的情况。
一分钟过去了一张都没出来,所以为了避免这种情况的产生,我就引入了多线程。
但是这边会有这样一个问题,如果一瞬间屏幕上有20张图片要加载,或者我的列表是拖动的方式,去加载的,那不是拖动了100项,就直接开启了100个线程了么?
这必然对CPU是一个巨大的考验,所以我又改变了多线程的形式,写了一个线程池去管理我开启的线程,控制线程在一个合理的范围内。
view plain
public class ThreadPool extends ThreadGroup {
final static private String TAG = "ThreadPool";
private boolean isClosed = false; // 线程池是否关闭
private LinkedList workQueue; // 工作队列
private static int threadPoolId = 1; // 线程池id
private boolean wait = false;

public boolean isWait() {
return wait;
}

public void setWait(boolean wait) {
this.wait = wait;
}

public ThreadPool(int poolSize) {
super(threadPoolId + ""); // 指定ThreadGroup的名称
setDaemon(true); // 继承到的方法,设置是否守护线程池
workQueue = new LinkedList(); // 创建工作队列
for(int i=0; i<poolSize; i++) {
new WorkThread(i).start(); // 创建并启动工作线程
}
}

// 向工作队列里加入一个新的任务,由工作线程去执行任务
public synchronized void execute(Runnable task) {
if(isClosed) {
throw new IllegalStateException();
}
if(task != null) {

while(wait) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}

workQueue.add(task);
notify();
}
}

private synchronized Runnable getTask(int threadId) throws InterruptedException {
while(workQueue.size() == 0) {
if(isClosed)
return null;
Log.d(TAG, "工作线程" + threadId + "等待任务...");
wait();
}

Log.d(TAG, "工作线程" + threadId + "开始执行任务...");
return (Runnable) workQueue.removeFirst(); // 返回队列中第一个元素
}

public synchronized void closePool() {
if(!isClosed) {
waitFinish(); // 等待工作线程执行完毕
isClosed = true;
workQueue.clear();
interrupt();
}
}

public synchronized void waitPool() {
this.wait = true;
}

public synchronized void notifyPool() {
this.wait = false;
}

// 等待工作线程把所有任务执行完毕
public void waitFinish() {
synchronized(this) {
isClosed = true;
notifyAll();
}

Thread[] threads = new Thread[activeCount()]; // activeCount() 返回该线程组中活动线程的估计值

int count = enumerate(threads); // enumerate() 方法继承自ThreadGroup类,根据活动线程的估计值获得线程组中当前所有的活动的工作线程

for(int i=0; i<count; i++) {
if(!threads[i].isInterrupted()) {
threads[i].interrupt();
}
}
}

private class WorkThread extends Thread {
private int id;

public WorkThread(int id) {
super(ThreadPool.this, id + "");
this.id = id;
}

public void run() {
while(!isInterrupted()) {

Runnable task = null;

try {
task = getTask(id);
} catch (InterruptedException e) {
e.printStackTrace();
}

// 如果getTask()返回null或者线程执行getTask()时中断,则结束此线程
if(task == null)
return;

try {
task.run();
} catch (Throwable t) {
t.printStackTrace();
}
}
}
}
}

那么如何把线程池加入之前的操作里呢?
view plain
class PhotosLoader extends Thread {
public void run() {
try {
while (true) {
// thread waits until there are any images to load in the
// queue
if (photosQueue.photosToLoad.size() == 0)
synchronized (photosQueue.photosToLoad) {
photosQueue.photosToLoad.wait();
}
if (photosQueue.photosToLoad.size() != 0) {
PhotoToLoad photoToLoad;
synchronized (photosQueue.photosToLoad) {
photoToLoad = photosQueue.photosToLoad.remove(0);
}
BitmapDownAndDisplay bdd = new BitmapDownAndDisplay(photoToLoad, (Activity) photoToLoad.imageView.getContext());
threadPool.execute(bdd);
}
if (Thread.interrupted())
break;
}
} catch (InterruptedException e) {
// allow thread to exit
}
}
}

// Used to support multi thread
class BitmapDownAndDisplay implements Runnable {
private PhotoToLoad photoToLoad;
private Activity activity;

public BitmapDownAndDisplay(PhotoToLoad photoToLoad, Activity activity) {
super();
this.photoToLoad = photoToLoad;
this.activity = activity;
}

public void run() {
Bitmap bitmap = null;
synchronized (this) {
bitmap = getBitmap(photoToLoad.url, photoToLoad.persistent);
if(bitmap != null) {
cache.put(String.valueOf(photoToLoad.url.hashCode()), new SoftReference(bitmap));
if(photoToLoad.imageView.getTag() != null &&
photoToLoad.imageView.getTag().toString().equals(photoToLoad.url)) {
BitmapDisplayer bd = new BitmapDisplayer(bitmap, photoToLoad.imageView, photoToLoad.url);
activity.runOnUiThread(bd);
}
} else {
if(!wait) {
// 非阻塞线程则加会队列末尾
photosQueue.photosToLoad.add(photoToLoad);
}
}
}
}
}

这样一个相对完整并且稚嫩的在线程池管理下的懒加载图片类就如此了。



上面的代码只是随性而至,然后这边整个流程如下所示:

Para obter informações mais completas sobre otimizações do compilador, consulte nosso aviso de otimização.