Activity

本系列主要是讀書筆記,知識點百分之90來自 <<安卓開發藝術探索>>和<<安卓羣英傳>>,還有百分之10是自己的擴展與理解.歡迎吐槽

生命週期

一.正常情況下生命週期如圖
這裏寫圖片描述
正常生命週期 開起activity調用onCreate() onStart() onResume(),按下返回鍵 onPause() onStop() onDestroy()

啓動activity onCreate() onStart() onResume()
切換到桌面或者打開一個新的activity onPause() onStop() 返回該activity onRestart() onStart() onResume()
如果打開新的activity主題設置爲透明 則 onPause() 返回該activity onResume()

這裏唯一需要注意的是,當前activity onPause()方法執行後 纔會執行新的activity的onCreate(),也就是說onPause()不能進行耗時操作,不然會影響下個activity顯示速度

這裏寫圖片描述


二.異常情況生命週期
系統配置發生改變 或者 系統內存不足情況下發生

1.系統配置發生改變
默認情況下,如果activity不做特殊處理,那麼系統配置發生改變後,activity就會被銷燬並重新創建
這裏寫圖片描述
系統配置發生改變後,activity會被銷燬,即onPause(),onStop(),onDestroy()會執行,由於是異常情況下終止的,系統會調用onSaveInstanceState()來保存當前狀態,該方法調用在onStop()之前,(要糾正下的是該方法不止是隻有異常情況下才會調用,正常情況比如activityA打開activityB也會執行onSaveInstanceState()方法這裏有日誌爲證)
這裏寫圖片描述
雖然跟我們平時聽說的有不太一樣…但日誌如此,實踐是檢驗真理的唯一標準.
當activity重建後,系統會調用onRestoreInstanceState()並把銷燬時onSaveInstanceState()所保存的Bundle對象作爲參數傳遞給onRestoreInstanceState ()和onCreate()(兩者區別是:onRestoreInstanceState()被調用其參數Bundle saveInstanceState一定有值,而onCreate()如果正常啓動的話,其參數Bundle saveInstanceState 爲null,所以必須要額外的非空判斷),從調用順序上來說onRestoreInstanceState()在onStart()之後並且只有異常情況下才會調用.

同時,在onSaveInstanceState()與onRestoreInstanceState()方法中,系統自動爲我們做了一定的恢復工作,例如異常情況下頁面重啓的時候,系統會默認爲我們恢復當前activity視圖結構(文本框輸入數據,listview滾動位置等),具體針對每個特定的view系統能爲我們恢復那些數據,可以查看view源碼,每個view都有
onSaveInstanceState()與onRestoreInstanceState()方法,看下他的實現,就知道系統能自動爲每個View恢復哪些數據了.

2.資源內存不足導致低優先級的activity被殺死
activity優先級從高到低可以分爲
前臺activity->可見但非前臺activity(比如activity中彈出了一個對話框,導致activity可見但是不可操作)->後臺activity,因爲內存不足activity重啓的生命週期和之前1的並無差異


通過上面分析,我們知道系統配置發生改變後,activity會被重新創建,那麼有沒有辦法不重新創建呢?答案是有的,系統配置中有很多內容,如果當某個配置發生改變,我們不想創建activity可以給他指定configChanges屬性,具體可以看下面表格

VALUE DESCRIPTION
“mcc” 國際移動用戶識別碼所屬國家代號是改變了—– sim被偵測到了,去更新mcc mcc是移動用戶所屬國家代號
“mnc” 國際移動用戶識別碼的移動網號碼是改變了—— sim被偵測到了,去更新mnc MNC是移動網號碼,最多由兩位數字組成,用於識別移動用戶所歸屬的移動通信網
“locale” 地址改變了—–用戶選擇了一個新的語言會顯示出來
“touchscreen” 觸摸屏是改變了——通常是不會發生的
“keyboard” 鍵盤發生了改變—-例如用戶用了外部的鍵盤
“keyboardHidden” 鍵盤的可用性發生了改變
“navigation” 導航發生了變化—–通常也不會發生
“screenLayout” 屏幕的顯示發生了變化——不同的顯示被激活
“fontScale” 字體比例發生了變化—-選擇了不同的全局字體
“uiMode” 用戶的模式發生了變化
“orientation” 屏幕方向改變了
“screenSize” 屏幕大小改變了
“smallestScreenSize” 屏幕的物理大小改變了,如:連接到一個外部的屏幕上

上面屬性只有一部分,具體以實際情況爲準,下面來一個例子,指定configChanges屬性爲orientation|screenSize,activity橫豎屏切換後,不會重新創建

<application
    android:allowBackup="true"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    android:name=".base.AppApplication"
    android:supportsRtl="true"
    android:theme="@style/AppTheme">
    <activity android:name=".MainActivity"
        android:configChanges="orientation|screenSize">
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />

            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>

    <activity android:name=".TakePicture" />
</application>

橫豎屏切換後,activity並未重啓,而是調用了onConfigurationChanged()這裏寫圖片描述


啓動模式

目前有四種啓動模式standard,singleTop,singleTask和singleInstance

一.standard
標準模式,也是系統默認的模式,每次啓動一個activity都會創建一個新的實例,不管這個實例是否存在,生命週期就是標準的生命週期,在這種模式下誰啓動了這個activity,那麼這個activity就運行在啓動它的那個activity所在棧中, 最終會形成的特色棧況爲: ABCDEF…….或者ABCDDCEFAB……..

二.singleTop
棧頂複用模式,如果新activity位於棧頂,此時啓動該activity,該activity並不會被重新創建,同時他的onNewInstance方法會被調用, 所以不會出現ABCDD,而只會有ABCD ,生命週期如下
這裏寫圖片描述
如果activity實例已經存在但是不位於棧頂,那麼新activity仍會創建

三.singleTask
棧內複用模式,這是一種單實例模式,只要activity在一個棧中存在,那麼多次啓動activity都不會重新創建實例,系統會把該activity切換到棧頂並調用其onNewIntent()方法 (棧內所有在他之上的activity全部出棧 )

具體點,當一個具有singleTask模式的Activity請求啓動後,比如ActivityA,系統首先會尋找是否存在ActivityA想要的任務棧(ps:這個想要的任務棧根據AndroidManifest.xml文件中TaskAffinity屬性的值來決定,如果當前有值並且跟系統默認的任務棧包名不相同則會創建新的任務棧再將activity壓入棧中,詳情後面會說到),如果不存在,就重新創建一個任務棧,然後創建一個A的實例後把A放入棧中,如果存在A所需的任務棧,這時候要看A是否在棧中有實例存在,如果有實例存在,那麼系統會把A調到棧頂調用其onNewInstance方法並把在他之上的activity全部出棧,如果實例不存在,就創建A實例並把A壓入棧中.

這裏驗證下TaskAffinity屬性
1.啓動模式SingleTask的Activity不設置TaskAffinity(等價於想要的任務棧爲默認的名字爲包名的任務棧),是否創建新的任務棧在壓入Activity

<application
    android:allowBackup="true"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    android:name=".base.AppApplication"
    android:supportsRtl="true"
    android:theme="@style/AppTheme">
    <activity android:name=".MainActivity"
        android:configChanges="orientation|screenSize"
       >
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />

            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>

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

</application>

清單就給TwoActivity設置singleTask模式,在每個Activity onCreat()方法打印TaskId

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    KLog.i("MainActivity:onCreate");
    int taskId = getTaskId();
    KLog.i("MainActivity:taskId"+taskId);

}

日誌如下taskId未改變並未創建新的任務棧,因爲TaskAffinity未設置.
這裏寫圖片描述

2.設置TaskAffinity,看是否創建新的任務棧
清單如下

<application
    android:allowBackup="true"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    android:name=".base.AppApplication"
    android:supportsRtl="true"
    android:theme="@style/AppTheme">
    <activity android:name=".MainActivity"
        android:configChanges="orientation|screenSize"
       >
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />

            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>

    <activity android:name=".TwoActivity"
        android:launchMode="singleTask"
        android:taskAffinity="cn.yy"
        />

</application>

其他沒有變化,日誌如下
這裏寫圖片描述
日誌中taskId改變了,確實創建了新的任務棧.結論正確

四.singleInstance
單實例模式,加強版的singleTask模式,具有此種模式的activity只能單獨的位於一個任務棧中,比如第一次啓動singleInstance模式的ActivityA,系統會先創建一個新的任務棧,然後A獨自在這個新的任務棧中,再次啓動該activity由於棧內複用的特性均不會創建activity,除非這個任務站被系統銷燬了

這裏驗證一下 singleInstance 模式的activity只能單獨的位於一個任務棧中
MainActivity 啓動singleInstance模式的TwoActivity, Two在啓動一個正常模式的ThreeActivity,比較Two與Three任務棧是否相同

<application
    android:allowBackup="true"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    android:name=".base.AppApplication"
    android:supportsRtl="true"
    android:theme="@style/AppTheme">
    <activity android:name=".MainActivity">
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />

            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>

    <activity android:name=".TwoActivity"
        android:launchMode="singleInstance"
        />

    <activity android:name=".ThreeActivity"/>
</application>

每個activity,onCreate()方法如下

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    KLog.i("MainActivity onCreate");
    int taskId = getTaskId();
    KLog.i("MainActivity taskId"+taskId);
}

日誌如下
這裏寫圖片描述
可以看到ThreeActivity與TwoActivity是不同的任務棧,證明TwoActivity確實是獨佔一個任務棧.

需要注意的是activity任務棧,這得從一個參數說起TaskAffinity,這個參數標示了一個activity所需任務棧的名字,默認情況下,所有activity所需的任務棧的名字都爲應用的包名,當然,我們我可以爲每個activity都指定獨立的TaskAffinity屬性,這個值必須和包名不相同,否則相當於沒指定,另外需要注意的是TaskAffinity需要SingleTask或者allowTaskReparenting屬性配合使用,否則沒有意義.另外任務棧分爲前臺任務棧和後臺任務棧,用戶可以通過切換將後臺任務棧再次調到前臺.

五.Activity的Flags
這裏只介紹幾個常用的,
1.FLAG_ACTIVITY_NEW_TASK(默認)
默認的跳轉類型,它會重新創建一個新的Activity,不過有這種情況,比如說Task1中有A,B,C三個Activity,此時在C中啓動D的話,如果在AndroidManifest.xml文件中給D添加了TaskAffinity的值和默認的任務棧(包名)不一樣的話,則會創建一個新的任務棧名字爲TaskAffinity的值,然後壓入這個Activity。如果是默認的或者指定的 TaskAffinity 和Task一樣的話,就和標準模式一樣了啓動一個新的Activity.

2.FLAG_ACTIVITY_SINGLE_TOP
這個FLAG就相當於啓動模式中的singletop,例如:原來棧中結構是A B C D,在D中啓動D,棧中的情況還是A,B,C,D。

3.FLAG_ACTIVITY_CLEAR_TOP
如果在ABCD的堆棧狀態下,以該標識啓動B,則會銷燬CD,且B也是重新創建的(與singleTask有區別),如果配合FLAG_ACTIVITY_SINGLE_TOP,則就會成爲singleTask的模式



IntentFilter匹配規則

activity啓動分爲兩種,隱式調用和顯示調用,

1.顯示調用
就像啓動Activity,我們常常就是顯式的調用,那何爲顯式調用呢?

Intent itent = new Intent();
itent.setClass(Activity_A.this, Activity_B.class);
startActivity(itent);

哦,這就是顯式調用。之說以叫做顯式調用,我們爲Intent清楚的指出了被啓動組件的信息(這裏就是Activity_B),當調用了startActivity(itent)後,我們就只會很明確的知道,這次的任務是啓動Activity_B,而沒有其它的過程。

2.隱式調用
看了顯式調用,應該猜都猜得到了,隱式調用就是沒有明確的指出組件信息。而是通過Filter去過濾出需要的組件。

Intent intent = new Intent(); 
intent.setAction(Intent.ACTION_BATTERY_LOW);
intent.addCategory(Intent.CATEGORY_APP_EMAIL);
intent.setDataAndType(Uri.EMPTY, "video/mpeg"); 
startActivity(intent);

這裏就是一個隱式的調用,可以看到我爲Intent設置了三個屬性Action、Category、Data。
然後startActivity(intent)就會根據我們設置的這三個屬性去篩選合適的組件來打開,也就是因爲這樣,所以有時候,當我們APP來分享一個東西的時候,會有很多組件(比如QQ、微信、微博…)來供我們選擇,因爲他們都滿足Filter條件。

2.1action
action是一個字符串,匹配規則是Intent中必須有一個action且必須能夠和過濾規則中的action的字符串完全一樣,需要注意的是action區分大小寫

2.2category
category是一個字符串,category 要求Intent中可以沒有category,但是你一旦有category,不管幾個,它必須是IntentFilter中定義了的category。

這裏我們說Intent中可以沒有category,其實不然,只是在我們啓動組件(eg:startActivity( ))的時候,默認給我們的Intent給加了一個category(“android.intent.category.DEFAULT” ).

哦,我們知道了這裏,那麼匹配就和action差不多了,就是我們的Intent中的category必須在IntentFilter中存在。這裏得注意,Intent中都會包括默認的category,並且如果你想隱式啓動某個組件,那麼就得在IntentFilter中添加android.intent.category.DEFAULT這個category才行喲。

2.3data
先了解下data結構

<data android:scheme="string"
          android:host="string"
          android:port="string"
          android:path="string"
          android:pathPattern="string"
          android:pathPrefix="string"
          android:mimeType="string" />

data由兩部分組成,mimeType和URI,mimeType指媒體類型,比如image/jpeg,video/*等,可以便是圖片,音頻等不同媒體格式,而URI包含的就比較多了

<scheme>://<host>:<port>/[<path>|<pathPrefix>|<pathPattern>]

這個給個例子就比較好理解了

http://www.baidu.com:80/serch/info

Scheme:URI的模式,比如http,file,content等
Host:URI的主機名,比如www.baidu.com
port:端口號,比如80,僅當URI中指定了scheme和host參數的時候port纔有意義
path,pathPattern和pathPrefix:這三個參數標示路徑信息,path標示完整路徑信息,pathPattern也標示完成路徑信息,但是它裏面可以包含通配符*,來代替0個或者多個任意字符,pathPrefix標示路徑前綴信息

data匹配規則,
data數據能夠完全匹配過濾規則中某一個data
舉個栗子

     <intent-filter >  
                     <action android:name="com.intenttest.OTHER"/>  
                     <category android:name="android.intent.category.DEFAULT"/>  
                     <data android:scheme="test" android:host="www.google.com" android:port="80"/>  
                     <data android:scheme="test2"/>  
                     <data android:mimeType="text/*" />  
                 </intent-filter>  

匹配代碼如下

    Intent intent = new Intent();  
    intent.setAction("com.intenttest.OTHER ");  
    Uri data = Uri.parse("test://www.google.com:80");  
    intent.setDataAndType(data, "text/*");  
    startActivity(intent);  
* 如果設置了多個data,只要匹配一個就可以啓動這個activity。

* 如果設置了 <data android:scheme="test" android:host="www.google.com" android:port="80"/>,必須完全匹配 Uri data = Uri.parse("test://www.google.com:80");才能啓動。

* 如果 <data android:scheme="test" android:host="www.google.com"/>,那麼 Uri data = Uri.parse("test://www.google.com:80"),Uri data = Uri.parse("test://www.google.com:88"), Uri data = Uri.parse("test://www.google.com")都可以匹配。

* 如果只設置了 <data android:scheme="test"/>,那麼 Uri data = Uri.parse("test://")就可以匹配,後面也可以加其他參數。

* 如果設置了mimeType,那麼必須使用 intent.setDataAndType(data, "text/*");啓動activity。

最後,我們隱式方式啓動activity的時候最好加上判斷避免沒有匹配的activity的錯誤,
利用Intent的resolveActivity方法,如果找不到匹配的組件就會返回null

另外action和category中,有一類比較重要

<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />

這兩者共同作用是用來標明這是一個activity入口,並且會出現在系統的應用列表中,少了任何一個都沒有意義,也無法出現在系統的應用列表中,

發佈了51 篇原創文章 · 獲贊 359 · 訪問量 28萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章