天天看點

閱讀手劄:《Android開發藝術探索》(一)

《Android開發藝術探索》這本書在Android開發界内可謂口碑極佳,他的作者任玉剛在百度擔任Android資深開發工程師。我的一個國中朋友現在深圳百度做Android研發,上周去深圳找他玩的時候碰巧在他電腦桌上看到了這本書,他談到這本書内容多、有深度且知識點非常系統值得反複閱讀。的确,《Android開發藝術探索》這本書去年看過一遍就感覺非常不錯,下面将一些書中的重要知識點歸納并分享出來,這是本系列的第一篇,主要介紹的是 Activity的生命周期和啟動模式

評語:本節也從原了解釋了為什麼每次繼承Activity後,需要重寫onCreate(Bundle savedInstanceState),方法裡面的savedInstanceState雖然用的少,但是很重要!谷歌Android團隊的這種設計政策可以從側面反映其思慮之遠、邏輯健壯性以及降低特殊情況的損耗。正文有詳細解釋

下面是我們熟悉的Activity的生命周期流程圖:

閱讀手劄:《Android開發藝術探索》(一)

Activity的生命周期

下面就生命周期逐個分析:

(1)onCreate:create 表示Activity正在被建立,這是Activity生命周期的第一個方法,也是我們在android開發中接觸的最多的生命周期方法。它本身的作用是進行Activity的一些初始化工作,比如使用setContentView加載布局,對一些控件和變量進行初始化等。但也有很多人将很多與初始化無關的代碼放在這,其實這是不規範的。此時Activity還在背景,不可見。是以動畫不應該在這裡初始化......

(2)onStart:start 表示啟動,這是Activity生命周期的第二個方法。注意:此時Activity已經可見了,但是還沒出現在前台,還無法與Activity互動,其實将Activity的初始化工作放在這也沒有什麼問題,放在onCreate中是由于官方推薦的以及我們開發的習慣。

(3)onResume:resume 表示繼續、重新開始,這名字和它的職責也相同。此時Activity經過前兩個階段的初始化已經蓄勢待發。Activity在這個階段就表示 已經出現在前台并開始活動

(4)onPause:pause 表示暫停,當Activity要跳到另一個Activity或應用正常退出時都會執行這個方法。此時Activity在前台并可見,我們可以進行一些輕量級的存儲資料和去初始化的工作,不能在這個方法裡面做耗時以及重量級的操作,因為在跳轉Activity時隻有當一個Activity執行完了onPause方法後另一個Activity才會啟動,而且android中指定如果onPause在500ms即0.5秒内沒有執行完畢的話就會強制關閉Activity。從生命周期圖中發現可以在這快速重新開機,但這種情況其實很罕見,比如使用者切到下一個Activity的途中按back鍵快速得切回來。注意:onPause()必須先執行完畢,新的Activity的onResume()才會執行

(5)onStop:stop 表示停止,此時Activity已經不可見了,但是Activity對象還在記憶體中,沒有被銷毀。這個階段的主要工作也是做一些資源的回收工作。

(6)onDestroy:destroy 表示毀滅,這個階段就表示Activity被銷毀,不可見。常見的操作是:将沒釋放的資源釋放,進行一些回收工作。例如EventBus的登出、MVP架構下,對P層進行登出等等

(7)onRestart:restart 表示重新開始,Activity在這時可見,當使用者按Home鍵切換到桌面後又切回來或者從後一個Activity切回前一個Activity就會觸發這個方法。目前Activity從不可見重新變為可見狀态,就會調用這個方法

切換過程:

  • 針對一個特定的Activity,第一次啟動過程的結果回調:onCreate - onStart - onResume
  • 打開新Activity或者切換桌面,回調:onPause - onStop (如果目标Activity采用透明主題,目前Activity不會回調onStop)
  • 回到原來的Activity,回調如下:onRestart - onStart - onResume
  • back鍵回退,回調如下:onPause - onStop - onDestroy

容易混淆的兩個概念:

  • onStart 和 onResume,onPause 和 onStop 描述感覺一樣,但是差別在于onStart跟onStop是從Activity 是否可見 來回調的;onResume和onStop是從Activity 是否位于前台 這個角度來回調的
  • 假設目前Activity打開新的Activity,那麼新的 Activity的 onResume()先執行還是舊的Activity先執行onPause ()?答案是:舊的Activity先執行onPause(),執行完畢以後,在執行新Activity的 onResume()進行啟動

上面的内容都是針對正常情況下Activity生命周期的流程處理,在一些情況出現的時候,可能會導緻Activity的生命周期異常,當然,谷歌Android團隊在設計之初就考慮了這個問題。

異常情況下的生命周期:

  • 當資源相關的系統配置發生改變以及系統記憶體不足等一些特殊的情況就會導緻Activity生命周期異常而銷毀,這種特殊情況,系統會調用 onSaveInstanceState來儲存目前Activity狀态,理論上來說,這個方法一般不會被調用,如果出現這種情況,當Activity重新建立的時候,系統會調用onRestoreInstanceState,将之前onSaveInstanceState方法儲存的Bundle對象作為參數同時傳遞給onRestoreInstanceState和onCreate方法。
  • 還有一種情況,我們知道Activity優先級有如下區分:

    (1)前台可見正在和使用者互動的activity優先級最高;

    (2)可見但是非前台activity,如activity彈出個視窗。

    (3)背景已經暫停的activity優先級最低,

    當系統記憶體不足的時候就會優先回收這些Activity所在的程序,後通過onSaveInstanceState和onRestoreInstanceState來存儲和恢複資料。較好的方法是将背景程式放入service中保證優先級的提高,這樣就不會輕易被殺死。

Activity啟動模式:

關于啟動模式這個話題的确老生常談,但是還是需要溫故知新,為了更好的了解Activity的啟動模式,必須要先了解掌握以下幾個基礎概念:

  • 基礎概念一:棧結構,是一種先進後出(也稱為:後進先出)的順序
  • 基礎概念二:預設啟動,多次啟動一個Activity,系統會建立對應的執行個體對象,并将其放入任務棧中,按下Back鍵,就會一 一回退
  • 基礎概念三:基于此(其實是節約性能以及記憶體和适應多種開發場景的考慮),谷歌提供了四種Activity啟動模式

standard模式(标準模式)

系統的預設模式,每次啟動一個新的Activity就會建立一個執行個體對象(誰啟動了這個Activity,那麼這個Activity 就 運作在啟動它的那個Activity所在的棧中)

singleTop模式(棧頂複用模式)

如果新的Activity位于任務棧的棧頂,那麼此Activity就不會被重複建立,同時,這個Activity的onNewIntent方法會被回調。

  • 注意一:此模式下的Activity的onCreate、onStart不會被系統調用,因為沒有發生改變
  • 注意二:如果新的Activity執行個體已存在但不是在棧頂,那麼新的Activity還是會被重建

singleTask模式(棧内複用模式)

隻有Activity在一個棧中存在,那麼多次啟動此Activity都不會建立執行個體,當然它會回調onNewIntent方法。這種模式,系統首先會判斷新的Activity是否存在任務棧,如果不存在任務棧,則建立一個新的任務棧,接着将Activity的執行個體對象放入剛才建立的任務棧中;如果存在該Activity的任務棧,首先要判斷該棧是否存在這個activity的執行個體對象,如果執行個體對象存在,系統就會把這個Activity調到棧頂并回調它的onNewIntent方法;如果該執行個體對象不存在,就建立這個Activity的執行個體并壓入對應的任務棧中

singleInstance模式(單執行個體模式)

這種模式算是加強的singleTask模式,這種模式的Activity隻能單獨的運作在一個任務棧中。

那麼,聲明指定Activity的方式大家也應該都知道,可以通過清單配置檔案裡,Activity标簽内的launchMode去聲明具體的啟動模式(一個Activity隻能有一種):

<activity
            android:name="com.sample.MainActivity"
            android:label="@string/app_name"
            android:launchMode="standard"
            android:launchMode="singleTop"
            android:launchMode="singleTask"
            android:launchMode="singleInstance"
            >
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>
           
intent-filter :

intent-filter我們一般稱為意圖過濾器。試想這樣一種場景,如果一個 Intent 請求在一片資料上執行一個動作, Android 如何知道哪個應用程式(群組件)能用來響應這個請求呢?Intent Filter的出現就是為了解決這個問題(本質是根據設定的條件去過濾),它是用來注冊 Activity 、 Service 和 Broadcast Receiver,讓其 具有能在某種資料上執行一個動作的能力。使用 Intent Filter ,應用程式元件就會告知 Android ,它們能為其它程式的元件的動作請求提供服務,包括同一個程式的元件、本地的或第三方的應用程式。

另外,Activity的跳轉啟動,一般分為兩種:

  • 第一種:顯示調用

    如果是顯示調用來實作Activity的跳轉啟動,需要明确指定被啟動對象的元件資訊,包括包名和類名。一般是在相同的應用程式内部實作的。以兩個Activity之間的跳轉為例

Intent intent = new Intent(MainActivity.this, NewActivity.class);
startActivity(intent);
           
  • 第二種:隐式調用

    隐式調用則是通過Intent Filter來實作的,它一般用在沒有明确指出目标元件名稱的前提下。Android系統會根據隐式意圖中設定的動作(action)、類别(category)、資料(URI和資料類型)找到最合适的元件來處理這個意圖。一般是用于在不同應用程式之間。

注意:原則上一個Intent不應該即是顯式調用有是隐式調用,如果二者共存以顯式調用為主。

上面提到了action、category、data三個标簽,下面就逐個分析:

action的比對規則

一個intent filter中可以有多個action,那麼隻要intent中的action能和intent filter中的任何一個action相同集合比對成功。需要注意的是,intent中沒有指定action,那麼比對失敗。注意,action嚴格區分大小寫、action嚴格區分大小寫!

category的比對規則

category的比對規則和action不同,intent中如果出現了category,不管有幾個,都必須是intent filter中已經定義的category,也就是說intent可以沒有category,一旦有,每個都必須和intent filter中定義的任意一個category相同。

那為什麼我們沒在intent filter中加android.intent.category.DEFAULT這個category會報錯呢?原因是系統在調用startActivity或者startActivityForResult的時候會預設在intent中加上android.intent.category.DEFAULT這個category。

還是以兩個Activity之間的跳轉為例:

Intent intent = new Intent();
intent.setAction("com.android.test");
intent.addCategory("com.android.testcat");
startActivity(intent);

AndroidManifest.xml配置檔案中:

<activity android:name=".NewActivity">
            <intent-filter>
                <action android:name="com.android.test"/>
                <category android:name="com.android.testcat"/>
                //必須加上,否者報錯
                <category android:name="android.intent.category.DEFAULT"/>
            </intent-filter>
        </activity>
           

data的比對規則

data的比對規則和action類似,如果intent filter中定義了data,那麼intent中也必須要定義可比對的data,否者比對失敗。

首先來了解一下data的結構,data由兩部分組成,分别是:mimeType和URI。

  • mimeType指媒體類型,比如image/jpeg、text/plaind、video/*等,可以表示圖檔、文本、視訊等不同的媒體格式。
  • URI的結構看上去複雜,但耐心分析也就那回事,下面是URI的結構: <schema>://<host>:<port>/[<path>|<pathPrefix>|<pathPattern>]

假設現在有這樣一串URI:

https://www.baidu.com:80/android/info

根據上面的URI結構,這串URI代表的含義如下:

  • Schema:這個代表URI的模式,常見的schema有http、file、content等等。注意:如果URI沒有指定URI,這就意味整個URI無效!
  • host:主機名,這裡的就是 www.baidu.com ,如果host沒有聲明任何屬性值,同樣意味該URL無效
  • port:端口号,這裡的端口号就是指80,目前僅當URI聲明schema和host的時候,port才有意義
  • path、pathPrefix、pathPattern:代表的就是完整的路徑資訊

上面說完了URI,接下來開始介紹data的過濾規則,下面分情況說明

情況一:data規則不完整。如下所示

<intent-filter>
      <data android:mimeType="image/*"/>
</intent-filter>
           

這種規則指定了媒體類型為所有類型的圖檔,那麼intent中的mimeType屬性必須為“image/*”才能比對成功,這種情況雖然沒有指定URI,但intent中的URL部分的schema預設值為content和file。也就是說雖然沒有指定URI,但是intent中的URI部分的schema必須為content或者file才能比對成功,這點尤其需要注意。

情況二:定義了多組data規則,并且每個data都定義了完整屬性,既有URI又有mimeType。如下所示:

<intent-filter>
    <data android:mimeType="video/mpeg" android:scheme="http" ... />
    <data android:mimeType="audio/mpeg" android:scheme="http" ... />
    ...
</intent-filter>
           

為了比對這種intent filter,我們也需要在intent中完整定義其中一組data規則才能比對成功。另外,如果要為intent指定完整的data,必須調用setDataAndType方法,不能先調用setData再調用setType,因為這兩個方法彼此會清除對方的值。

最後,但我們通過隐式方式啟動一個Activity時,可以做一下判斷,看是否有Activity能夠比對我們的intent,以免不必要的奔潰。判斷方法有兩種:一是采用PackageManager的resolveActivity方法,二是采用Intent的resolveActivity方法,如果他們找不到比對的Activity就會傳回null,根據其傳回值我們就規避上述問題了。

《Android開發藝術探索》第一章的内容大抵就是這樣,因為書中的内容都是幹貨,是以值得反複閱讀和品位,我個人的思考是:提高代碼的穩健性和健壯性是我們必須要考慮的問題,以及出現異常情況下,我們該如何處理減少損失。

如果這篇文章對你有幫助,希望各位看官留下寶貴的star,謝謝。

Ps:著作權歸作者所有,轉載請注明作者, 商業轉載請聯系作者獲得授權,非商業轉載請注明出處(開頭或結尾請添加轉載出處,添加原文url位址),文章請勿濫用,也希望大家尊重筆者的勞動成果。

繼續閱讀