FMX開發android和iOS越來越穩定完善,期待delphi能夠有更多新人接力。
下面說說在FMX開發中APP經常莫名其妙閃退的一些原因:
1)線程訪問UI: 優先排查最常見的線程訪問UI控件沒有加同步保護,下面是相應的建議;
為了避免界面UI因為一些耗時較長的調用(網絡訪問,阻塞請求等)導致APP提示無響應,建議在各種用戶交互操作中使用以下代碼,此代碼幾乎是各種操作通用的調用方法:
procedure TfmDemo.Button1Click(Sender: TObject); begin TThread.CreateAnonymousThread(procedure begin //Do something here... //update UI TThread.Synchronize(nil, procedure begin Button1.Text := 'done'; end); end).Start; end;
2)內存泄漏:檢查變量的創建和釋放,訪問等相關代碼,排除空指針,無效變量,無效類實例等調用。
這里沒什么好說的,枯燥無聊的排查,讓人沉浸其中且煩悶的過程,不過也有以下方法:
- A.可以將程序先輸出為Windows平台,設置 System.ReportMemoryLeaksOnShutdown := True; 來開啟內存泄漏檢測(當然也有其他內存泄漏檢測組件包,大同小異),實際中需詳細排查,此方法排查跨平台代碼。
- B.在可調試條件下,直接調試並在出錯的地方看CallStack是最直觀的,可一層一層剝開看到出錯的地方(程序員做的最多就是且應該是這種操作了)
- C.輸出運行日志,比較基礎的操作就是利用Log.D或者其他自主實現的記錄日志方法,在程序各個函數的入口出口處記錄參數和結果(在VCL中經常使用OutputDebugString的人會比較習慣)
- D.單元測試:相信這個方法90%以上的人做不到,當然也包括本人,不過大公司規范嚴格的會有此類要求,接觸過的人會比較習慣
3)運行時函數的非法參數:系統時間日期格式問題
這個問題多半出現在將時間字符串轉成TDateTime過程中,該動作在取數據庫時間,在顯示日期時間等功能中經常使用,
假如使用了StrToDateTime,EncodeDate,EncodeTime等RTL函數,因為一些習慣問題,很多人認為時間字符串格式默認就是“YYYY-MM-DD hh:mm:ss”,但是很多時候Windows系統Android系統,甚至iOS系統都不是這個格式,具體有系統設置區域語言控制,這時候調用上面的RTL函數會出現異常錯誤,而在手機上此類錯誤會導致直接閃退。
解決辦法就是先設置TFormatSettings中與時間格式相關的字段,可參考如下:
function StrToDateTime(sDateTime: string; const Default: TDateTime): TDateTime; var aFormatSettings: TFormatSettings; begin aFormatSettings := TFormatSettings.Create; aFormatSettings.TimeSeparator := ':'; aFormatSettings.ShortTimeFormat := 'hh:mm:ss'; aFormatSettings.LongTimeFormat := 'hh:mm:ss'; if sDateTime.Contains('/') then begin aFormatSettings.ShortDateFormat := 'YYYY/MM/DD'; aFormatSettings.LongDateFormat := 'YYYY/MM/DD'; aFormatSettings.DateSeparator := '/'; end else begin aFormatSettings.ShortDateFormat := 'YYYY-MM-DD'; aFormatSettings.LongDateFormat := 'YYYY-MM-DD'; aFormatSettings.DateSeparator := '-'; end; Result := SysUtils.StrToDateTimeDef(sDateTime, Default, aFormatSettings); end;
4)編寫習慣錯誤:新版本的變量釋放用法,當然這里只是說明一下在傳統VCL轉到FMX后經常碰到問題時的大概率原因。
在VCL開發中有些人會經常使用destroy來釋放創建的類示例(Create和Destroy或Free一一對應),
這也是因為在VCL中的Free內部其實也是調用Destroy,導致很多人釋放時直接調用Destroy。
而Delphi FMX在開發Android APP時,經常出現APP崩潰閃退也有很大可能性是因Destroy導致的。
因為在FMX,全局變量和類變量采用ARC引用計數來釋放,而計數的增加很多時候是隱藏在代碼背后自動添加的,屬於FMX框架運行時所需的計數量。
對於Delphi語法來說 Create 應該和 Free(或FreeAndNil)一一對應來使用。
但在FMX中,經常有人反饋占用內存越來越大,因為Free並不能直接釋放內存,因此Delphi又提供了DisposeOf來釋放(並不是強制釋放,仍與計數有關),
我們在開發中如確定實例已經不再被需要了,可以調用DisposeOf來釋放,正常情況都可以直接釋放。
簡單調用如下
procedure TfmDemo.DoProcess(); var aObj: TDemoObj; begin aObj:=TDemoObj.Create; try //..... finally aObj.DisposeOf;
// 建議標准做法: aObj.Free; aObj := nil;
// FreeAndNil目前由於調用時會增加新的引用計數,所以不推薦,若以后Delphi有針對該方法優化則可直接調用 end; end;
為什么在VCL調用Destroy可以正常,而在FMX中卻容易出錯
注:當然並不一定會出錯,在非類方法的純函數(面向過程)中仍可正常調用(只是實際中測試正常,無法保證隨着以后的升級更新后仍然正常)
通過Delphi提供的TObject源碼可知,若直接調用Destroy,則會導致類示例的Destroy方法被執行並直接釋放內存,也就相當於“內容”被擦除了,但程序中某些地方仍會使用實例變量,且變量的引用計數仍在,所以當FMX框架內部在引用計數為0時的自動釋放變量所指的實例過程中,會再次擦除不存在的實例“內容”,相當於訪問不存在的非法內存,就會導致崩潰,且調試中大部分無法直接定義代碼位置,若沒有該經驗,最終覺得毫無頭緒。。。
就像上面說的,全局變量或類變量會有引用計數的影響,而且需要注意的是,匿名函數在編譯時也會被當作匿名類處理,其局部變量也會轉換為類變量。
當我們編譯匿名函數(最常用的是匿名線程中的各種調用)時,若有未使用變量的警告,提示內容一般是:
[DCC Hint] Unit1.pas(56): H2164 Variable 'aSSS' is declared but never used in '(null).[0]'
這里的 (null).[0] 就是編譯器自動創建的匿名函數類名稱。
補充:
根據EMB的說法,未來將會取消ARC,所以目前代碼的編寫習慣,建議是保持Create和Free 一一對應即可,沒必要糾結內存增長,而歸根結底內存增長其實是由於編碼不規范導致的,不能認為是編譯器或者FMX框架的BUG,變量的交叉關聯引用都會導致ARC無法正常釋放,所以改善編碼質量,局部變量及時釋放;全局變量或類變量避免重復創建,合理釋放即可;
個人認為保持代碼的編譯兼容性最為重要,對於移交代碼,跨平台編譯,多環境編譯非常有用。
5)補充一個非常冷門的原因,代碼優化BUG:常出現在APP使用DEBUG編譯時運行正常,但使用RELEASE編譯運行則崩潰
這種情況下,首先確認你的水平處於中高以上時,對自己的代碼已經非常確定無錯誤,但實際中編譯出來仍然會出現崩潰,可嘗試關閉代碼優化,DCC的編譯器BUG是存在的,特別是對代碼優化的處理上,每個版本的更新都會被報告或多或少幾個編譯器BUG。 所以關閉代碼優化,可能可以解決崩潰問題!
需要注意的是,使用DEBUG和使用RELEASE編譯使用的dcu不同,甚至代碼中可能使用{$IFDEF DEBUG}做編譯開關導致處理代碼不同,因此需要優先排查此部分代碼是否存在BUG。