在之前的一篇文章《基于場景解讀Android四大組件》中談到Activity是Android提供給開發(fā)者的一個組件仆百,主要用于前臺界面的展示和交互金砍,為了讓開發(fā)者很方便的達到這個目的,Activity這個組件提供了兩大接口:生命周期和啟動模式,官方手冊上對于這兩塊描述的很詳細,但是我們在實際使用的過程中依然會遇到很多問題,所以今天我們結(jié)合實際使用場景來解讀Activity的這兩大接口瑞妇。
Activity的生命周期
這張圖大家應(yīng)該很熟悉了,這里我按使用場景來把它們分成三組:
- onCreate和onDestroy
- onStart梭冠、onRestart和onStop
- onResume和onPause
第一組使用頻率最高辕狰,為什么?因為它的使用場景是最多的控漠,在一個App里面我們會經(jīng)常需要打開(startActivity)和關(guān)閉(finish)一個頁面蔓倍。onCreate是Activity生命周期里面的第一步,當(dāng)我們進入到這一步時就表示一個Activity實例對象(從Java的角度看)已經(jīng)產(chǎn)生了盐捷,當(dāng)我們New了一個Java對象之后偶翅,首先要做的肯定是對其進行初始化了,那么onCreate就是Android提供給開發(fā)者用來對Activity實例對象中的成員做初始化的碉渡。Tips:Android為了方便對Activity組件的管理以及開發(fā)者使用聚谁,對Activity做了封裝,開發(fā)者不能直接new一個Activity對象(你也可以直接new滞诺,但是new出來的對象不會被Android管理垦巴,也就失去了界面的展示和交互的功能媳搪,跟普通Java對象無異).
在onCreate里面一般我們會做View的初始化操作,比如添加View(setContentView骤宣,addView等)和View中數(shù)據(jù)的填充(setText秦爆,setImage等),那么問題來了:為什么對View的初始化要放在這里憔披,可不可以放到其他地方(onStart或者onResume)等限?答案是肯定的,但是我們不建議這么做芬膝,因為放在onCreate里面可以保證初始化操作只做一次望门,而放到其他地方可能會調(diào)用多次,當(dāng)然你可以添加一個flag來標記是否已經(jīng)做過初始化锰霜,這樣就可以保證放在onStart或者onResume里面也只做一次初始化筹误,但是這樣不覺得多此一舉么?既然onCreate已經(jīng)幫我們實現(xiàn)了這么一個功能癣缅,為啥不用呢厨剪,當(dāng)然如果你有特殊需求,另當(dāng)別論友存。
還有就是在onCreate里面有個savedInstanceState參數(shù)祷膳,這個主要用于你的Activity在非正常情況下被銷毀前幫你自動保存的一些數(shù)據(jù),這些數(shù)據(jù)會在這個Activity被重新創(chuàng)建時用到屡立,因此Android將這個參數(shù)放在了onCreate里面直晨。注意,我這里說的是非正常情況銷毀Activity膨俐,這種場景比較多勇皇,比如系統(tǒng)內(nèi)存不夠用,系統(tǒng)語言改變焚刺,屏幕方向改變等儒士,如果你不清楚哪些是非正常情況,沒關(guān)系檩坚,只要清楚正常情況就行了着撩,其他的自然就都可以認為是非正常場景下的Activity銷毀行為。那么正常情況是什么匾委?用戶主動意愿想要銷毀Activity就是正常情況拖叙,這種場景很少,就兩種:調(diào)用finish和帶特殊啟動模式的startActivity方法赂乐。那么對于非正常情況下的onCreate我們在里面又該如何使用這個savedInstanceState薯鳍?那么就要搞清楚savedInstanceState會保存到哪些數(shù)據(jù),有兩種:系統(tǒng)幫你自動保存的和你自己保存的。系統(tǒng)只會保存它認為有必要保存的(比方說EditText里面的內(nèi)容挖滤,CheckBox的Check狀態(tài)崩溪,F(xiàn)ragment實例等),但是很多童鞋不知道Activity會自動保存其中的Fragment實例斩松,onCreate寫成這樣子:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);
getSupportFragmentManager().beginTransaction()
.add(R.id.container, FragmentBase.newInstance(this, "One", FragmentBase.class.getName()))
.add(R.id.container, FragmentFour.newInstance(this, "Two", FragmentFour.class.getName()))
.add(R.id.container, FragmentFive.newInstance(this, "Three", FragmentFive.class.getName()))
.commit();
}
這樣會有個問題伶唯,當(dāng)Activity在非正常情況下重啟時,由于系統(tǒng)已經(jīng)保存了FragmentBase惧盹,F(xiàn)ragmentFour和FragmentFive三個Fragment實例乳幸,而你又重新添加了三個Fragment實例,結(jié)果導(dǎo)致Activity中存在了6個Fragment實例钧椰。截圖如下:
這樣不僅浪費內(nèi)存資源還有可能會引發(fā)App行為異常粹断。所以在了解了savedInstanceState的作用后正確的寫法應(yīng)該是這樣的:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);
if (savedInstanceState != null) {
// 這里根據(jù)自己需要去從savedInstanceState中去數(shù)據(jù)
Fragment fragment = getSupportFragmentManager().findFragmentByTag(FragmentBase.class.getName());
if (fragment instanceof FragmentBase) {
FragmentBase base = (FragmentBase) fragment;
}
} else {
getSupportFragmentManager().beginTransaction()
.add(R.id.container, FragmentBase.newInstance(this, "One", FragmentBase.class.getName()))
.add(R.id.container, FragmentFour.newInstance(this, "Two", FragmentFour.class.getName()))
.add(R.id.container, FragmentFive.newInstance(this, "Three", FragmentFive.class.getName()))
.commit();
}
}
再說說onDestroy,執(zhí)行到這一步嫡霞,一般代表activity即將要被銷毀掉瓶埋,不管是正常情況還是非正常情況關(guān)閉activity。一般在這里面我們會做一些資源的釋放操作诊沪,以防止出現(xiàn)資源泄露或者依賴activity所引發(fā)的一些異常情況的發(fā)生养筒。這里我舉兩個例子來說下上面說的兩種情況:
- 異步任務(wù)引發(fā)的資源泄露,比如handler或者thread娄徊。這種情況發(fā)生的原因主要是異步任務(wù)的生命周期與activity生命周期不同步造成的,以handler中的message為例:
Handler handler = new Handler();
handler.postDelayed(new Runnable() {
@Override
public void run() {
tvContent.setText("newContent");
}
}, 2000);
handler.obtainMessage(1).sendToTarget();
不管是使用哪種形式來發(fā)送message盾戴,message都會直接或者間接引用到當(dāng)前所在的activity實例對象寄锐,如果在activity finish后,還有其相關(guān)的message在主線程的消息隊列中尖啡,就會導(dǎo)致該activity實例對象無法被GC回收橄仆,引起內(nèi)存泄露。所以一般我們需要在onDestroy階段將handler所持有的message對象從主線程的消息隊列中清除衅斩。示例如下:
@Override
protected void onDestroy() {
super.onDestroy();
if (handler != null) {
handler.removeCallbacksAndMessages(null);
}
}
- 異步任務(wù)引發(fā)的App運行異常盆顾,這里以一個顯示Dialog的場景為例:
Handler handler = new Handler();
handler.postDelayed(new Runnable() {
@Override
public void run() {
new AlertDialog.Builder(MainActivity.this).setMessage("Show Dialog").show();
}
}, 5000);
由于我們設(shè)置的是5秒后顯示一個dialog,當(dāng)activity在5秒內(nèi)被finish后可能會導(dǎo)致顯示dialog時App發(fā)生崩潰畏梆。
FATAL EXCEPTION: main
Process: xiaofei.com.fragmenttest, PID: 4645
android.view.WindowManager$BadTokenException: Unable to add window -- token android.os.BinderProxy@aebf1e6 is not valid; is your activity running?
at android.view.ViewRootImpl.setView(ViewRootImpl.java:567)
at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:310)
at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:85)
at android.app.Dialog.show(Dialog.java:319)
at android.support.v7.app.AlertDialog$Builder.show(AlertDialog.java:955)
at xiaofei.com.fragmenttest.Main2Activity$1.run(MainActivity.java:49)
at android.os.Handler.handleCallback(Handler.java:739)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:148)
at android.app.ActivityThread.main(ActivityThread.java:5417)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)
由于我們在activity的onDestroy中會銷毀activity對應(yīng)的窗體資源您宪,所以在顯示Dialog的時候由于dialog找不到父窗體就發(fā)生異常了。關(guān)于onDestroy中系統(tǒng)到底做了哪些資源清理的工作看下面的代碼就清楚了:
final void performDestroy() {
mDestroyed = true;
mWindow.destroy();
mFragments.dispatchDestroy();
onDestroy();
mFragments.doLoaderDestroy();
if (mVoiceInteractor != null) {
mVoiceInteractor.detachActivity();
}
}
所以在這種場景下正確的做法應(yīng)該是這樣的:
Handler handler = new Handler();
handler.postDelayed(new Runnable() {
@Override
public void run() {
if (!MainActivity.this.isDestroyed()) {
new AlertDialog.Builder(MainActivity.this).setMessage("Show Dialog").show();
}
}
}, 5000);
isDestroyed方法要求最低api level是17奠涌,一般我們目前app支持的最低api level是14宪巨,所以你可以在activity中添加一個flag來標記當(dāng)前activity的狀態(tài)是否被destroyed,其實activity源碼里面也是這么干的溜畅,依葫蘆畫瓢就行捏卓。
/**
* Returns true if the final {@link #onDestroy()} call has been made
* on the Activity, so this instance is now dead.
*/
public boolean isDestroyed() {
return mDestroyed;
}
onStart、onRestart和onStop
說實話這三個回調(diào)接口在實際使用場景中并不多慈格,對onStart怠晴、onRestart和onStop的使用可以從是否可見這點來找到它的正確使用姿勢遥金。這里舉幾個常用的場景:
- 對數(shù)據(jù)的時效性要求較高。以新聞類App為例:Activity A代表新聞列表蒜田,點擊列表中的一個Item進入到Activity B新聞詳情稿械,在從B返回到A的時候為了保證用戶能看到最新的新聞,就需要從服務(wù)器拉取最新的新聞列表數(shù)據(jù)并填充到Activity A物邑,那么這個工作就可以放在onStart里面溜哮,當(dāng)然你也可以放在onRestart里面,但是activity首次加載啟動的時候不會調(diào)用onRestart色解,所以也就不會去拉取新聞列表數(shù)據(jù)茂嗓。
- 需要顯示動畫效果。有些activity需要顯示一些動畫來幫助提升用戶體驗科阎,但是當(dāng)我們從該頁面進入到一個新頁面時述吸,由于該頁面已經(jīng)不可見了,所以就可以把當(dāng)前頁面中的動畫給關(guān)掉以節(jié)省系統(tǒng)資源锣笨,而這個工作就可以放在onStop中進行蝌矛。
onResume和onPause
這兩個接口使用的頻率比上一組要高,對onResume和onPause的使用可以從可以從是否獲得焦點(焦點即代表是是否可交互)這點來找到它的正確使用姿勢错英,這里也舉幾個常見場景:
- 結(jié)束占用CPU的動畫或者其他正在運行任務(wù)入撒,這種在使用地圖SDK的時候比較常見:
@Override
protected void onPause(){
mMapView.onPause();
super.onPause();
}
@Override
protected void onResume(){
mMapView.onResume();
super.onResume();
}
- 視頻播放,當(dāng)視圖組件獲得焦點時椭岩,即onResume中播放視頻茅逮,當(dāng)視圖組件失去焦點時,即onPause中暫停播放視頻判哥。
- 保存重要數(shù)據(jù)献雅,為了防止App被意外強殺,一般會在onPause中將一些重要數(shù)據(jù)保存到本地塌计。
其實一般情況下他們和onStart挺身、onRestart和onStop這一組里面做的事情可以是一樣的,也就是說放在onResume中執(zhí)行的任務(wù)也可以放在onStop中去做锌仅,放在onPause中執(zhí)行的任務(wù)也可以放到onStop中去做章钾。不過他們之間還是有區(qū)別的:可見性和可交互性。我們需要根據(jù)具體的需求來分析哪些任務(wù)可以在視圖可見或者不可見的時候做热芹,哪些任務(wù)可以在焦點獲得或者失去的時候去做伍玖。
Activity四種啟動模式
個人認為Activity四種啟動模式的設(shè)定出于兩種目的:
- 復(fù)用機制,節(jié)省系統(tǒng)資源剿吻,這種情況主要發(fā)生在除Standard模式外的三種模式上窍箍,通過他們的名字前綴Single也可以看出,跟Java中的單例模式有類似的思想,避免太多的實例對象創(chuàng)建開銷椰棘。
- 根據(jù)用戶的交互行為定義纺棺,因為Activity最終的目的還是完成用戶在各種交互場景下的需求。
下面結(jié)合具體使用場景來細說每種啟動模式:
Standard
這種啟動模式最常見邪狞,也是Activity的默認啟動模式祷蝌,每當(dāng)我們需要開啟一個新的Activity頁面時系統(tǒng)都會新建一個Activity實例對象,然后開啟上面說的Activity的生命周期流程之旅帆卓,onCreate->onStart->onResume巨朦。
SingleTop
設(shè)置該模式后,當(dāng)通過startActivity啟動的Activity與當(dāng)前Task 棧中最頂部的Activity一樣時剑令,系統(tǒng)不會重新創(chuàng)建一個Activity實例糊啡,而是進入一個特殊的方法onNewIntent,具體流程為:onNewIntent->onResume吁津。這種場景還是比較多的棚蓄,比方說商品詳情頁面一般都會有相關(guān)的商品推薦,點擊推薦的某個商品后進入的還是一個商品詳情頁面碍脏,這個時候就不需要重新再創(chuàng)建一個新的商品詳情Activity頁面梭依,直接復(fù)用已有的頁面,刷新下View中的數(shù)據(jù)就好了典尾。
SingleTask
設(shè)置該模式可以保證當(dāng)前Task棧中每種Activity只會有一個實例存在役拴,當(dāng)通過startActivity啟動Activity A時,如果當(dāng)前Task棧中已經(jīng)存在一個Activity A的實例钾埂,那么不再重新創(chuàng)建一個新的Activity實例河闰,而是直接復(fù)用該實例,進入該Activity的onNewIntent方法勃教,同時將位于Activity A實例之上的所有Activity彈出Task棧并銷毀淤击。這種場景在IM應(yīng)用中使用的比較多匠抗,比如QQ或者微信的聊天頁面故源,當(dāng)從聊天頁面進入其他頁面,然后在重新進入聊天頁面時就會直接進入原來的聊天頁面汞贸,同時銷毀中間新創(chuàng)建的Activity頁面绳军,并刷新聊天頁面的數(shù)據(jù)。
SingleInstance
設(shè)置該模式可以保證該Activity所在的Task中有且僅有一個activity實例矢腻,當(dāng)通過startActivity啟動Activity A時门驾,如果該Activity的實例已經(jīng)存在,那么不再重新創(chuàng)建一個新的Activity實例多柑,而是直接復(fù)用該實例奶是,進入該Activity的onNewIntent方法。這種場景出現(xiàn)的比較少,該Activity在整個系統(tǒng)只有一個實例聂沙,一般用于系統(tǒng)應(yīng)用秆麸,并且可以被其他應(yīng)用共享使用(有點類似于操作系統(tǒng)概念中的臨界資源),比方說來電呼叫頁面及汉,在整個系統(tǒng)中就只能有一個沮趣,因為同一時刻只能存在一個電話呼叫。
可以發(fā)現(xiàn)后面三種模式的原理其實跟一些App中頁面的交互流程比較類似坷随,可能Android也是考慮到為了更方便實現(xiàn)這種交互方式而定義了這三種特殊的啟動模式房铭。其實不管是哪種啟動方式最終都會影響到Activity的生命周期流程,因此我們在啟動一個Activity頁面的時候需要留意其對該activity生命周期回調(diào)的影響温眉,并做相應(yīng)的處理邏輯缸匪。此外這四種啟動方式一般都會在AndroidManifest中寫死,同時也可以根據(jù)需要在代碼中動態(tài)配置芍殖,代碼中可使用的啟動標志一般有以下三個:
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
一般將Intent.FLAG_ACTIVITY_NEW_TASK和Intent.FLAG_ACTIVITY_CLEAR_TOP搭配使用實現(xiàn)類似SingleTask的效果豪嗽,將Intent.FLAG_ACTIVITY_NEW_TASK和Intent.FLAG_ACTIVITY_SINGLE_TOP搭配使用實現(xiàn)類似SingleTop的效果。
到此豌骏,關(guān)于Activity的兩大主題:生命周期和啟動模式就基本講完了龟梦。當(dāng)然Activity作為與用戶交互的入口,所包含的內(nèi)容還遠不止這些窃躲,比如與Activity關(guān)聯(lián)的Window计贰,View,還有Dialog等等蒂窒。如何正確理解他們與Activity的關(guān)系躁倒,正確的使用他們并與Activity配合來完成用戶的交互,后面再單獨分析洒琢。