王晉東
摘 要:隨著移動設備的普及,越來越多的互聯(lián)網(wǎng)應用都對加載圖片的速度有很高的要求。在簡要對比傳統(tǒng)的同步和多線程兩種加載圖片方式后,給出相應地實現(xiàn)算法和評價。
關鍵詞:移動 圖片 多線程 算法
中圖分類號:TP316 文獻標識碼:A 文章編號:1007-3973(2013)009-102-03
1 前言
iOS、Android、Windows Phone及Symbian等主流的移動操作系統(tǒng)現(xiàn)在均已支持加載jpg、png等常見格式的圖片,隨著應用對網(wǎng)絡依賴程度的加強,這些操作系統(tǒng)也已支持直接從網(wǎng)絡直接讀取圖片。而讀取只要給出該圖片的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
這種方式的優(yōu)點是簡單,缺點也很明顯:客戶端增多時,服務器壓力會陡然增大,而此種方式要求圖片必須是連續(xù)加載,即客戶端需要等待自己的加載要求出服務器隊列時才會收到應答。圖片一般比較大,所以它們都是在基本框架加載后才逐漸加載上的,整個加載的過程非常不雅觀,或者是從模糊逐漸變清晰,或者是從上往下拓展開(當然你也可以認為這些都是不錯的特效)。
3 多線程方式
3.1 統(tǒng)一需求
除了不能滿足快速加載網(wǎng)絡圖片的要求以外,同步方式還存在諸多缺點。多線程方式通過采用不同的機制,不僅保證了快速加載圖片的基本要求,更從流量上等方面具有較大的優(yōu)勢。
3.2 多線程與線程池
由于程序的代碼中存在著數(shù)據(jù)和控制依賴關系,單線程只能很有限地滿足當今處理能力的要求。為了增加處理器的處理能力而一味地強化指令的執(zhí)行順序和細化分支,有時也不見得能事半功倍。因此,現(xiàn)代微處理器多采用硬件多線程技術來發(fā)掘線程之間的線程級并行潛力。移動終端所采用的處理器多為ARM架構,很好地滿足了多線程的處理要求。
多線程技術主要解決處理器單元內多個線程并行執(zhí)行的問題,它可以顯著減少處理器單元的閑置時間,增加處理器單元的吞吐能力。但如果對多線程應用不當,會增加對單個任務的處理時間。可以舉一個簡單的例子:
假設在一臺服務器完成一項任務的時間為T,它包括創(chuàng)建線程T1、執(zhí)行任務T2、線程同步T3以及線程銷毀的時間T4。顯然,在理想狀況下,T必然是這幾部分的時間之和。
可以看出T1,T4是多線程本身的帶來的開銷,我們渴望減少T1,T4,從而減少T的時間。但是如果在程序中頻繁地創(chuàng)建或銷毀線程,會導致T1和T4在T中占有相當大比例,從而使整個執(zhí)行的時間變長。這顯然并沒有很好地利用線程的并發(fā)性。
線程池技術正是關注如何縮短或調整T1,T4時間的技術,從而提高服務器程序性能的。它把T1,T4分別安排在服務器程序的啟動和結束的時間段或者一些空閑的時間段,這樣在服務器程序處理客戶請求時,不會有T1,T4的開銷了。另外,線程池不僅調整T1,T4產(chǎn)生的時間段,而且它還顯著減少了創(chuàng)建線程的數(shù)目。
3.3 常見系統(tǒng)的線程類
Java中線程類Thread的繼承關系為java.lang.Thread,在C#中是System.Threading.Thread,而iOS 支持NSThread等多個層次的多線程編程,層次越高的抽象程度越高,使用起來也越方便,也是蘋果最推薦使用的方法。
這些系統(tǒng)的線程類使用起來都非常方便,它將作為我們快速加載圖片的基礎。
3.4 多線程模式的算法
為了不失一般性,我們約定,采用一種類似C#(當然也可以是Java)的語言來實現(xiàn)算法。
3.4.1 線程池的構造
class ThreadPool
{
Assume:isClosed,Queue,poolId and wait;
//是否關閉,隊列,id,是否等待狀態(tài)
//構造函數(shù),參數(shù)為線程池大小
ThreadPool(size)
Initialize Queue as a queue; //創(chuàng)建新隊列
For i:= 0 to size
Create and start a new thread; //啟動一個新線程
//是否等待狀態(tài)
bool isWait return wait
//設置為等待
void setWait(_wait) wait := _wait;
// 向隊列里加入一個新的任務,由工作線程去執(zhí)行任務
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(); // 等待工作線程執(zhí)行完畢
isClosed := true;
Queue.clear();
interrupt();
//讓線程池處于等待狀態(tài)
synchronized void waitPool() this.wait := true;
//喚醒線程池
synchronized void notifyPool() this.wait := false;
// 等待工作線程把所有任務執(zhí)行完畢
void waitFinish
synchronized;
isClosed := true;
notifyAll;
threads[] := new Thread[activeCount()]; // 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或者線程執(zhí)行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系統(tǒng)的模擬器,運行兩段代碼,一段為同步加載圖片,另一段為多線程方式,在系統(tǒng)內部均沒有緩存的情況下進行測試。網(wǎng)絡圖片來自http://pivotstudio.org,加載的效果如圖2。
很明顯,多線程方式的加載時間要大大優(yōu)于同步方式。
5 多線程方式的優(yōu)點總結
(1)在加載圖片時使UI不至于阻塞太久,縮短了加載時間;
(2)多個圖片控件同時加載,速度更快;
(3)實現(xiàn)算法具有通用性,主流平臺均受支持;
(4)運用線程池以防止開啟過多的線程,從而增大CPU的壓力;
(5)可擴展性:在此基礎上加入內存管理的機制,可以提高終端內存的使用率。
6 總結
在實際的應用中,越來越多的應用和游戲需要從網(wǎng)絡獲取很多的圖片,本文所討論的多線程加載圖片的機制就很好地滿足了上述要求。當然,盡管算法是通用的,在具體的項目和開發(fā)環(huán)境中還需要根據(jù)語言和平臺的特點靈活編寫程序,方能發(fā)揮出算法最佳地性能。
參考文獻:
[1] 黃天柱,涂時亮.iOS開發(fā)UITableView加載圖片的內存管理[J].計算機系統(tǒng)應用,2012(09).
[2] 沙博.基于Android手機平臺的應用研究[D].吉林大學,2012.
[3] (美)馬克,拉馬赫.著.iPhone 3開發(fā)基礎教程(第1版)[M].漆振,等.譯.北京:人民郵電出版社,2009.
[4] 伏英娜.Windows Phone 7應用開發(fā)指南(第1版)[M].北京:電子工業(yè)出版社,2011.
[5] 佘志龍,陳昱勛,鄭名杰,等.Google Android SDK開發(fā)范例大全(第3版)[M].北京:人民郵電出版社,2011.