Activity

生命周期

一.正常情況下生命周期如圖
這裡寫圖片描述
正常生命周期 開起activity調用onCreate() onStart() onResume(),按下返回鍵 onPause() onStop() onDestroy()vcD4NCjxwPsb0tq9hY3Rpdml0eSBvbkNyZWF0ZSgpIG9uU3RhcnQoKSBvblJlc3VtZSgpPGJyIC8+DQrH0Lu7tb3XwMPmu/LV37Tyv6rSu7j20MK1xGFjdGl2aXR5IG9uUGF1c2UoKSBvblN0b3AoKSC3tbvYuMNhY3Rpdml0eSBvblJlc3RhcnQoKSBvblN0YXJ0KCkgb25SZXN1bWUoKTxiciAvPg0KyOe5+7Tyv6rQwrXEYWN0aXZpdHnW98ziyejWw86qzbjD9yDU8iBvblBhdXNlKCkgt7W72LjDYWN0aXZpdHkgb25SZXN1bWUoKTwvcD4NCjxwPtXiwO/OqNK70OjSqtei0uK1xMrHLLWxx7BhY3Rpdml0eSBvblBhdXNlKCm3vbeo1rTQ0LrzILLFu+HWtNDQ0MK1xGFjdGl2aXR5tcRvbkNyZWF0ZSgpLNKyvs3Kx8u1b25QYXVzZSgpsrvE3L340NC6xMqxstnX9yyyu8i7u+HTsM/sz8K49mFjdGl2aXR5z9TKvsvZtsg8L3A+DQo8cD48aW1nIGFsdD0=”這裡寫圖片描述” src=”/uploadfile/Collfiles/20160912/20160912092720651.png” title=”\” />

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

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橫豎屏切換後,不會重新創建


    
        
            

            
        
    

    

橫豎屏切換後,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


    
        
            

            
        
    

    

清單就給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,看是否創建新的任務棧
清單如下


    
        
            

            
        
    

    

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

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

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


    
        
            

            
        
    

    

    

每個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由兩部分組成,mimeType和URI,mimeType指媒體類型,比如image/jpeg,video/*等,可以便是圖片,音頻等不同媒體格式,而URI包含的就比較多瞭

://:/[||]

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

https://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 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。

* 如果設置瞭 ,必須完全匹配 Uri data = Uri.parse("test://www.google.com:80");才能啟動。

* 如果 ,那麼 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")都可以匹配。

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

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

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

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


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

發佈留言