這是一位開發人員在經曆多年的開發,總結出了對於Symbian開發人員的22條建議,希望能給從事Symbian開發的開發人員提供相應的指導。
1、 確保您的應用程式能夠對系統關機事件做出響應。在您的AppUi::HandleCommandL()方法中,必須要對EEikCmdExit(以及任何特定平台相關的事件,例如Series 60上的EAknSoftkeyBack)做出響應。
2、 要對外來系統事件做出響應。請牢記,您的應用程式在一個多任務電話系統上運行,您需要將注意力集中於剛獲得/遺失的事件上,以確保當使用者獲得一個高優先順序的通知時您能夠做出正確響應。例如,正打進來的電話會干擾您的應用程式的運行,這時應確保您已儲存了系統狀態和資料(即:您需要對標準的“背景”事件採取恰當的行動—請參閱SDK)。一般來說,系統架構會處理這個問題,您不需要採取任何特殊行動—但一定要確保您沒有妨礙系統架構的正常操作。
3、記憶體處理是Symbian OS需要考慮的一個重要課題。在這一點上,應注意電話有時會不同於模擬器。因此在將您的應用程式呈交給“Symbian 認證簽名”進行測試之前,務必確保已經在實際電話裝置上測試了您的程式。
4、 記憶體堆棧空間有限!應儘可能將對象放到記憶體堆中,而不要放到棧裡。KERN-EXEC 3異常(panic)發生的主要原因之一就是棧的破壞/溢出。
5、 應用程式發生異常(panic)表明您的代碼中一定有錯誤。以下是一些主要的、常見的錯誤:
Ⅰ:忘記將非對象成員、被分配到堆的變數加到CleanupStack上。
Ⅱ:將成員變數放到CleanupStack上—這一點要千萬避免(在解構函式中將這些變數刪除就可以了)。
Ⅲ:“重複刪除”--例如,沒有正確的從CleanupStack上Pop()出已經被銷毀的對象,造成CleanupStack以後試圖再次刪除它。或者使用過一個對象之後將其刪除但忘記將其值設成NULL,從而在解構函式裡又試圖刪除一次。
Ⅳ:用可能不存在於您的解構函式中的變數調用函數。例如,以下代碼可能導致異常因為有可能您在分配記憶體之前您的對象已經被銷毀,或者在應用程式的另一處已經刪除了該記憶體,這樣iSomeServer就會處於NULL:
CMyClass::~CMyClass()
{
iSomeServer->Close();
delete iSomeServer;
}
應該如下編寫該代碼:
CMyClass::~CMyClass()
{
If(iSomeServer)
{
iSomeServer->Close();
delete iSomeServer;
}
}
Ⅴ:在NULL指標上調用函數。
Ⅵ:函數調用另一個函數,而其使用的變數已經超出範疇,例如:
把一個棧變數傳送到一個非同步函數的回調(callback)裡。
6、 在系統資源不夠的情況下,得體的處理失效情況是非常重要的。最受限制的資源通常是系統RAM,因此您需要注意正確的處理記憶體不足的情況。採用“兩端構造方法”和如下所述的CleanupStack機制,對這種防禦性編程來說是必不可少和極其重要的。
7、 對帶“R”字頭、具備Close()方法的類,總是使用CleanupClosePushL()。這將確保當Leave事件發生時,它們會被恰當的清除。例如:
RFile file;
User:LeaveIfError(file.Open(…));
CleanupClosePushL(file);
…
CleanupStack::PopAndDestroy(&file);
對用Release()或Destroy()的“R”類,亦可使用CleanupDeletePushL()及CleanupRelasePushL()來取代Close()。
8、 另外,請記住CleanupStack機制是可擴充的,面對所有Leave事件,都可以用它來有效清除任何對象。即使您需要處理的是較複雜的情況,也不應該忽略採用正規的清理機制。欲進一步瞭解TCleanupItem,請參閱Symbian OS Library文檔。
9、 倘若您意圖對HBufC變數重新分配資源,在清除它們之後,總是將其設為NULL。由於HBufC的資源分派或其再分配可能會導致Leave事件的發生,從而可能會出現解構函式試圖刪除已經不存在的HBufC變數的情況。當然,對於任何由堆分配資源的變數而言都應如此,對HBufC變數採取此做法更是已經成為普遍的使用模式。
10、 當必須採用自己的TRAP時,請忽略所有的報錯。常見的編碼錯誤是:
TRAPD(err, DoSomethingL());
If(err == KErrNone || err == KErrNotFound)
{
//Do something else
}
這意味著其他錯誤碼都被忽略。然而,倘若您非用上述模式不可,應採用Leave機制來處理其他錯誤:
TRAPD(err, DoSomethingL());
If(err == KErrNone||err == KerrNotFound)
{
//Do something else
}
else
User::Leave(err);
⒒、不要延遲將對象PushL()到CleanupStack上。所有新建立的對象(成員變數除外)應被立即壓入該堆棧。例如,下面的做法是錯的:
Void doExampleL()
{
CSomeObject* myObject1 = new (ELeave)CSomeObject;
CSomeObject* myObject2 = new (ELeave)CSomeObject;
…
//Do something here with the variables
CleanupStack::PushL(myObject1);
CleanupStack::PushL(myObject2);
//Do something more with the variables
…
CleanupStack::PopAndDestroy(2);
//myObject2,myObject1
}
因為myObject2的建立可能失敗,造成myObject1”懸”在那裡不能被清理。應該這樣來實現:
Void doExampleL()
{
CSomeObject* myObject1 = new (ELeave)CSomeObject;
CleanupStack::PushL(myObject1);
CSomeObject* myObject2 = new (ELeave)CSomeObject;
CleanupStack::PushL(myObject2);
…
//Do something here with the variables
…
CleanupStack::PopAndDestroy(2);
//myObject2,myObject1
}
12、注意,那些名稱有大寫字母C結尾的函數(例如NewLC())會自動把其對象置於CleanupStack。您不應該自己來將這些對象壓入CleanupStack,否則該對象會入棧兩次。當您建立非成員變數並為其分配記憶體時,這些由C結尾的函數很有用。
13、“兩端構造方法”是Symbian OS記憶體管理的關鍵區段。基本原則是Symbian OS中的建構函式或解構函式永遠不應該發生Leave。倘若一個C++建構函式Leave,構造過程未完成的對象得不到清理,因為還沒有產生指標指向該對象。為此,Symbian OS中的建構函式僅將該對象執行個體化,而後調用該對象的ConstructL()函數,在其中將成員資料執行個體化。一旦ConstructL()發生Leave,標準的解構函式將被調用來清除所有至此已被成功分配的成員變數。在您的編碼中照用這一設計模式來防止記憶體流失,至為關鍵。當您寫每一行代碼時,都應該問自己:“這一行代碼能否發生Leave?”假如回答為“是”,則應考慮“是否所有資源都將被釋放?”。
14、編碼中請勿使用_L()宏---而應使用_LIT()。_L()自Symbian OS V5起已是“不推薦使用”(deprecated),它的問題在於它將調用TPtrC(const TText*)建構函式,該建構函式會調用strlen()函數來計算該字串的長度。雖然這不會帶來額外的RAM開銷,卻會在運行時佔用更多CPU周期。相反,宏_LIT()直接建立了一個在編譯時間就全部執行個體化的對象,節省了構造TPtrC的CPU開銷。當然,您首先應該考慮的是否應該使用硬式編碼字串常量,因為當您將來地方化(localize)您的程式時,這種常量類型的描述符(descriptor)可能需要重新編碼(我覺得作者這裡寫的有問題,_LIT()及_L()定義的是常量字串,並非描述符,它不能夠用描述符的一些通用的方法,只有調用_LIT()和_L()重載的()運算子才會變成const TDesC&類型的描述符,並使TDesC的一些通用方法可用---孫東風注)。
15、當在函數參數中使用描述符(descriptor)時,應預設使用基類。在大多數情況下,以const TDesC&形式來傳遞描述符。對可修改的描述符,則應使用TDes&。
16、當在函數中傳遞或返回對象時,應確保如果您擁有該對象的所有權,您應負責將其清除!Symbian採取的約定是:函數中的指標表示所有權轉移到調用者,而使用引用則表示被傳遞對象的所有權仍屬於原所有者。
17、Active Objects是Symbian OS的重要特性之一。請仔細研究SDK文檔、Symbian Developer NetWork白皮書,以充分理解其工作原理。下面有一些有用的竅門:
Ⅰ:在RunL()內無需使用TRAP()。Active Scheduler本身會TRAP函數RunL()並在其發Leave時調用CActive::RunError()。
Ⅱ:為此,您應實現自己的RunError()函數來處理從RunL()的Leave事件。
Ⅲ:保證RunL()操作儘可能簡短。長時間啟動並執行RunL()將阻塞其他Active Objects。
Ⅳ:總是實現DoCancel()函數,總是在AO解構函式中調用Cancel()。
18、您應儘可能利用Active Object架構機制。對於使用電池供電的裝置,在一個迴圈中緊密不斷地進行輪流檢測(polling)是極其不適當的,將帶來大量耗電。寫遊戲時,對此尤需特別注意,詳情參閱Symbian Developer Network網站的技術文檔:
www.symbian.com/developer/techli ... /XenGames_paper.pdf
19、ViewSrv 11異常對於繁忙啟動並執行程式(例如遊戲)是一個潛在的問題。當您的,或者其他任何程式中的ViewSrv active object不能及時響應View Server時就會導致此種異常。典型的最長回應時間是10-20秒。FAQ-0900有詳細解釋,FAQ-0920有針對如何避免此類問題的實用技巧。二者均可從www3.symbian.com/faq.nsf網頁上的Symbian OS FAQ資料庫擷取。
20、您無需使用HBufC::Des()來進入一個HBufC對象。只需採用*操作符來為HBufC對象解除引用(dereference)。這對於向某個接受TDesC&(上文的推薦做法)的函數傳遞HBufC參數時尤其有用。
21、.當使用標準的程式.INI檔案的功能時(即在您的應用UI類中使用Application()->OpenIniFileLC();API時),確保將版本號碼資訊寫入流(stream)中。這樣使您能夠在未來新版本的程式中建立新的流,意味著即使某個終端使用者將來安裝您的軟體的新版本時,不會因為在舊的.INI檔案中找不到正確配置或流時發生異常。
22.、在您的程式中實現架構類(framework class)時要小心。應該始終從所提供的平台相關的架構類中繼承。例如,對UIQ而言,不要從CQikAppUi繼承。所有的應用基類(CQikAppUi、CQikApplication、CQikDocument)添加的功能支援更廣的架構範圍來保證應用程式正確運行。