你沒看錯,如今是2018年,我正在寫一篇連我本身都感到很驚訝的關於Android系統的Activity生命週期的文章。android
一開始,我以爲Activity的生命週期雖然過於複雜,但它不該該是一個難題。個人意思是:對於Android開發新手來講,如何正確地處理Activity生命週期可能有點困難,可是我沒法想象對於那些富有經驗的android開發者來講,這依然是一個棘手的問題。安全
我仍是想的太簡單了。bash
一下子我會告訴你整個故事,可是先讓我簡述下我寫這篇文章的目的。數據結構
我想要與大家分享我是如何處理Activity的生命週期的,它比官方文檔裏描述的更簡單,並且還涵蓋絕大多數棘手的極端情形。框架
一個星期內發生的兩件事情讓我意識到:即便在今天,Activity的生命週期仍然是Android開發人員面臨的一個難題。異步
幾周前一位Redditor在「androiddev」裏分享了一篇關於Activity的生命週期的文章。這篇文章的寫做基礎來源於我在StackOverflow社區裏分享的一個答案。當時提問者推薦了一種處理Activity的生命週期的方法,我以爲並不許確,因而我當即提交了一條評論,但願其餘讀者能意識到這一點。async
而後陸續有幾位redditor回答並質疑了個人觀點,進而演變成了一場漫長而很是有見地的討論。在討論過程當中,我觀察到了一些有趣的關於Activity的生命週期的現象:ide
幾天以後,我正在訪問一個潛在客戶。該公司僱傭了一批富有經驗的Android開發人員,他們嘗試在老的代碼庫裏添加新功能,這不是一件容易的事。函數
在快速代碼審查期間,我注意到其中一位維護人員決定在Activity的onCreate(Bundle)中訂閱EventBus,並在onDestroy()中取消訂閱。這樣作會帶來很大的麻煩,因此我建議他們重構這部分代碼。動畫
上述兩件事讓我意識到仍是有許多android開發人員對Activity的生命週期充滿困惑。如今,我想嘗試經過分享個人經驗來給其餘開發人員提供便利。
我不打算在這裏爲Activity的生命週期從新編寫一份文檔,這代價太大了。我會告訴你如何在Activity生命週期的各個方法之間劃分邏輯以實現最簡單的設計並避免最多見的陷阱。
Android framework並無提供Activity的構造函數,它會自動建立Activity類的實例。那麼咱們應該在哪裏初始化Activity呢?
你能夠把全部應該在Activity的構造函數裏處理的邏輯放在onCreate(Bundle)這個方法裏。
理想狀況下,構造函數只會初始化對象的成員變量:
public ObjectWithIdealConstructor(FirstDependency firstDependency,
SecondDependency secondDependency) {
mFirstDependency = firstDependency;
mSecondDependency = secondDependency;
}
複製代碼
除了初始化成員變量外, onCreate(Bundle)方法還承擔着另外兩個職責:恢復以前保存的狀態並調用setContentView()方法。
因此,onCreate(Bundle)方法中的基本功能結構應該是:
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getInjector().inject(this); // inject dependencies
setContentView(R.layout.some_layout);
mSomeView = findViewById(R.id.some_view);
if (savedInstanceState != null) {
mWelcomeDialogHasAlreadyBeenShown = savedInstanceState.getBoolean(SAVED_STATE_WELCOME_DIALOG_SHOWN);
}
}
複製代碼
顯然,這並不適用於全部人,你可能會有一些不一樣的結構。不過不要緊,只要你在onCreate(Bundle)中沒有如下內容:
每當我須要決定某一段邏輯是否應該放在onCreate(Bundle)方法裏時,我就會問本身這個問題:這段邏輯與Activity的初始化有關嗎?若是答案是否認的,我就會另尋別處。
當Android framework建立了一個Activity實例,並且它的onCreate(Bundle) 方法執行完畢以後,這個Activity就會處於已建立狀態。
處於已建立狀態的Activity 並不會觸發資源分配,也不會接收到系統裏其餘對象發出的事件。從這種意義上來說,「created」狀態是一種已經準備好了,可是不活躍和隔離的狀態。
爲了使Activity可以與用戶交互,Android framework接着會調用Activity的onStart()方法使其處於活躍狀態。onStart() 方法中的一些基本的邏輯處理包括:
對於第1點和第2點,你可能會感到很驚訝。由於在大多數官方文檔和教程裏,它們都會被放在onCreate(Bundle)裏。我認爲這是對複雜的Activity的生命週期的一種誤解,我會在接下來的onStop()方法裏講到這一點。
因此,onStart()方法中的基本功能結構應該是:
@Override
public void onStart() {
super.onStart();
mSomeView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
handleOnSomeViewClick();
}
});
mFirstDependency.registerListener(this);
switch (mSecondDependency.getState()) {
case SecondDependency.State.STATE_1:
updateUiAccordingToState1();
break;
case SecondDependency.State.STATE_2:
updateUiAccordingToState2();
break;
case SecondDependency.State.STATE_3:
updateUiAccordingToState3();
break;
}
if (mWelcomeDialogHasAlreadyBeenShown) {
mFirstDependency.intiateAsyncFunctionalFlowAndNotify();
} else {
showWelcomeDialog();
mWelcomeDialogHasAlreadyBeenShown = true;
}
}
複製代碼
須要強調的一點是,你實際在onStart()中所執行的操做是由每一個特定Activity的詳細需求決定的。
在Android framework調用完onStart()方法後,Activity將從已建立狀態轉換爲已啓動狀態。在此狀態下,它能夠正常工做,而且能夠與系統的其餘組件協做。
關於onResume()方法的第一條準則是你不須要覆蓋onResume()方法。第二條準則也是你不須要覆蓋onResume()方法。第三條準則是在某些特殊狀況下你纔會確實須要覆蓋onResume()方法。
我搜索了個人一個項目的代碼庫,發現有32個onStart()方法被覆蓋重寫,平均每段代碼約5行,一共有大約150多行代碼。相反,只有2個onResume()方法被覆蓋重寫,一共8行代碼,這8行代碼主要是用於恢復播放動畫和視頻。
這總結了我對onResume()的見解:它只能用於在屏幕上啓動或恢復一些動態的對象。你想在onResume()而不是onStart()中作這件事的緣由將在稍後的onPause()部分討論。
在Android framework調用完onResume()方法後,Activity將從已啓動狀態轉換爲"resumed"狀態。在此狀態下,用戶能夠與它進行交互。
在這個方法裏,您應該暫停或中止在onResume()中恢復或啓動的動畫和視頻。就像onResume()同樣,你幾乎不須要重寫onPause()。在onPause()方法而不是onStop()方法中處理動畫的緣由是由於當Activity被部分隱藏(好比,彈出系統對話框)或者是在多窗口模式下失去焦點的時候,只有onPause()方法會被調用。所以若是你想在 只有用戶正在與Activity交互的狀況下播放動畫,同時避免在多窗口模式下分散用戶注意力並延長電池壽命,onPause() 是你惟一能夠信賴的選擇。 這一結論的前提是你但願你的動畫或視頻在用戶進入多窗口模式時中止播放,若是你但願你的動畫或視頻在用戶進入多窗口模式時繼續播放,那麼你不該該在onPause()中暫停它。在這種狀況下,你須要將onResume()/ onPause()中的邏輯移動到onStart()/ onStop()。
一種特殊的狀況是相機的使用,因爲相機是全部應用程序共享的單一資源,所以一般您會想要在onPause()方法中釋放它。
在Android framework調用完onPause()方法後,Activity將從"resumed"狀態轉換爲已啓動狀態。
在這個方法裏,您將註銷全部觀察者和監聽者,並釋放onStart()中分配的全部資源。
因此,onStop()方法裏的基本功能結構應該是:
@Override
public void onStop() {
super.onStop();
mSomeView.setOnClickListener(null);
mFirstDependency.unregisterListener(this);
}
複製代碼
讓咱們來討論一個問題:爲何你須要在這個方法裏取消註冊?
首先,若是此Activity不被mFirstDependency取消註冊的話,您可能會泄漏它。這不是我願意承擔的風險。
因此,問題變成了爲何要在onStop()方法裏取消註冊,而不是onPause()方法或者是onDestroy()方法?
想要快速清晰地解釋這些確實有點棘手。
若是在onPause()方法裏調用mFirstDependency.unregisterListener(this),那麼Activity將不會收到相關異步流程完成的通知。所以,它不能讓用戶感知到這一事件,從而徹底違背了多窗口模式的設計初衷,這不是一種好的處理方式。
若是在onDestroy()方法裏調用mFirstDependency.unregisterListener(this),這一樣不是一種好的處理方式。
當應用被用戶推到後臺(例如,點擊「home」按鈕)時,Activity的onStop()將被調用,從而使得其返回到「已建立」狀態,這個Activity能夠在幾天甚至幾周的時間內保持這個狀態。
若是這時候mFirstDependency產生了連續的事件流,那麼在這幾天甚至幾周的時間裏,Activity能夠都處理這些事件,即便用戶在這段時間內從未真正與它交互過。這將是對用戶電池壽命的不負責任的浪費,並且在這種狀況下,應用消耗的內存會逐漸增多,應用進程被OOM(Out Of Memory)Killer殺死的可能性也會增大。
所以,在onPause()和onDestroy()裏調用mFirstDependency.unregisterListener(this)都不是一種好的處理方式,您應該在onStop()中執行此操做。
關於註銷View的事件監聽器,我想多說幾句。
因爲Android UI框架的不合理設計,像這個問題中所描述的奇怪場景是可能會發生的。有幾種方法能夠解決這個問題,可是從onStop()中註銷View的事件監聽器是最乾淨的一種。
或者,您能夠徹底忽略這種罕見的狀況。這是大多數Android開發人員所作的,可是,您的用戶偶爾會遇到一些奇怪的行爲和崩潰。
在Android framework調用完onStop()方法後,Activity將從已啓動狀態轉換爲已建立狀態。
永遠都不要覆蓋重寫這個方法。
我搜索了我全部的項目代碼庫,沒有一個地方須要我重寫onDestroy()方法。
若是你思考一下我前面對onCreate(Bundle)的職責的描述,你會發現這是徹底合理的,它僅僅執行了初始化Activity對象的操做,因此徹底沒有必要手動完成清理工做。
當我在查看新的Android代碼庫時,我一般會搜索幾個常見的錯誤模式,這使我可以快速地瞭解代碼的質量。 onDestroy()的覆蓋重寫是我尋找的第一個錯誤模式。
此方法用於保存一些臨時的狀態,在這個方法裏你位移須要作的就是將你想保存的狀態存入Bundle數據結構中:
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putBoolean(SAVED_STATE_WELCOME_DIALOG_SHOWN, mWelcomeDialogHasAlreadyBeenShown);
}
複製代碼
在這裏,我想提一個與Fragment有關的常見陷阱。
若是你在這個方法執行完以後,提交Fragment事務,那麼應用程序會拋出IllegalStateException異常而致使崩潰。
你須要知道的一點是onSaveInstanceState(Bundle)將在onStop()以前被調用。
好消息是,通過多年的努力,谷歌終於意識到了這個bug。 在Android P中,onStop()將在onSaveInstanceState(Bundle)以前被調用。
是時候結束這篇文章了。
雖然本文的內容既不簡短也不簡單,但我認爲它比大多數文檔(包括官方教程)更簡單,更完整。
我知道一些經驗豐富的專業開發人員會質疑我處理Activity生命週期的方式,不要緊。事實上,我很是期待您的質疑。
不過,請記住,我已經使用這個方案好幾年了。根據個人經驗,使用這種方法編寫的代碼比許多項目中看到的對於Activity生命週期的處理邏輯要清晰簡潔的多。
這種方法的最終驗證來自Google自己。
從Nougat開始,Android系統支持多屏任務。我在這篇文章中與你分享的方法幾乎不須要任何調整。這基本上證明,相對於官方文檔,它包含了對Activity生命週期更深刻的看法。
另外,Android P中對於Activity的onStop()和onSaveInstanceState(Bundle)方法之間的調用順序的調整將使得這種方法對於Fragments的使用來講是最安全的。
我不認爲這是巧合。