前幾天在做一個密碼管理本類的應(yīng)用樊零,遇到這么一個需求:
應(yīng)用每次退出到后臺撤师,都要關(guān)閉存儲密碼信息的數(shù)據(jù)庫破花,并在應(yīng)用再切換到前臺時,向用戶展示一個輸入框讓用戶輸入訪問口令(該口令用于對數(shù)據(jù)庫進(jìn)行加解密)季研。
這就要求我對應(yīng)用的前后臺切換進(jìn)行監(jiān)聽。
為實現(xiàn)這個需求誉察,我第一個想到就是借助Activity
生命周期各個回調(diào)方法來實現(xiàn)与涡。
大致思路就是在onPause()
/onStop()
方法中關(guān)閉數(shù)據(jù)庫,然后在onStart()
/onResume()
方法中檢查數(shù)據(jù)庫的狀態(tài)持偏,
然后根據(jù)這個狀態(tài)值的來做出對應(yīng)的動作驼卖。偽代碼表示如下:
public class BaseActivity extends AppCompatActivity {
@Override
protected void onStart() {
super.onStart();
if(isSecureDataBaseClosed()){
showEnterPasswordActivity();
}else{
//do something else here
}
}
@Override
protected void onStop() {
super.onStop();
closeSecureDatabase();
}
}
興沖沖地按照這個思路實現(xiàn)了代碼,在虛擬機(jī)上測試鸿秆,很快就暴露了問題:
當(dāng)一個Activity
啟動另外一個Activity
酌畜,或者一個Activity
從另外一個Activity
返回時,都會調(diào)用相應(yīng)的onStart()
和onStop()
方法卿叽。
因此這個方法僅僅適用于應(yīng)用只有單個Activity
情況桥胞,當(dāng)有多個Activity
時,上面的思路并不能夠達(dá)到需求考婴。
于是只能看看有沒有別的方法贩虾。例行g(shù)oogle了一下,發(fā)現(xiàn)stackoverflow上早已有人問過類似的問題沥阱。有兩個高票回答看起來都十分可行整胃。于是決定整理一下,當(dāng)作筆記。
1. 使用定時器TimerTask + Timer
第一個的思路跟我上面所述的方法十分地相似屁使,但是解決了上述方法中存在的問題在岂。
上述方法之所以不能工作,原因在于在onStop()
方法中馬上就認(rèn)為應(yīng)用即將進(jìn)入后臺蛮寂,并且馬上關(guān)閉了數(shù)據(jù)庫蔽午。這樣就會對Activity
間的切換進(jìn)行誤判。
而stackoverflow上的方案就是對時間進(jìn)行了一個判斷酬蹋。因為Activity
間的一次切換時間是比較短的及老,因此可以借助這一點來改良上述方案。
下面給出描述這個思路的偽代碼:
private final long MAX_ACTIVITY_TRANSITION_TIME_MS = 2000;
@Override
public void onResume(){
super.onResume();
if(wasAppInBackground()){
//Do specific came-here-from-background code
}
stopTimer();
setAppWasInBackground(false);
}
@Override
public void onPause(){
super.onPause();
startTimer(MAX_ACTIVITY_TRANSITION_TIME_MS,TaskToSetAppWasInBackground(true));
}
簡單概括這個方法范抓,就是在onPause()
啟動一個定時任務(wù)骄恶,在MAX_ACTIVITY_TRANSITION_TIME_MS
時間間隔之后才將應(yīng)用狀態(tài)設(shè)置為后臺狀態(tài)。
然后在onResume()
方法中讀取應(yīng)用狀態(tài)匕垫,同時停止定時任務(wù)僧鲁,將應(yīng)用狀態(tài)設(shè)置為不在后臺。
這樣一來象泵,如果在onPause()
方法調(diào)用后,超過MAX_ACTIVITY_TRANSITION_TIME_MS
時間間隔才調(diào)用onResume()
方法寞秃,則認(rèn)為應(yīng)用是從后臺切換到前臺的,否則就認(rèn)為是Activity
間的切換偶惠。
具體的實現(xiàn)代碼在原帖里找到春寿。
2. 使用ComponentCallbacks2
這其實是一個非常直接有效的方法,也是我最終選擇的方法忽孽。從 Ice Cream Sandwich (API 14) 開始绑改,Android 官方便提供了這么一個回調(diào)接口。
ComponentCallbacks2
繼承自ComponentCallbacks
兄一,在ComponentCallbacks
的 基礎(chǔ)上添加了onTrimMemory(int)
回調(diào)厘线。
我們首先來看一下 Android Developers 上對ComponentCallbacks2
的說明:
Extended ComponentCallbacks interface with a new callback for finer-grained memory management. This interface is available in all application components (Activity, Service, ContentProvider, and Application).
You should implement onTrimMemory(int) to incrementally release memory based on current system constraints. Using this callback to release your resources helps provide a more responsive system overall, but also directly benefits the user experience for your app by allowing the system to keep your process alive longer. That is, if you don't trim your resources based on memory levels defined by this callback, the system is more likely to kill your process while it is cached in the least-recently used (LRU) list, thus requiring your app to restart and restore all state when the user returns to it.
可見ComponentCallbacks2
的主要作用就是在內(nèi)存狀態(tài)變化的時候通知應(yīng)用中的組件,讓應(yīng)用對其所占用的資源進(jìn)行適當(dāng)?shù)尼尫篷瑏斫档捅幌到y(tǒng)殺死的概率皆的。ComponentCallbacks2
提供的回調(diào)適用于 Application 的各種組件。
在onTrimMemory(int)
回調(diào)中,系統(tǒng)提供給我們一個int
型的Level值蹋盆,這個值代表著當(dāng)前系統(tǒng)可用內(nèi)存的狀態(tài)费薄。不同的值對應(yīng)不同的級別。
其中有一個值為TRIM_MEMORY_UI_HIDDEN
,對這個level值的定義如下:
Your app's UI is no longer visible, so this is a good time to release large resources that are used only by your UI.
沒錯就是它栖雾,這個值代表了當(dāng)前應(yīng)用的UI已不再可見楞抡。通過它,我們就可以認(rèn)為應(yīng)用進(jìn)入了后臺析藕。
清楚了原理之后召廷,實現(xiàn)就變得非常簡單了。首先我們要寫一個實現(xiàn)ComponentCallbacks2
的類,比如這里命名為MemoryBoss
:
public class MemoryBoss implements ComponentCallbacks2 {
@Override
public void onConfigurationChanged(final Configuration newConfig) {
}
@Override
public void onLowMemory() {
}
@Override
public void onTrimMemory(final int level) {
if (level == ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) {
// We're in the Background
}
// you might as well implement some memory cleanup here and be a nice Android dev.
}
}
當(dāng)然如果不想寫一個額外的類竞慢,使用匿名內(nèi)部類也是可以的先紫。
接著在應(yīng)用的 Application 的onCreate()
方法中注冊該回調(diào):
MemoryBoss mMemoryBoss;
@Override
public void onCreate() {
super.onCreate();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
mMemoryBoss = new MemoryBoss();
registerComponentCallbacks(mMemoryBoss);
}
}
如果不再需要該回調(diào),可以通過unregisterComponentCallbacks(mMemoryBoss)
來注銷它筹煮。不過一般不必要這么做遮精。
當(dāng)然,ComponentCallbacks2
還提供了許多不同的Level值來指明內(nèi)存當(dāng)前的狀態(tài)败潦。有興趣可以去Android Developers查閱本冲。
參考鏈接: