Activity的生命周期
- onCreate: 聲明周期的第一個方法.做一些初始化的動作,例如setContentView
- onRestart: 表示Activity重新啟動.當界面從不可見變為可見時調用,場景Home鍵切換,從任務棧返回
- onStart: 表示Activity正在被啟動.Activity為理論可見(取決上層界面是否透明),但不是前臺無法操作.
- onResume: 表示Activity已經可見,并且為前臺. 與onStart主要是前臺后臺,有無焦點的區別
- onPause: 表示Activity正在停止. 做一些存儲數據,停止動畫操作.不能太耗時,這會影響新的Activity啟動,onPause()必須先執行完,新Activity的onResume()才會執行.
- onStop: 表示Activity即將停止,可以做稍微重量級的回收工作,同樣不能太耗時.
- onDestroy: 表示Activity即將被銷毀. 可以做一些最終的資源釋放
場景 當界面A打開界面B時的生命周期調用順序: A的onPause()-> B的onCreate() -> B的onStart() -> B的onResume() -> A的onStop().
-
onStart()
和onStop()
是針對Activity是否可見的角度回調 -
onResume()
和onPause()
是針對Activity是否位于前臺的角度回調的
異常情況下的生命周期
當系統配置發生改變的時候
Activity就會因為異常情況被銷毀并重新創建.例如橫豎屏切換,語言切換等.
當異常狀態發生的時候. 在界面銷毀前會調用onSaveInstanceState()
進行當前界面的數據保存,如文本輸入的數據,listView滾動的位置等. 在重建后會調用onRestoreInstanceState()
進行因為異常重建的原始數據的恢復.
準確的說onSaveInstanceState
() 會在onStop
()之前執行, 而onRestoreInstanceState會在onStart之后執行.
當屏幕發生旋轉時,聲明周期調用過程如下:
這里我們要清楚,當發生了異常情況下,系統會幫我們自動恢復大部分的數據,但是如果我們想要自己從異常中恢復.那么我們可以通過onCreate()
和onRestoreInstanceState()
中的參數Bundle
來進行值得保存.
- 當正常情況下onCreate中的
Bundle
類型參數是為null的. 而onRestoreInstanceState是不會被調用的. - 當異常發生后的重建,onCreate,onRestoreInstanceState都會被觸發,并且其中的Bundle類型參數都不為null
資源內存不足時低優先級的Activity被殺死
這種場景不好模擬,但是在存儲和恢復的過程是與上面的過程一致的.
關于Activity的優先級的高低
- 前臺Activity–正在和用戶交互的Activity,優先級最高
- 可見但是非前臺Activity–比如Activity中彈出了一個對話框,導致Activity可見但是無法操作.
- 后臺Activity–已經被暫停的Activity,比如執行了onStop,優先級最低.
禁止異常重建Activity
如果不想Activity重建.可以通過清單文件中對activity標簽進行配置.
android:configChanges="orientation|screenSize"
當給一個Activity聲明了上述的屬性之后,當手機旋轉的時候,activity不會重建,也就沒有任何聲明周期方法的回調, 但是會調用onConfigurationChanged()
方法.
- orientation: 屏幕方向發生了改變,手機旋轉
- screenSize: 屏幕的尺寸信息發生改變,此屬性和編譯版本有關系,當minSdkVersion和targetSdkVersion均低于13,此選項不會導致界面重啟.如果高于那么會重啟.
所以這兩個最好成對出現.
日常開發中我們比較常用的local
,orientation
,keyboardHidden
,uiMode
. local為本地語言切換. uiMode界面模式發生切換,如夜間模式(API8中增加)
Activity的啟動模式
當我們打開的activity會被系統以任務棧的形式來存儲起來.后進先出.當每一個任務棧為空的時候這個棧就會被回收
- standard: 默認模式,就是新進入的壓在已存在的界面之上.
- singleTop: 棧頂復用模式.
- singleTask: 棧內復用模式
- singleInstance: 棧內單例模式
singleTop
如果新Activity已經位于任務棧的棧頂,那么此Activity不會被重新創建,同時onNewIntent()
會被回調.
如果使用此模式,那么在任務棧中棧頂到棧低為CBA的情況下,再次打開C,那么C界面的onCreate()
和onStart()
不會被調用,真正的調用時onPause()
–>onNewIntent()
–>onResume()
singleTask
如果用棧內復用,當打開C時候,會查詢所有的任務棧,如果有任務棧包含C,那么把這個任務棧移動到所有棧的首位,并清除掉這個棧內C到棧頂的其他Activity,最后調用C的onNewIntent()
方法. 如果沒有那就直接在所需任務棧的棧頂創建C的實例.
這里由于singleTask默認具有clearTop的效果,所以會清除C以上activity的出棧. 這里和具體的啟動模式有關.
所需任務棧: 和一個參數有關系,TaskAffinity
.這個參數標識了一個Activity所需要的任務棧的名字,默認情況下所有的Activity的所需的任務棧的名字都為應用包名.
這里我們可以通過命令adb shell dumpsys activity
測試一下,打開順序為MainActivity–>ModeSingleTopArc
當我們不指定taskAffinity
的所需棧的時候,查看任務棧的結果為:
如果指定了taskAffinity
這個時候任務棧的狀況為:
<activity android:name=".launchmode.ModeSingleTaskAct"
android:launchMode="singleTask"
android:taskAffinity=".nishibendan"/>
一般情況下TaskAffinity
屬性一般和singleTask啟動模式或者allowTaskReparenting屬性配對使用,其他情況下使用沒有意義. 另外任務棧分為前臺任務棧和后臺任務棧,后臺任務棧中的activity屬于暫停狀態,用戶可以切換將后臺調到前臺.
singleInstance
這個模式是加強版的singleTask,除了singleTask具有的屬性之外,還具有創建新棧的能力,這個棧只有這一個實例. 就是說如果假設EActivity沒有被創建過,那么創建時,首先會創建一個新的任務棧,然后創建實例放入這個新的棧內,然后下一個實例不會和這個EActivity所屬棧共存,會創建一個新的棧繼續存放.
給Activity添加啟動模式
- 通過清單文件中
activity標簽
添加android:launchMode="singleTask|singleTop|singleInstance"
- 通過代碼中startActivity(Intent)中的intent通過
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
優先級 代碼設置優先于xml布局中的設置.
限定范圍不同
- xml無法直接為其設置
FLAG_ACTIVITY_CLEAR_TOP
. - 代碼添加flag無法添加
singleInstance
模式.
注意: 如果通過代碼添加添加Intent.FLAG_ACTIVITY_NEW_TASK
和xml中設置singleTask
是不一樣的.代碼動態添加是沒有clean_top的效果,看圖:
Activity的Flags
影響啟動模式的標識位: 可以設定Activity的啟動模式
影響運行時的標識位: 可以影響運行的Activity的運行狀態
FLAG_ACTIVITY_NEW_TASK
相對于xml中的singleTask,但是動態的時候需要給intent添加兩個標志位,否則無法達到效果,如下
intent3.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent3.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
-
FLAG_ACTIVITY_SINGLE_TOP
相對于xml中的singleTop -
FLAG_ACTIVITY_CLEAR_TOP
在同一個任務棧中所有位于它上面的Activity都要出棧,此標志一般和singleTask一起出現. 如果啟動模式為standard
它連同上面的都要出棧,之后系統會創建一個新的實例放入棧頂.
IntentFilter匹配規則
- 顯示調用: 需要指定組件信息如包名,類名.
- 隱式調用: 不需要指定組件信息, Intent需要匹配目標組件IntentFilter中所設置的過濾信息即可.
IntentFilter的過濾信息有action,category,data. 一個Activity可以有多個Intentfilter,只要intent能匹配任意一組intent-filter即可成功啟動.
關于每一項的具體匹配規則
- action: 本身是字符串. Intent匹配中存在action且必須和過濾規則中的其中一個action相同,那么action就算匹配ok. xml中必須有一個action聲明.
-
category: 本身是字符串.Intent匹配中intent可以不存在category,但是如果添加了category那么必須要和定義的
intent-filter
中的category一致,否則失敗. 如果我們在Intent不添加的時候,那么系統會自動為我們添加一個預定義的屬性android.intent.category.DEFAULT
. xml中需添加這個默認的category屬性. - data: xml過濾規則中可以不聲明,如果聲明只要匹配了一個就可以.一般情況下data有兩部分組成mimeType和URI. mimeType指媒體類型,URI規則如下:
<scheme>://<host>:<port>/[path]
如下實例: scheme:表示URI的模式,例如http, file, content.
http://www.baidu.com:80/search/info
最簡單的隱式打開
//代碼中
Intent intent = new Intent();
intent.setAction("com.test1");
startActivity(intent);
//xml中
<activity android:name=".intentfilter.FilterAct">
<intent-filter>
<action android:name="com.test1"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
</pre>
一個比較完整的匹配代碼
//代碼中
Intent intent = new Intent();
intent.setAction("com.test1");
intent.addCategory("com.category1");
//intent.setDataAndType(Uri.parse("content://"),"audio/plain");//如果過濾規則中沒有聲明URI的屬性,那么會有默認值content和file的屬性
intent.setDataAndType(Uri.parse("http://"),"audio/plain");
startActivity(intent);
//xml中
<activity android:name=".intentfilter.FilterAct">
<intent-filter>
<action android:name="com.test1"/>
<action android:name="com.test2"/>
<category android:name="com.category1"/>
<category android:name="com.category2"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:mimeType="text/plain"/>
<data android:mimeType="audio/plain" android:scheme="http"/>
</intent-filter>
</activity>
在進行data屬性匹配的時候盡量使用setDataAndType
, 因為源碼中setData
和setType
會把彼此屬性置為null.
Intent-filter匹配規則對于Service和BroadcastReceiver也是同樣. 不過建議Service的使用盡量使用顯示調用服務.
判斷是否有匹配的Intent
- PackageManager的
resolveActivity()
方法 - Intent的
resolveActivity()
方法
使用演示:
ResolveInfo resolveInfo = getPackageManager().resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY);
參數二使用PackageManager.MATCH_DEFAULT_ONLY
,意義在僅僅匹配哪些在intent-filter
中聲明<category android:name="android.intent.category.DEFAULT"/>
,只要使用這個標記不返回null,那么startActivity就一定可以打開. 如果不用這個標記就會把沒有設置default的匹配出來.從而導致判斷失敗.因為不含有DEFAULT這個category的Activity是無法接收隱式Intent的
這樣如果返回的為null,那就是沒有匹配到,如果不為null那就是可以匹配.
參看文章
《Android 開發藝術探索》書集
《Android 開發藝術探索》 01-Activity的生命周期和模式
https://github.com/feiwodev/AndroidDevelopmentArt