時間過得飛快一轉(zhuǎn)眼一年時間就過去了,先小小的感嘆下啊。回到正題,最近在投簡歷看看是否有更好的機(jī)會和平臺,很高興得到了愛奇藝的面試機(jī)會。在準(zhǔn)備期間發(fā)現(xiàn)自己雖然平時輸入了很多但都沒有整理時間一久都忘記了(過完年感覺都就著飯消化了)。還有發(fā)現(xiàn)其實很多Android的基礎(chǔ)知識并不是很深入細(xì)節(jié)的了解,所以決定堅持把學(xué)到的知識點進(jìn)行總結(jié),與大家共同進(jìn)步。
首先先從最基本Android的四大組件:
- Activity
- Service
- Broadcast receiver
- Content provider
首先我就從第一個Activity開始學(xué)習(xí)總結(jié),Activity被中文翻譯成“活動”,但我覺得《Android開發(fā)藝術(shù)探索》的作者任玉剛翻譯的更貼切,他翻譯成“界面”。接下來的知識點總結(jié)其實也是基于此書的內(nèi)容。Activity是我們Android開發(fā)人員最熟悉的組件了(不熟悉的請拖出去。。。)在我們的日常開發(fā)中每個UI界面就可以簡單的理解成一個Activity。關(guān)于它的技術(shù)點其實有很多值得總結(jié)的,此篇文章只針對它的生命周期進(jìn)行總結(jié)。
1.1 Activity的生命周期分析
1.1.1 簡述生命周期方法
在正常情況下Activity的創(chuàng)建到銷毀會經(jīng)歷如下的生命周期。
onCreate() :首次創(chuàng)建Activity時調(diào)用,這是生命周期的第一個方法。在這里你可以做一些靜態(tài)設(shè)置,如:調(diào)用setContentView()方法來設(shè)置視圖、該Activity所需要的一些初始化操作等。該方法會傳入一個Bundle對象,其中包含Activity的上一狀態(tài)信息,不過前提是捕獲了該狀態(tài),后續(xù)也會提及此知識點。它始終后接onStart()。
onRestart():在Activitie已停止后再次啟動前調(diào)用,表示Activity正在重新啟動。一般情況下當(dāng)Activity從不可見狀態(tài)變?yōu)榭梢姞顟B(tài)時,onRestart()就會被調(diào)用。換一種更專業(yè)的說法既是當(dāng)前Activity處于非棧()頂狀態(tài)又再次回到棧頂狀態(tài)時,此時就會觸發(fā)此方法。這種情形一般都是用戶行為操作導(dǎo)致的,如用戶在當(dāng)前界面按Home鍵切換到桌面或者用戶打開了一個新的Activity,這時當(dāng)前的Activity就會處于后臺不可見狀態(tài),也就是onPause()和onStop()被執(zhí)行了,但并沒用執(zhí)行onDestroy(),接著用戶又回到了這個Activity,此時就會調(diào)用onRestart()。它始終后接onStart()。
onStart():在Activity即將對用戶可見之前調(diào)用。此時Activity對用戶還不可見不可以進(jìn)行交互。如果Activity轉(zhuǎn)入前臺,則后接onResume(),如果Activity轉(zhuǎn)入隱藏狀態(tài),則后接onStop()。
onResume():表示Activity已經(jīng)處在棧頂,并且捕獲用戶的所有輸入,可以與用戶進(jìn)行交互。這里注意這個點"可以與用戶交互",這也是與onStart()方法最大的區(qū)別。onStart與onResume都表示Activity已經(jīng)可見,但是onStart的時候相對用戶還是處于后臺,并不能與用戶進(jìn)行交互。可以簡單的總結(jié)成,onStart表示可見但不能與用戶交互;onResume表示即可見又可以跟onStart進(jìn)行交互。
-
onPause():表示用戶正在離開當(dāng)前Activity,Activity失去焦點并進(jìn)入暫停狀態(tài)。通常情況下接下來onStop方法會被調(diào)用,但有些極端情況下,如果當(dāng)前快速地再回到當(dāng)前Activity,那么onResume會被調(diào)用。這是一種極端情況,用戶操作很難重現(xiàn)這一場景。這里官方建議做一些輕量級的釋放資源的操作,不能做太耗時的操作,因為這會影響到新Activity的顯示,onPause必須先執(zhí)行完畢,新Activity的才會開始創(chuàng)建并執(zhí)行到新的Activity的onResume狀態(tài)。
這里還有提一下官方列舉了進(jìn)入onPause狀態(tài)的情景:- 一些阻礙APP運行的事件發(fā)生時,這也是最通常的情況。
- 再Android7.0(API 24)或更高版本時,多個APP運行在多個窗口中。此時系統(tǒng)會給不處于焦點狀態(tài)的Activity調(diào)用此方法。
- 開啟一個半透明的Activity(如一個只包含Dialog的Activity),當(dāng)前Activity部分可見并沒有焦點,此時當(dāng)前Activity處于onPause狀態(tài)。這里強(qiáng)調(diào)一下開啟一個新的半透明的Activity,之前我的理解有偏差,以為只要當(dāng)前界面開啟了一個Dialog,當(dāng)前Activity就會處于onPause狀態(tài),其實并不是。
關(guān)于這點網(wǎng)上也有很多解釋的文章,大體意思既是只有當(dāng)前Activity不再處于Activity棧的棧頂時才會被調(diào)用。Dialog本身不是一個Activity而需要依附在Activity上,所以它并不能引起Activity棧的變化即不能觸發(fā)onPause狀態(tài)。但這里還有一種常見的dialog功能實現(xiàn)的方法,即是用一個半透明背景的Activity來實現(xiàn)dialog效果。此時相當(dāng)于開啟了一個新的Activity,導(dǎo)致當(dāng)前Activity并在處于棧頂,所以就會觸發(fā)onPause。
-
onStop(): 表示當(dāng)前Activity不再顯示給用戶,即將終止。這里可以做一些稍微重量級的操作,但同樣也不能太耗時。同時官方文檔里有這樣一句話:
It is also important that you use onStop() to release resources that might leak memory, because it is possible for the system to kill the process hosting your activity without calling the activity's final onDestroy() callback.
翻譯過來就是你應(yīng)該在onStop方法內(nèi)完成可能造成內(nèi)存泄漏的資源釋放,因為當(dāng)系統(tǒng)去殺掉你Activity所在的進(jìn)程時,可能最終不會調(diào)用onDestroy方法。
onDestroy(): 表示該Activity即將被銷毀。這是Activity生命周期中的最后一個回調(diào),在這里我們可以釋放onStop中還未釋放的資源的操作。
正常情況下Activity只會經(jīng)歷以上的生命周期回調(diào),下面附一張官網(wǎng)的圖片,清晰地展示了各生命周期方法的流程:
以上就是Activity正常生命周期狀態(tài)變化時所需要回調(diào)的方法。但有時系統(tǒng)會經(jīng)歷一些特殊情況,如內(nèi)存不足、系統(tǒng)資源配置發(fā)生改變等,下面我們就來一起學(xué)習(xí)異常情況下Activity生命周期是如何切換的。
1.1.2 異常情況下的生命周期分析
1. 情況1:資源相關(guān)的系統(tǒng)配置發(fā)生改變導(dǎo)致Activity被殺掉并重新創(chuàng)建
這里提到的系統(tǒng)配置發(fā)生改變包括很多種情況,如常見的屏幕旋轉(zhuǎn)、系統(tǒng)改變語言、Android 7.0以上系統(tǒng)多窗口切換等。當(dāng)這些系統(tǒng)配置情況改變時,Activity就會被銷毀后重建。
在默認(rèn)情況下,如果我們的Activity不做任何處理,那么當(dāng)系統(tǒng)配置發(fā)生改變后,它的生命周期就會如下變化:
當(dāng)系統(tǒng)配置發(fā)生變化的時候Activity會被銷毀重建,也會調(diào)用onPause、onStop、onDestroy這三個生命周期方法。同時因為Activity是在異常情況下停止的,系統(tǒng)還會調(diào)用onSaveInstanceState。關(guān)于這個方法官網(wǎng)是如下介紹的:
The onSaveInstanceState() callback is designed to store relatively small amounts of data needed to easily reload the state of UI controller, such as an activity or a fragment, if the system stops and later recreates that controller.
翻譯一下即是,onSaveInstanceState()方法是在被設(shè)計成保存相對小數(shù)據(jù)量的用于恢復(fù)UI控制器(如Activity、Fragment)的狀態(tài)的數(shù)據(jù)。它會在系統(tǒng)停止UI控制器并且稍后要重新創(chuàng)建UI控制器時調(diào)用。同時官網(wǎng)也表明了該方法被調(diào)用的情景:
- 在系統(tǒng)內(nèi)存受限時,系統(tǒng)會殺掉處于后臺的APP進(jìn)程
- 當(dāng)系統(tǒng)配置發(fā)生變化時,如屏幕旋轉(zhuǎn)或者輸入語言變化
所以這里需要強(qiáng)調(diào)一點,正常情況下Activity并不會回調(diào)這個方法(例如用戶點擊back鍵finish掉當(dāng)前Activity
),只有當(dāng)Activity被異常終止時才會回調(diào)這個方法。這個方法的調(diào)用事件在onStop之前,它與onPause沒有對應(yīng)的時間關(guān)系。它內(nèi)部通過Bundle對象來存儲簡單的數(shù)據(jù),并在重建時將該Bundle對象同時傳遞給onCreate和onRestoreInstanceState。我們擇其一,在重建時進(jìn)行恢復(fù)操作。二者的區(qū)別是:
- 調(diào)用時機(jī)不同,onCreate是重建時第一個調(diào)用的方法,而onRestoreInstanceState會在onStart后進(jìn)行回調(diào)。
- 恢復(fù)方式不同,onCreate方法的Bundle參數(shù)可能為null,所以使用時需要加入判斷代碼。而onRestoreInstanceState一旦調(diào)用就代表Activity被異常關(guān)閉的,調(diào)用過了onSaveInstanceState方法,所以onRestoreInstanceState方法的Bundle參數(shù)一定不為null,不需要判斷可以讀取恢復(fù)數(shù)據(jù)。
此外,還需要注意onSaveInstanceState和onRestoreInstanceState兩個方法,必須總是調(diào)用superclass的同名方法。因為系統(tǒng)默認(rèn)為我們做了一些保存和恢復(fù)的操作,比如文本框輸入的數(shù)據(jù),列表停留的視圖位置等。這里需要知道每個View都有onSaveInstanceState和onRestoreInstanceState這兩個方法,具體實現(xiàn)的話各位看官可以自行查看下。關(guān)于View恢復(fù)的流程是這樣的:Activity被意外關(guān)閉 ,Activity會調(diào)用onSaveInstanceState去保存數(shù)據(jù),然后Activity會委托Window去保存數(shù)據(jù),Window接著會委托它上面的頂層容器去保存數(shù)據(jù),一般情況下就是DecorView。最后頂層View會繼續(xù)再去一一通知它的子元素來保存數(shù)據(jù)。這是一種典型的委托思想,上層委托下層、父容器委托子元素去處理一件事情,在Android中很多應(yīng)用,如事件分發(fā)機(jī)制、View繪制機(jī)制等。數(shù)據(jù)恢復(fù)的流程與保存流程也是類似的。這里還需要重點提一下關(guān)于View的自動恢復(fù)狀態(tài)的條件,其中一個就是必須設(shè)置唯一的ID值。大家請看View源碼中的dispatchSaveInstanceState()方法:
protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) {
if (mID != NO_ID && (mViewFlags & SAVE_DISABLED_MASK) == 0) {
mPrivateFlags &= ~PFLAG_SAVE_STATE_CALLED;
Parcelable state = onSaveInstanceState();
if ((mPrivateFlags & PFLAG_SAVE_STATE_CALLED) == 0) {
throw new IllegalStateException(
"Derived class did not call super.onSaveInstanceState()");
}
if (state != null) {
// Log.i("View", "Freezing #" + Integer.toHexString(mID)
// + ": " + state);
container.put(mID, state);
}
}
}
這里mID
即是我們通過xml布局中android:id
屬性賦值或調(diào)用setId()
方法賦值的。大家可以清楚的看到如果mID
的值為NO_ID
時,就不會調(diào)用container.put(mID,state)
這條語句。這里的container
類型為SparseArray<Parcelable>
即是用mID
為key,上一級傳入的包含View狀態(tài)的Parcelable對象
為value。
2. 情況2:資源內(nèi)存不足導(dǎo)致低優(yōu)先級的Activity被殺死
此種情況Activity的數(shù)據(jù)存儲和恢復(fù)過程和情況1完全相同。這里要提一下Android系統(tǒng)中一個不尋常的一點,一個程序的進(jìn)程生命周期并不由程序本身直接管理,而是由系統(tǒng)決定。系統(tǒng)建立了一套進(jìn)程優(yōu)先級制度來管理各個進(jìn)程,所以這也是需要我們開發(fā)人員注意學(xué)習(xí)的地方。其中官網(wǎng)就列出了一個比較容易出錯的點:
A common example of a process life-cycle bug is a BroadcastReceiver that starts a thread when it receives an Intent in its BroadcastReceiver.onReceive() method, and then returns from the function. Once it returns, the system considers the BroadcastReceiver to be no longer active, and thus, its hosting process no longer needed (unless other application components are active in it). So, the system may kill the process at any time to reclaim memory, and in doing so, it terminates the spawned thread running in the process. The solution to this problem is typically to schedule a JobService from the BroadcastReceiver, so the system knows that there is still active work being done in the process.
翻譯過來即是,很容易出bug的點即是在BroadcastReceiver.onReceive()方法內(nèi)部開啟一個線程去執(zhí)行一個耗時操作時,因為BroadcastReceiver的生命周期很短,當(dāng)onReceive()方法運行完成后系統(tǒng)就認(rèn)為BroadcastReceive不再活動,所以在它的進(jìn)程(如果沒有其他程序組件在運行)就會隨時被系統(tǒng)回收,導(dǎo)致你的耗時操作根本不能運行出結(jié)果。所以官方也給出了解決方式:在BroadcastReceiver中開啟一個JobService,這樣當(dāng)onReceive()方法結(jié)束后,進(jìn)程中仍然有Service組件在運行,進(jìn)程優(yōu)先級較高,系統(tǒng)就不會輕易收回此進(jìn)程了。
好我們來總結(jié)一下Android系統(tǒng)對進(jìn)程優(yōu)先級制度是如何制定的,優(yōu)先級順序由高到低:
- 前臺進(jìn)程:優(yōu)先級最高,具備下列條件的進(jìn)程會被定義為前臺進(jìn)程:
- 一個正在運行在屏幕上的Activity,用戶正在與它進(jìn)行交互。(也就是說它的onResume方法被調(diào)用)。
- 當(dāng)一個BroadcastReceive正在運行時,即是它的onReceive方法正在被在被執(zhí)行時。
- 當(dāng)一個Service正在執(zhí)行回調(diào)方法時,即是它的onCreate()、onStart()、onDestroy()正在被調(diào)用時。
當(dāng)內(nèi)存過低時,前臺進(jìn)程作為最后一個被系統(tǒng)回收的進(jìn)程。或者當(dāng)前臺進(jìn)程本身已經(jīng)無法繼續(xù)運行時,也會被系統(tǒng)回收。
- 可見進(jìn)程: 優(yōu)先級次之,系統(tǒng)結(jié)束此類進(jìn)程也會給用戶帶來明顯的負(fù)面影響。它的條件是:
- 一個對用戶可見的Activity,但未處于前臺(如該Activity正處于onPause狀態(tài))。通常發(fā)生的情況是當(dāng)一個前臺Activity顯示一個dialog,而之前的Activity處于它的后邊同樣能夠被用戶看到。另一個例子是當(dāng)你調(diào)用運行時權(quán)限對話框時。
- 當(dāng)一個Service作為一個前臺Service在運行,通過調(diào)用它的startForeground()方法。
- 一個綁定到可見或處于前臺Activity的Service,如:動態(tài)壁紙Service、輸入法Service等。
但請記住雖然前臺進(jìn)程不易被殺掉,但如果前臺進(jìn)程對內(nèi)存壓力過大,可見進(jìn)程仍有可能被系統(tǒng)殺掉。舉例當(dāng)前Activity背后可見的Activity被殺掉后,對用戶來說將看到一個黑屏背景。所以你需要在Activity的onSaveInstanceState()和onRestoreInstanceState()兩個方法來做存儲和恢復(fù)界面的操作,及時的彌補(bǔ)這個問題。
服務(wù)進(jìn)程:一個Service通過startService()方法被開啟。通常就是指那些在后臺做下載或上傳操作的Service。錯非內(nèi)存無法保證前臺進(jìn)程和可見進(jìn)程的運行,才會殺掉服務(wù)進(jìn)程。這里需要注意,當(dāng)一個服務(wù)進(jìn)程運行過長(如30分鐘或更久)系統(tǒng)會降低它的重要級別。把它放入緩存進(jìn)程中,這是為了避免一個長時間運行的服務(wù)導(dǎo)致內(nèi)存泄漏或消耗過大的問題。
緩存進(jìn)程: 當(dāng)前不被需要的,系統(tǒng)可以在需要時隨時殺掉的進(jìn)程。一般場景下,設(shè)備上的許多內(nèi)存就是用在這上面的,讓你重新回到之前打開過的某個 Activity 。Activity不是為了被殺而創(chuàng)建的(記住重新創(chuàng)建需要代價的),所以這些進(jìn)程會保留一段時間,按照最近最少使用的原則回收。
最后,一個進(jìn)程的優(yōu)先級可能會被提高基于它所依賴的其他更高優(yōu)先級的進(jìn)程。例如當(dāng)一個進(jìn)程A綁定了一個運行在B進(jìn)程的Service設(shè)置flag
參數(shù)為Context.BIND_AUTO_CREATE
,此時B進(jìn)程擁有跟A進(jìn)程一樣的優(yōu)先級。
從上面的一系列總結(jié)也可以了解到,當(dāng)一個進(jìn)程中沒有Android四大組件運行,它的優(yōu)先級為最低的,隨時可能被系統(tǒng)回收。
我們再來看看Activity的優(yōu)先級情況排序:
- 前臺Activity: 正在和用戶進(jìn)行交互,處于onResume和onPause之間。
- 可見但非前臺Activity: 比如上面提到的彈出運行時權(quán)限對話框,處于onPause。
- 后臺Activity: 已經(jīng)被切換的Activity,處于onStop。
3. 系統(tǒng)配置改變的應(yīng)對方法
我們知道當(dāng)系統(tǒng)配置發(fā)生改變后,Activity會被重新創(chuàng)建。那如何阻止重新創(chuàng)建呢?我們可以在AndroidManifest清單文件中通過設(shè)置Activity的configChanges屬性來阻止系統(tǒng)的默認(rèn)處理方式。比如我們想讓屏幕旋轉(zhuǎn)時不重新創(chuàng)建Activity,只需將configChanges屬性添加orientation這個值。如:
android:configChanges="orientation"
如果我們想指定多個值,可以用"|"連接起來。比如:android:configChanges="orientation|keyboardHidden"。關(guān)于這個屬性的配置值請看下圖:
這里官方關(guān)于配置屏幕旋轉(zhuǎn)情況有一個特別提醒如下:
注意:從 Android 3.2(API 級別 13)開始,當(dāng)設(shè)備在縱向和橫向之間切換時,“屏幕尺寸”也會發(fā)生變化。因此,在開發(fā)針對 API 級別 13 或更高版本(正如
minSdkVersion
和targetSdkVersion
屬性中所聲明)的應(yīng)用時,若要避免由于設(shè)備方向改變而導(dǎo)致運行時重啟,則除了"orientation"
值以外,您還必須添加"screenSize"
值。 也就是說,您必須聲明android:configChanges="orientation|screenSize"
。但是,如果您的應(yīng)用面向 API 級別 12 或更低版本,則 Activity 始終會自行處理此配置變更(即便是在 Android 3.2 或更高版本的設(shè)備上運行,此配置變更也不會重啟 Activity)。
具體的測試代碼我就不寫了,這里需要說明的是當(dāng)設(shè)置了相關(guān)屬性值以后。系統(tǒng)配置再發(fā)生相應(yīng)變化時,將不再調(diào)用onSaveInstanceState和onRestoreInstanceState兩個方法,取而代之的是調(diào)用了Activity的onConfigurationChanged方法。這個時候我們可以在這里做一些特殊處理。
寫到這我要說的是,以上配置變化的應(yīng)對方式是我們開發(fā)當(dāng)中常見的處理方式。但官方并不建議開發(fā)人員這樣處理:
注:自行處理配置變更可能導(dǎo)致備用資源的使用更為困難,因為系統(tǒng)不會為您自動應(yīng)用這些資源。 只能在您必須避免 Activity 因配置變更而重啟這一萬般無奈的情況下,才考慮采用自行處理配置變更這種方法,而且對于大多數(shù)應(yīng)用并不建議使用此方法。
其實關(guān)于為什么官方不建議采用此種方式的原因,我也搜索到了一個開發(fā)者的看法。其中一位的觀點我覺得比較容易理解。他的觀點如下:
當(dāng)用戶離開應(yīng)用,在回到應(yīng)用前被銷毀的話,例如點擊了屏幕的Home鍵或者有個電話打進(jìn)來,用戶很久之后才回到應(yīng)用程序,但是在此之前系統(tǒng)因為資源緊張而銷毀了應(yīng)用進(jìn)程,當(dāng)用戶返回還是要重新創(chuàng)建activity,問題等于沒解決。
所以綜合一些原因,官方建議使用一個無界面的Fragment來保存Activity的狀態(tài)數(shù)據(jù)。因為當(dāng) Android 系統(tǒng)因配置變更而關(guān)閉 Activity 時,不會銷毀您已標(biāo)記為要保留的 Activity 的片段。 這里需要強(qiáng)調(diào)為了保留Fragment不被清理需要調(diào)用setRetainInstance(true)
方法。關(guān)于這個方法的官方注釋:
Control whether a fragment instance is retained across Activity re-creation(such as from a configuration change).
控制是否在重新創(chuàng)建Activity時保留Fragment(例如當(dāng)Activity經(jīng)歷了系統(tǒng)配置變化后的重建)。
這里我也寫了一個Demo跟大家共同學(xué)習(xí)下利用Fragment保留Activity信息的方法,首先默認(rèn)不做任何恢復(fù)操作時的效果:
可以看到界面上的進(jìn)度信息隨著轉(zhuǎn)換屏幕重建
Activity
而中斷。接下來看下加入恢復(fù)操作后的模擬效果:
可以Activity并沒有因為旋轉(zhuǎn)屏幕而丟失信息。這里貼一下我的實現(xiàn)方法,幫助大家拋磚引玉了:
Activity部分:
public class RotateActivity extends AppCompatActivity {
private static final String TAG = "RotateActivity";
private static final int ACTION_START = 0x1000;
private static final int ACTION_END = 0x1001;
private ProgressBar mPb;
private TextView mTvShowProgress;
private Button mBtnStart;
private ProgressHandler mHandler = new ProgressHandler(this);
private int progress;
private Thread mThread;
private volatile boolean exit = false;
private BlankFragment dataFragment;
private ViewBean viewData;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_rotate);
Log.i(TAG, "onCreate()");
mPb = findViewById(R.id.pb_rotate_ac);
mTvShowProgress = findViewById(R.id.tv_rotate_ac);
mBtnStart = findViewById(R.id.btn_rotate_ac);
mThread = new SafeThread();
mBtnStart.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mThread.start();
}
});
FragmentManager fragmentManager = getFragmentManager();
dataFragment = (BlankFragment) fragmentManager.findFragmentByTag("data");
if (dataFragment == null) {
dataFragment = new BlankFragment();
FragmentTransaction transaction = fragmentManager.beginTransaction();
transaction.add(dataFragment, "data").commit();
}
if (dataFragment.getViewData() != null) {
// restore view info
viewData = dataFragment.getViewData();
progress = viewData.getProgress();
mPb.setProgress(viewData.getProgress());
mTvShowProgress.setText(viewData.getProgress() + "%");
mThread.start();
}
}
private ViewBean collectViewData() {
viewData = new ViewBean();
viewData.setProgress(progress);
return viewData;
}
@Override
protected void onRestart() {
super.onRestart();
Log.i(TAG, "onRestart()");
}
@Override
protected void onStart() {
super.onStart();
Log.i(TAG, "onStart()");
}
@Override
protected void onResume() {
super.onResume();
Log.i(TAG, "onResume()");
}
@Override
protected void onPause() {
super.onPause();
Log.i(TAG, "onPause()");
}
@Override
protected void onStop() {
super.onStop();
if (mThread.isAlive()) {
Log.i(TAG, "close thread");
exit = true;
}
Log.i(TAG, "onStop()");
}
@Override
protected void onDestroy() {
super.onDestroy();
Log.i(TAG, "onDestroy()");
// store the data in fragment
dataFragment.setViewData(collectViewData());
}
private static class ProgressHandler extends Handler {
private WeakReference<RotateActivity> mContext;
public ProgressHandler(RotateActivity context) {
mContext = new WeakReference<>(context);
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case ACTION_START:
mContext.get().mPb.setProgress(msg.arg1);
mContext.get().mTvShowProgress.setText(msg.arg1 + "%");
break;
case ACTION_END:
Toast.makeText(mContext.get(), "Download completed.", Toast.LENGTH_SHORT).show();
break;
}
}
}
private class SafeThread extends Thread {
@Override
public void run() {
super.run();
while (!exit && progress < 100) {
progress++;
Message message = mHandler.obtainMessage();
message.what = ACTION_START;
message.arg1 = progress;
message.sendToTarget();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if (!exit) {
Message message = mHandler.obtainMessage();
message.what = ACTION_END;
message.sendToTarget();
}
}
}
}
Fragment部分:
public class BlankFragment extends Fragment {
private ViewBean mViewBean;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// retain this fragment
setRetainInstance(true);
}
public void setViewData(ViewBean data) {
this.mViewBean = data;
}
public ViewBean getViewData() {
return this.mViewBean;
}
}
需要簡單說明下,這里的ViewBean
類就是針對具體需求對需要保存的Activity
信息進(jìn)行的封裝。我這里只簡單的保留了進(jìn)度信息,大家可以自身要求做具體的變化。
最后的總結(jié),這篇文章的整體思路是基于《Android開發(fā)藝術(shù)探索》這本書的內(nèi)容。此外,也增加了一部分自己整理的一些知識點,算是對這一階段的學(xué)習(xí)的匯總。寫完以后發(fā)現(xiàn)一個技術(shù)點如果真的整理起來可以擴(kuò)展很多,原本以為自己已經(jīng)掌握的點其實并沒有完全理解透徹,想寫一篇讓自己滿意的文章同時也需要大量的時間來探究和整理。也希望自己能夠做到合理有效的利用時間來堅持做好這件事。期待與大家一同進(jìn)步~