1 前言
iOS、Android、Windows Phone及Symbian等主流的移动操作系统现在均已支持加载jpg、png等常见格式的图片,随着应用对网络依赖程度的加强,这些操作系统也已支持直接从网络直接读取图片。而读取只要给出该图片的http地址(URL)即可。
对用户界面响应速度要求较高的应用而言,图片的加载速度不宜过慢。而随着如今终端屏幕分辨率的增加,用户对手机的图片质量也有了很高的要求。就单一服务器模式而言,以前流行的分布式算法显然达不到要求。
2 同步方式
同步方式十分简单。由客户端向服务器提交请求,服务器对此做出应答——应答的结果就是从远程向客户端返回符合要求的图片代码,图片接收完毕后再由客户端作解码、还原。同步方式的算法如下。
客户端:
BEGIN
Activate webservice
Send request to server
Wait for response
Download complete
END
服务器:
BEGIN
Receive reqests
Create response queue
While queue is not empty
dequeue
handle every request
END
这种方式的优点是简单,缺点也很明显:客户端增多时,服务器压力会陡然增大,而此种方式要求图片必须是连续加载,即客户端需要等待自己的加载要求出服务器队列时才会收到应答。图片一般比较大,所以它们都是在基本框架加载后才逐渐加载上的,整个加载的过程非常不雅观,或者是从模糊逐渐变清晰,或者是从上往下拓展开(当然你也可以认为这些都是不错的特效)。
3 多线程方式
3.1 统一需求
除了不能满足快速加载网络图片的要求以外,同步方式还存在诸多缺点。多线程方式通过采用不同的机制,不仅保证了快速加载图片的基本要求,更从流量上等方面具有较大的优势。
3.2 多线程与线程池
由于程序的代码中存在着数据和控制依赖关系,单线程只能很有限地满足当今处理能力的要求。为了增加处理器的处理能力而一味地强化指令的执行顺序和细化分支,有时也不见得能事半功倍。因此,现代微处理器多采用硬件多线程技术来发掘线程之间的线程级并行潜力。移动终端所采用的处理器多为ARM架构,很好地满足了多线程的处理要求。
多线程技术主要解决处理器单元内多个线程并行执行的问题,它可以显著减少处理器单元的闲置时间,增加处理器单元的吞吐能力。但如果对多线程应用不当,会增加对单个任务的处理时间。可以举一个简单的例子:
假设在一台服务器完成一项任务的时间为T,它包括创建线程T1、执行任务T2、线程同步T3以及线程销毁的时间T4。显然,在理想状况下,T必然是这几部分的时间之和。
可以看出T1,T4是多线程本身的带来的开销,我们渴望减少T1,T4,从而减少T的时间。但是如果在程序中频繁地创建或销毁线程,会导致T1和T4在T中占有相当大比例,从而使整个执行的时间变长。这显然并没有很好地利用线程的并发性。
线程池技术正是关注如何缩短或调整T1,T4时间的技术,从而提高服务器程序性能的。它把T1,T4分别安排在服务器程序的启动和结束的时间段或者一些空闲的时间段,这样在服务器程序处理客户请求时,不会有T1,T4的开销了。另外,线程池不仅调整T1,T4产生的时间段,而且它还显著减少了创建线程的数目。
3.3 常见系统的线程类
Java中线程类Thread的继承关系为java.lang.Thread,在C#中是System.Threading.Thread,而iOS 支持NSThread等多个层次的多线程编程,层次越高的抽象程度越高,使用起来也越方便,也是苹果最推荐使用的方法。
这些系统的线程类使用起来都非常方便,它将作为我们快速加载图片的基础。
3.4 多线程模式的算法
为了不失一般性,我们约定,采用一种类似C#(当然也可以是Java)的语言来实现算法。
3.4.1 线程池的构造
class ThreadPool
{
Assume:isClosed,Queue,poolId and wait;
//是否关闭,队列,id,是否等待状态
//构造函数,参数为线程池大小
ThreadPool(size)
Initialize Queue as a queue; //创建新队列
For i:= 0 to size
Create and start a new thread; //启动一个新线程
//是否等待状态
bool isWait return wait
//设置为等待
void setWait(_wait) wait := _wait;
// 向队列里加入一个新的任务,由工作线程去执行任务
synchronized void execute(task)
//如果线程池已关闭,抛出异常,否则
if(task != null) then
While wait=true
Try
wait;
Catch
//Throw an exception
Add task to Queue;
Notify Queue;
synchronized Runnable getTask(threadId)
While Queue is not null
If isClosed then return null;
wait();
return First element in Queue // 返回队列中第一个元素
synchronized void closePool()
If !isClosed then
waitFinish(); // 等待工作线程执行完毕
isClosed := true;
Queue.clear();
interrupt();
//让线程池处于等待状态
synchronized void waitPool() this.wait := true;
//唤醒线程池
synchronized void notifyPool() this.wait := false;
// 等待工作线程把所有任务执行完毕
void waitFinish
synchronized;
isClosed := true;
notifyAll;
threads; // activeCount() 返回该线程组中活动线程的估计值
count := number of active threads
for I := 0 to count
if thread[i] is not interrupted then
set thread[i] to interrupted
}
3.4.2 工作线程类
class WorkThread
{
Assume:id;
WorkThread(_id)
Create a thread;
Id := _id;
void run()
While thread is not interrupted
Task := null;
Try
task = getTask(
id);
catch
//Throw an exception
// 如果getTask()返回null或者线程执行getTask()时中断,则结束此线程
If task is not null then
try
Run the task;
catch
Throw an exception;
}
3.4.3 加载图片类
class LoadImage
{
Assume size;
void run()
Try
while true
//wait until images come into queue
if size = 0 then
wait for photoqueue;
else
create and initialize an instance;
Create a BitMapLoad instance;
threadPool.execute(bdd);
if Thread is interrupted then break
Catch
// Exit the Thread
}
3.4.4 BitMapLoad 类
// 本类用来支持多线程
class BitmapDownAndDisplay
{
Assume photoToLoad,Activity;
BitmapDownAndDisplay(photoToLoad, activity)
this.photoToLoad := photoToLoad;
this.activity := activity;
oid run()
create a bitmap;
synchronized (this)
bitmap := getBitmap(photoToLoad.url);
if bitmap is not null then
add bitmap to system cache;
if photoToLoad.imageView.getTag() is not null &&
photoToLoad.imageView.getTag() =photoToLoad.url then
set bitmap to UI thread to show
else
if wait=false
// 非阻塞线程则加会队列末尾
Add photoLoad to photoQueue;
}
4多线程与同步方式的比较
我们采用相同的实验平台:Windows Phone 7.5系统的模拟器,运行两段代码,一段为同步加载图片,另一段为多线程方式,在系统内部均没有缓存的情况下进行测试。网络图片来自http://pivotstudio.org,加载的效果如图2。
很明显,多线程方式的加载时间要大大优于同步方式。
5 多线程方式的优点总结
(1)在加载图片时使UI不至于阻塞太久,缩短了加载时间;
(2)多个图片控件同时加载,速度更快;
(3)实现算法具有通用性,主流平台均受支持;
(4)运用线程池以防止开启过多的线程,从而增大CPU的压力;
(5)可扩展性:在此基础上加入内存管理的机制,可以提高终端内存的使用率。
6 总结
在实际的应用中,越来越多的应用和游戏需要从网络获取很多的图片,本文所讨论的多线程加载图片的机制就很好地满足了上述要求。当然,尽管算法是通用的,在具体的项目和开发环境中还需要根据语言和平台的特点灵活编写程序,方能发挥出算法最佳地性能。
参考文献:
.计算机系统应用,2012(09).
[2] 沙博.基于Android手机平台的应用研究[D].吉林大学,2012.
[3] (美)马克,拉马赫.著.iPhone 3开发基础教程(第1版)[M].漆振,等.译.北京:人民邮电出版社,2009.
.北京:电子工业出版社,2011.
[5] 佘志龙,陈昱勋,郑名杰,等.Google Android SDK开发范例大全(第3版)[M].北京:人民邮电出版社,2011.