1. 程式人生 > >Android開發藝術探索(Activity)

Android開發藝術探索(Activity)

一、Activity生命週期分析

1:典型情況下的生命週期分析

(1)、

onCreate:表示Activity正在被建立,這是生命週期的第一個方法
onRestart:表示Acitivity正在重新啟動,一般情況下,onRestart是在當前activity從不可見重新變為可見狀態時被呼叫
onStart:表示Activity正在被啟動,在邏輯意義上Activity已經是可見的,但是我們使用者並不能看到。
onResume:Activity已經可見了,並且出現在前臺且開始活動。
onPause:表示Activity正在停止,正常情況下,緊接著onStop就會被呼叫。在這個函式裡面我們可以做一些資料儲存以及停止動畫等工作,但是不可以耗時,因為新的Activity的onResume是必須等待舊activity的onPause執行完畢才可以執行。
onStop:表示Activity即將停止,可以做一些稍微重量級的回收工作,但是也不可以過於耗時。
onDestroy:表示Activity即將被銷燬,這是Activity生命週期的最後一個回撥,在這裡我們可以做一些回收工作以及最終資源的釋放。

這裡寫圖片描述

(2):常見活動裡Activity的生命流程

(1):當一個Activity第一次被啟動的時候,以次:OnCreate->onStart->onResume
(2):當用戶開啟別的Activity或者按home鍵的時候,onPause->onStop。(如果新的Activity採用了透明主題,那麼這當前的Activity不會onStop)
(3):使用者再次返回到一個Activity的時候onRestart->onStart->onResume
(4):當用戶點選back鍵回退時:onPause->onStop->onDestroy
(5):Destroy之後的activity在此開啟生命週期是同第一次開啟的,但是所用過程不是都相同

2:異常情況下的生命週期分析
(1):資源相關的系統配置發生改變導致Activity被殺死並重新建立。
舉例:如果我們設定了landscape或者portrait狀態下的圖片,我們將手機橫屏的時候和將手機豎屏的時候系統會根據當前的裝置情況去載入合適的Resources資源。噹噹前的Activity處於豎屏狀態,如果我們突然旋轉螢幕,由於系統配置發生了改變,在預設的情況下,Activity就會被銷燬並重新建立,其生命週期如圖所示:
這裡寫圖片描述
當系統配置發生改變後,Activity會被銷燬,其onPause,onStop,onDestroy均會被呼叫,同時由於Activity是在異常的情況下終止的,系統會呼叫onSaveInstanceState來儲存當前的Activity的狀態。這個方法的呼叫是在onStop之前,但是它和onPause沒有確定的時許關係。需要強調的是,這個方法只會出現在Activity被異常終止的情況下。當Activity被重新建立之後,系統會呼叫onRestoreInstanceState,斌且把Activity銷燬時onSaveInstanceState所儲存的Bundle物件作為引數同時傳遞給onRestoreInstancState和onCreate方法。因此我們可以通過onCreate方法和onRestoreInstanceState來判斷Acitivity是否被重新建立了。從時序上來說,onRestoreInstanceState的呼叫時機在onStart之後。
除了這些,在onSaveInstanceState和onRestoreInstanceState方法中,系統自動為我們做了一定的恢復工作。當Activity在異常情況下需要重新建立的時候系統會預設為我們儲存當前Activity的檢視結構,並且在Activity重啟之後為我們恢復這些資料(比如:文字框中的使用者輸入,ListView的滾動位置)
關於儲存和恢復View的層次結構,系統的工作流程是這樣的:首先Activity被意外終止時。

        1、Activity呼叫onSaveInstanceState
        2、Activity委託Window去儲存資料
        3、Window再委託它的頂層容器去儲存資料,頂層容器是一個ViewGroup,一般來說它是DecorView。
        4、頂層容器一一通知它的子元素來儲存資料。完成!

這種委託思想在Android裡面有很多應用,View的繪製,事件的分發等,當然我們也可以阻止系統重新建立Activity。如果系統配置中有很多內容,如果當某項內容發生改變之後,我們不想讓系統重新建立Activity,可以給Activity指定configChanges屬性。比如Activity在螢幕旋轉的時候被重新建立就可以給configChanges新增

    android:configChanges="orientation"

如果想指定多個值,多個值直接可以用 | 連結,下面是系統配置所含專案
這裡寫圖片描述

(2):資源記憶體不足導致低優先順序的Activity被殺死
這種情況下,Activity的資料恢復過程和情況1完全一致。這裡我們描述一下Activity的優先順序情況。Activity按照優先順序的從高到低,有高到低可以分為如下三種:

1、前臺Activity--正在互動
2、可見但是非前臺--比如由於對話方塊的彈出致使Activity可見但是位於後臺,無法直接與使用者互動
3、後臺Activity--比如執行過onStop的Activity

二、Activity的啟動模式

1、Activity的LaunchMode

Activity為什麼需要啟動模式。預設情況下,當我們多次啟動同一個Activity的時候,系統預設會建立多個例項,並把它們一一放入任務棧中,當我們點選back鍵,會發現這些Activity會一一回退。多次啟動同一個Activity,系統重複建立多個例項,這樣很浪費資源。但是我們可以修改系統的預設行為,目前有四種啟動模式:standard,singleTop,singleTask,singleInstance。

(1)standard:標準模式

 這也是系統的預設模式。每次啟動一個Activity都會重新建立一個新的例項,不管這個例項是否已經存在。被建立的例項的生命週期符合典型情況下Activity的生命週期。這是一種典型的多例項的實現。一個任務棧裡面可以有多個例項,而且每個例項也可以屬於不同的任務棧。在這種模式下,誰啟動了Activity,那麼這個Activity就執行在啟動它的那個Activity所在的棧中。比如Activity A 啟動了 Activity B(B是標準模式),那麼B就會進入到A所在的棧中。所以當我們的Activity是standard模式下,我們不可以用ApplicationContext去啟動Activity,不然會報錯如下:

這裡寫圖片描述

(2)singleTop:棧頂複用模式。

 在這種模式下,如果新Activity已經位於任務棧的棧頂,那麼此Activity不會被重新建立,同時它的onNewIntent方法會被回撥,通過此方法的引數我們可以取出當前的請求的資訊。需要注意的是,這個Activity的onCreate,onStart不會被系統呼叫,因為它們沒有發生改變。如果新Activity的例項已經存在,但是不是位於棧頂,那麼新的Activity仍然會被重新建立。

(3)singleTask:棧內複用模式。

 這是一種單例項模式,在這種情況下,只要Activity在一個棧中存在,那麼多次啟動這個Activity都不會重新建立例項,和singleTop一樣,系統也會呼叫其onNewIntent。具體的來說,當一個singleTask模式的Activity請求啟動後,系統會首先尋找是否存在A想要的任務棧。如果不存在,那麼重新建立一個任務棧,然後建立A的例項,並把A放到任務棧中。如果存在A所需的任務棧,這時候會檢視任務棧裡面是否有A的例項,如果有,那麼系統會把A的例項調到棧頂並呼叫它的onNewIntent方法。如果不存在,建立A的例項,並把它壓棧。下面是例子:

1、目前任務棧S1中的情況為ABC,這時候Activity D以singleTask模式請求啟動,其所需要的任務棧為S2,由於S2和D的例項均不存在,所以系統會先建立任務棧S2,然後再建立D的例項,並將其入棧到S2。
2、假設D所需的任務棧為S1,S1中是ABC,那麼系統會直接建立D的例項,並將它壓入到S1中。
3、如果D所需的任務棧為S1,S1現在有ABDC,系統會把D切換到棧頂並呼叫D的onNewIntent方法,同時由於singleTask預設具有clearTop效果,這會導致所有在D上面的Activity全部出棧。所以C會出棧,S1最終為ABD

(4)singleInstance:單例項模式。

 這是一種加強版的singleTask模式,它除了具有singTask模式的所有特性之外,還加強了一點,那就是具有這種模式的Activity只能單獨的位於一個任務棧中,換句話說,比如Activity A 是singleInstance模式,當A啟動後,系統會為它建立一個新的任務棧,然後A肚子在這個新的任務棧中,由於棧內的複用特性,後續的請求均不會建立新的Activity,除非這個任務棧被系統所銷燬。

上面的幾種啟動模式,這裡還需要指出一種情況。目前有2個任務棧,前臺任務棧的情況為AB,而後臺任務棧的情況為CD,假設CD的啟動模式均為singleTask,現在請求啟動D,那麼整個後臺任務棧的任務都會被切換到前臺,這時候整個後退列表變成了ABCD。當用戶按back鍵的時候,列表中的Activity會一一出棧。

這裡寫圖片描述

如果不是請求啟動D而是啟動C,那麼情況就不一樣了,如下圖
這裡寫圖片描述

另外一個問題是,在singleTask啟動模式中,多次提到某個Activity所需的任務棧,什麼是Activity所需的任務棧?這需要從一個引數說起,TaskAffinity,任務相關性,這個引數標示了一個Activity所需要的任務棧名字。預設的情況下,所有的Activity所需的任務棧名字應該為應用的包名。當然,我們可以為每個Activity都單獨指定TaskAffinity屬性。這個屬性值不能和包名相同,否則就相當於沒有指定。TaskAffinity屬性主要和singleTask啟動模式或者allowTaskReparening屬性配對使用,在其他情況下沒有意義。另外,任務棧分為前臺任務棧和後臺任務棧,後臺任務棧中的Activity位於暫停狀態,使用者可以通過切換將後臺任務棧再次調入前臺。

當TaskAffinity和singleTask啟動模式配對使用的時候,它是具有該模式的Activity的目前任務棧的名字,待啟動的Activity會執行在名字和TaskAffinity名字相同的任務棧中。

如何指定Activity的啟動模式?

1、第一種是通過AndroidManifest為Activity指定啟動模式,直接在activity的屬性裡面增加

<activity
    android:name="com.....Activity"
    android:launchMode="singleTask"/>

2、另外一種情況是通過在Intent中設定標誌為來為Activity指定啟動模式,比如:

Intent intent = new Intent();
intent.setClass(MainActivity.this,SecondActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);//注意這個flag就是singleTask
startActivity(intent);

這兩種方式都可以為Activity指定啟動方式,但是二者還是有區別的。
優先順序上: 2 > 1,兩種同時存在,按照第2種為準
限定範圍上:
第一種無法直接為Activity設定FLAG_ACTIVITY_CLEAR_TOP
第二種無法直接為Activity指定為singleInstance模式。

三、Activity的Flags

Activity的flag有很多,這裡主要分析一些比較常用的標誌位。標誌位的作用有很多,有的可以設定Activity的啟動模式,比如FLAG_ACTIVITY_NEW_TASK和FLAG_ACTIVITY_SINGLE_TOP等。還有的標記位可以影響Activity的執行狀態,比如FLAG_ACTIVITY_CLEAR_TOP和FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS等。

FLAG_ACTIVITY_NEW_TASK

這個標記位的作用是為Activity指定singleTask啟動模式。其效果和在XML中指定該啟動模式相同。同理FLAG_ACTIVITY_SINGLE_TOP

FLAG_ACTIVITY_CLEAR_TOP

具有此標記為的Activity,當它啟動時,在同一個任務棧中所謂位於它上面的Activity都要出棧。這個模式一般和FLAG_ACTIVITY_NEW_TASK配合使用,在這種情況下,被啟動ACTIVITY的例項如果已經存在,系統就會呼叫它的onNewIntent。如果被啟動的Activity採用standard模式,那麼它連同它之上的Activity都要出棧,系統會建立新的Activity例項並放入棧頂。singTask啟動模式預設具有此效果。

FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS

具有這個標記的Activity不會出現在歷史Activity的列表中,當某些情況下,我們不希望使用者通過歷史回到我們的Activity的時候,這個標記比較有用。它等同於在XML中指定Activity的屬性android:excludeFromRecents=”true”.

四、IntentFilter的匹配規則

Activity的啟動分為兩種,顯式呼叫和隱式呼叫。

隱式不明確指定啟動哪個Activity,而是在Intent Filter中設定Action、Data、Category,讓系統來篩選出合適的Activity。

Intent intent = new Intent();  
intent.setAction("android.intent.action.SEND");  
startActivity(intent);  

顯式呼叫是我們平常用到比較多的一種

Intent intent = new Intent();
intent.setClass(MainActivity.this,SecondActivity.class);
startActivity(intent);

 顯式呼叫需要明確的指定被啟動物件的元件資訊,包含包名和類名,而隱式呼叫則不需要指定元件資訊。原則上一個Intent不應該即是顯式呼叫又是隱式呼叫,如果二者共存的話,那麼以顯式呼叫為主。隱式呼叫需要Intent能夠匹配目標元件的IntentFilter中所設定的過濾資訊,如果不匹配將無法啟動目標Activity。IntentFilter中的過濾資訊有action,category,data,下面是一個過濾例項:

這裡寫圖片描述

為了匹配過濾列表,需要同時匹配過濾列表中的action,category,data資訊,否則匹配失敗。一個過濾列表中的action,category和data可以有多個,所有的action,category,data分別構成不同類別,同意類別的資訊共同約束當前類別的匹配過程。只有Intent完全匹配成功才能成功啟動目標Activity。另外一點,一個Activity中可以有多個intent-filter,一個Intent只要能匹配任何一組intent-filter即可成功的啟動對應的activity。
這裡寫圖片描述

1、Action的匹配規則

action是一個字串,系統預定義了一些action,同時我們也可以在應用中定義自己的action。action的匹配規則是Intent中的action必須能夠和過濾規則中的acton匹配,這裡說的匹配是指action的字串值完全一樣。一個過濾規則中可以含有多個action,只要Intent中的action能夠匹配任意一個即可。如果intent裡面沒有指定action,那麼匹配總是失敗,而且action區分大小寫。

2、category匹配規則

category是一個字串,系統預定義了一些category,同時我們也可以在應用中定義自己的category。category的匹配規則和action不同,它要求Intent中如果含有category,那麼所有的category都必須和過濾規則中的一個category相同,換句話說,Intent中如果出現了category,不管有category,對於每一個category來說,他必須是過濾規則中已經定義了的category。當然,Intent中可以沒有category,如果沒有category的話,按照上面的描述,這個Intent仍然可以匹配成功。為什麼不設定category也可以成功匹配呢?原因是系統哦你在呼叫startActivity或者startActivityForResult的時候會預設為Intent加上“android.intent.category.DEFALULT”這個category。同時,為了我們的activity能夠接收隱式呼叫,就必須在intent-filter裡面指定“android.intent.category.DEFAULT”這個category

3、data的匹配規則

data的匹配規則和action類似,如果過濾規則中定義了data,那麼Intent中必須也要定義可匹配的data。在介紹data的匹配規則之前,我們需要先了解data的結構,因為data稍微有些複雜。

data的語法如下:
<data
    android:scheme="string"
    android:host="string"
    android:port="string"
    android:path="string"
    android:pathPattern="string"
    android:pathPrefix="string"
    android:mimeType="string" />

data由兩部分組成,mimeTye和URI。mimeType指的是媒體型別,比如image/jpeg等,可以用來表示圖片,文字,視訊等不同的媒體格式,而URI中包含的資料就比較多了。

URI結構:
<scheme>://<host>:<port>/[<path>|<pathPrefix>|<pathPattern>]
例項:
content://com.example.project:200/folder/subfolder/etc
https://www.baidu.com

Scheme:URI的模式,比如https,file,content,如果URI中沒有指定scheme,那麼整個URI的其他引數無效
Host:URI的主機名,比如www.baidu.com,如果host未指定,那麼整個URI中的其他引數設定無效。
Port:URI中的埠號,比如80,僅當URI中指定了scheme和host引數的時候port引數才是有意義的。
Path,pathPatter和pathPrefix:這三個引數表示路徑資訊。其中Path表示完整的路徑資訊。pathPatter也表示完整的路徑資訊,但是它裡面可以包含萬用字元“”,這個符號表示0或者多個任意字元,需要注意的是,由於正則表示式的規範,如果想表示真實字串,那麼‘’要寫成”\*”,”\”要寫成“\\”。pathPrefix表示的是路徑字首資訊。

備註:節選自Android開發藝術探索–任玉剛