Android開發中一般都是使用Intent給Activity傳參。有時需要傳複雜物件時,我們會傾向於用全域變數(靜態變數或Application屬性)。但其實這樣做是有隱患的,跟Activity的生命週期有關,正好最近遇到這個問題,在這裡寫一下。
大概情況是這樣的:ActivityA中點擊按鈕啟動ActivityB,同時要傳一個大資料對象,懶得對這個對象進行序列化,於是就直接搞了個全域變數ActivityB.param寫了進去,在ActivityB.onCreate裡讀取並顯示資訊,編譯運行一切正常。這樣過了大半個月似乎也沒發現什麼問題。直到有一天發給客戶使用後,在友盟後台看到了null 指標錯誤,仔細分析堆棧代碼,錯誤就在ActivityB.onCreate裡讀取全域變數時發生,也就是全域變數返回了空!
全域變數為空白一般就是由於記憶體不足進程被KILL過重新建立了。按常理分析,ActivityA在給ActivityB.param賦值後會立即啟動ActivityB,這過程很短,進程不可能這麼快被KILL,因此理論上ActivityB.onCreate中應該能讀取到ActivityB.param的。
實際上,在ActivityA給ActivityB.param賦值啟動ActivityB後,ActivityB.onCreate確實是能讀取到ActivityB.param的;但是,ActivityB並不能保證永遠在前台,一旦ActivityB所在任務被切到後台(如有電話打進來了),系統就可以在記憶體不足時將ActivityB所在的進程KILL掉;而當ActivityB所在任務被切回前台(如電話打完了),這時系統會自動重新恢複ActivityB,這時全域變數自然就沒了。
有人說我不用靜態變數,用Application的屬性來存全域參數,是不是就可以避免這個問題了呢?其實也是不行的,因為進程被KILL再恢複後,Application對象也是銷毀重建了的;安卓系統並不保證會在KILL進程前給程式發通知,因此我們也無法在Application裡儲存恢複全域變數。
另外,全域變數也不能記錄安卓的介面Context相關的類(如Activity、View),因為安卓系統自動管理這些類,記錄它們會導致引用計數增加無法釋放的記憶體泄露問題;如果一定要記錄,則應該使用弱引用WeakReference。
總之安卓開發中是不推薦用全域變數傳參的。最好的辦法還是按照安卓的開發規範,完全使用Intent進行傳參,因為系統在KILL進程前會自動儲存Activity堆棧,同時儲存相關的Intent參數,並自動進行恢複。如果非要用全域變數,則至少必須在讀取全域變數處理時判斷是否為空白,避免程式出錯崩潰;同時最好在onPause時自行儲存資料以便被KILL後恢複。
但是我覺得全域變數也不是一無是處完全不能用,主要是要理解並避開安卓進程生命週期中全域變數的變化。例如用全域變數來記錄自己寫的全域處理類(如工廠類、類註冊器等),只要注意在被進程KILL後做好恢複工作,是完全可以的。