前言
在你的App中的很多地方都需要使用到數據信息,它可能是一個session token,一次費時計算的結果等等,通常爲了避免Activity之間傳遞數據的開銷,會將這些數據通過持久化來存儲。
有人建議將這些數據放在Application對象中方便所有的Activity訪問,這個解決方案簡單、優雅並且是……完全錯誤的。
你如果你將數據緩存到Application對象中,那麼有可能你的程序最終會由於一個NullPointerException異常而崩潰掉。
一個簡單的測試程序
這是自定義Application的代碼:
// access modifiers omitted for brevity
class MyApplication extends Application {
String name;
String getName() {
return name;
}
void setName(String name) {
this.name = name;
}
}
在第一個Activity中,我們將用戶信息存儲在Application對象中:
// access modifiers omitted for brevity
class WhatIsYourNameActivity extends Activity {
void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.writing);
// Just assume that in the real app we would really ask it!
MyApplication app = (MyApplication) getApplication();
app.setName("Developer Phil");
startActivity(new Intent(this, GreetLoudlyActivity.class));
}
}
然後在第二個Activity中通過Application獲取存儲的用戶信息:
// access modifiers omitted for brevity
class GreetLoudlyActivity extends Activity {
TextView textview;
void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.reading);
textview = (TextView) findViewById(R.id.message);
}
void onResume() {
super.onResume();
MyApplication app = (MyApplication) getApplication();
textview.setText("HELLO " + app.getName().toUpperCase());
}
}
測試步驟
打開這個APP;
在WhatIsYourNameActivity中,你按要求輸入用戶名並將其緩存到MyApplication這個對象中;
接着在GreetLoudlyActivity中,程序從MyApplication對象中取出用戶名並顯示出來;
用戶按了Home按鍵離開了該APP;
數小時之後,系統由於內存不足(用戶在體驗其它APP呢,前臺的任務總是優先的嘛)會在後臺將你的程序殺掉;在你重新啓動該APP之前一切看上去很好,但是.....;
用戶重新打開了這個APP;
Android會重新創建一個之前被Kill掉的MyApplication實例並恢復GreetLoudlyActivity;
GreetLoudlyActivity去獲取用戶名時,會因爲獲取的爲空值報NullPointerException而崩潰掉。
爲什麼會這樣?
在上面這個例子中,程序之所以會崩潰掉是因爲恢復之後APP的Application對象是全新的,所以緩存在Application中的用戶名成員變量爲空值,在程序調用String的toUpperCase()方法時由於NullPointerException而崩潰掉。
導致這個問題的主要原因是:Application對象並不是始終在內存中的,它有可能會由於系統內存不足而被殺掉。但Android在你恢復這個應用時並不是重新開始啓動這個應用,它會創建一個新的Application對象並且啓動上次用戶離開時的activity以造成這個app從來沒有被kill掉得假象。
我們以爲可以通過Application來緩存數據,卻沒想到恢復APP時直接跑了B Activity而不是先啓動A Activity,最終導致的結果是程序意外的崩潰掉了。
有哪些替代方法可用呢?
你可以按照下面其中一種方式來處理:
通過Intent在Activity之間來傳遞數據(但是請別傳遞大量數據,這有可能導致程序異常或者ANR);
使用官方推薦的方法中的一種將數據持久化,存儲在磁盤中;
在使用數據和句柄的時候做空值檢測;
我有一個好一點的解決辦法,那就是綜合起來使用:
1、如果數據僅僅在兩個相近的activity中,那麼就使用intent傳遞
2、如果這個數據需要在很多地方使用,那麼就還存放在appliction中,把變量設爲私有,增加get和set方法,在方法中進行處理,在set方法中進行數據持久化操作,在get方法中判斷變量是否爲null,如果是則從磁盤中讀取值付給變量
如何模擬應用程序被殺掉?
更新:Daniel Lew指出,最簡單的方法是在DDMS中點擊”Stop Porcess”殺掉你的程序,在你調試程序的時候可以這樣做。
你可以通過模擬器或者一個Root過的真機來測試實際效果:
按Home按鍵退出你的程序;
在控制檯,敲入如下命令(Windows系統下 WIN + R -> cmd -> 回車)
# 找到該APP的進程ID
adb shell ps
# 找到你APP的報名
# Mac/Unix: save some time by using grep:
adb shell ps | grep your.app.package
# 按照上述命令操作後,看起來是這樣子的:
# USER PID PPID VSIZE RSS WCHAN PC NAME
# u0_a198 21997 160 827940 22064 ffffffff 00000000 S your.app.package
# 通過PID將你的APP殺掉
adb shell kill -9 21997
# APP現在被殺掉啦
現在在桌面長按Home按鍵通過後臺任務管理器打開你的APP,此時系統就會重新創建一個MyApplication實例了。
總結
不要在Application對象中緩存數據化,這有可能會導致你的程序崩掉。請使用Intent在各組件之間傳遞數據,抑或是將數據存儲在磁盤中,然後在需要的時候取出來。
並不僅僅只有Application對象是這樣的,其它的單例或者公有靜態類也有可能會由於系統內存而被殺掉,謹記。