文章目錄
一、生命週期
Google官方公佈的Activity生命週期圖:
1.正常流程
PS:打印生命週期的代碼時,可以在當前方法中使用Thread.currentThread().stackTrace[2].methodName
獲取當前方法名。或提取出方法,使用stackTrace[3]:
fun getCurrentMethod(): String {
return Thread.currentThread().stackTrace[3].methodName
}
2.跳轉到另一個頁面再返回
需要注意的是,onPause
是在頁面銷燬時立即執行,onPause
執行完後第二個頁面才能執行onCreate -> onStart -> onResume
流程,onStop
是在第二個頁面onResume
之後才執行。
由此可知:onPause
中不應做太耗時的操作,如果onPause
太耗時會影響新Activity的顯示。
3.旋轉手機
如果當前Activity沒有在AndroidManifest中配置android:configChanges="orientation"
,旋轉手機時頁面將重建,生命週期如下:
這和調用Activity的recreate
方法的生命週期是一樣的。
如果當前Activity在AndroidManifest中配置了android:configChanges="orientation"
,旋轉手機時頁面不會重建,生命週期如下:
每旋轉一次,就會調用一次onConfigurationChanged
。可以通過判斷newConfig?.orientation獲取當前手機屏幕方向。
override fun onConfigurationChanged(newConfig: Configuration?) {
super.onConfigurationChanged(newConfig)
when (newConfig?.orientation) {
Configuration.ORIENTATION_PORTRAIT -> {
// 現在是垂直方向
}
Configuration.ORIENTATION_LANDSCAPE -> {
// 現在是橫置方向
}
}
}
其他配置也是一樣,當配置發生改變時,默認重建Activity。如果不希望Activity在配置發生改變時重建,在AndroidManifest中配置相應的configChanges
屬性即可。
4.onSaveInstanceState() 和 onRestoreInstanceState()觸發時機
onSaveInstanceState
和 onRestoreInstanceState
並不是生命週期方法,不一定被觸發。以下是onSaveInstanceState
調用時機:
(1)、按下HOME鍵時;
(2)、查看最近運行的程序時;
(3)、息屏時;
(4)、跳轉到另一個Activity時;
這四個調用時機都屬於同一種情況:當前 Activity 可能因爲長時間不使用或者系統內存不足被銷燬,如果Activity確實被銷燬了,再次回到當前頁面時Activity會重建,onRestoreInstanceState
纔會被調用,否則不會被調用。
(5)、Activity重建時。
此時 Activity 必然被銷燬,onRestoreInstanceState
必然被調用。
總而言之,onSaveInstanceState
的調用遵循一個重要原則,即當系統存在 未經你許可銷燬你的activity的可能時, onSaveInstanceState
會被系統調用,因爲這是系統的責任,它必須要提供一個機會讓你保存你的數據。如果Activity確實被銷燬了,再次回到當前頁面時Activity會重建, onRestoreInstanceState
會被調用,並且把onSaveInstanceState
中保存的Bundle對象作爲參數傳遞給 onRestoreInstanceState
和 onCreate
方法。
如果是用戶正常銷燬Activity,onSaveInstanceState
不會被調用。例如用戶按下返回鍵或程序調用finish
方法銷燬Activity時,不會觸發onSaveInstanceState
。
另外,和Activity一樣,每個View都有onSaveInstanceState
和onRestoreInstanceState
方法,android自帶的UI控件都恰當的實現了onSaveInstanceState
和onRestoreInstanceState
方法。因此,當activity被摧毀和重建時,這些UI控件會自動保存和恢復狀態數據。比如EditText控件會自動保存和恢復輸入的數據,CheckBox控件會自動保存和恢復選中狀態。開發者只需要爲這些控件指定一個唯一的ID(通過設置android:id屬性即可),剩餘的事情就可以自動完成了。如果沒有爲控件指定ID,這個控件不會進行自動的數據保存和恢復。
二、啓動模式
1.標準模式:android:launchMode="standard"
這是默認的啓動模式,每次啓動Activity時都會創建一個新的Activity,無論同樣的Activity是否已經存在。
2.棧頂複用模式:android:launchMode="singleTop"
如果要啓動的新Activity已經在棧頂,此Activity不會被重建,生命週期回調如下:
在onNewIntent
中可以獲取到新啓動請求的Intent信息。
3.棧內複用模式:android:launchMode="singleTask"
在這種模式下,只要Activity在任何一個Activity棧中存在,那麼多次啓動此Activity都不會重新創建實例,和singleTop
一樣,系統也會回調其onNewIntent
。
singleTask
默認具有clearTop
的效果,即:如果啓動的Activity在棧中已經存在,啓動時會將位於其上面的Activity全部出棧。
4.單實例模式:android:launchMode="singleInstance"
這是一種加強的singleTask
模式,它除了具有singleTask
模式的所有特性外,還加強了一點,那就是具有此種模式的Activity只能單獨地位於一個任務棧中。
注:如果需要指定Activity運行在一個另一個棧中,除了使用singleInstance
外,還可以使用taskAffinity
屬性+singleTask
啓動模式。taskAffinity
屬性默認爲包名,指定時至少需要包含一個分隔符"."
。例如:
<activity
android:name=".SecondActivity"
android:taskAffinity="com.myTask"
android:launchMode="singleTask" />
查看Activity棧信息的代碼如下:
val activityManager = getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
// maxNum表示最多獲取多少個Activity棧
val maxNum = 10
val runningTasks = activityManager.getRunningTasks(maxNum)
Log.d("~~~","taksNums = ${runningTasks.size}")
runningTasks.forEach {
Log.d("~~~", "numActivities = ${it.numActivities},topActivityName = ${it.topActivity.shortClassName}
}
使用前需要先在AndroidManifest中添加權限:
<!--獲取Activity任務棧 權限-->
<uses-permission android:name="android.permission.GET_TASKS" />
此權限和getRunningTasks
方法都已經標記過時了,此方法的註釋上解釋道因爲不安全所以限制了使用,但使用時仍然可以獲取到自己程序的任務棧,stackoverflow上已有過討論,所以請放心使用。
三、隱式啓動Activity
使用IntentFilter隱式啓動Activity時,需要action、category、data信息同時匹配成功才能啓動。
一個Activity中可以有多個intent-filter,只要Intent能夠匹配任何一組intent-filter,即可成功啓動Activity。
1.action匹配規則
Intent中必須有action,且必須和intent-filter中的其中一個action相同,action區分大小寫;
2.category匹配規則
每一個Intent在 startActivity 或 startActivityForResult 時都會默認添加android.intent.category.DEFAULT
這個category,所以自定義intent-filter時必須添加android.intent.category.DEFAULT
。
Intent中可以不指定category,因爲android.intent.category.DEFAULT
這個category已經能夠匹配intent-filter,如果指定了其他category,則必須和intent-filter中的category其中一個相同。
3.data匹配規則
如果intent-filter中定義了data,與action類似,Intent中必須有data,且必須和intent-filter中的其中一個data相同才能匹配。
data語法如下:
<data
android:scheme="string"
android:host="string"
android:port="8080"
android:path="/string"
android:pathPattern="string"
android:pathPrefix="/string"
android:mimeType="string"/>
含義是:
<scheme>://<host>:<port>/[<path>|<pathPrefix>|<pathPattern>]
例如:
content://com.example.project:200/folder/subfolder/etc
http://www.baidu.com:80/search/info
4.隱式跳轉的例子
AndroidManifest中定義intent-filter:
<activity android:name=".SecondActivity">
<intent-filter>
<action android:name="action" />
<category android:name="category" />
<category android:name="android.intent.category.DEFAULT" />
<data
android:mimeType="image/*"
android:scheme="content" />
</intent-filter>
</activity>
Intent設置action、category、data隱式跳轉:
val intent = Intent()
intent.action = "action"
intent.addCategory("category")
intent.setDataAndType(Uri.parse("content://abc"), "image/png")
startActivity(Intent.createChooser(intent, "Choose app"))
注:隱式啓動時,如果沒有任何Activity匹配此Intent,程序會報android.content.ActivityNotFoundException: No Activity found to handle Intent
異常,所以推薦做法是:
(1)使用
Intent.createChooser(intent, "Choose app")
(2)或檢查
packageManager.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY) != null
(3)或檢查
intent.resolveActivity(packageManager) != null
5.啓動Activity的標準的Action常量及對應的字符串
Action常量 | 對應字符串 | 簡單說明 |
---|---|---|
ACTION_MAIN | android.intent.action.MAIN | 應用程序入口 |
ACTION_VIEW | android.intent.action.VIEW | 顯示指定數據 |
ACTION_ATTACH_DATA | android.intent.action.ATTACH_DATA | 指定某塊數據將被附加到其他地方 |
ACTION_EDIT | android.intent.action.EDIT | 編輯指定數據 |
ACTION_PICK | android.intent.action.PICK | 從列表中選擇某項並返回所選的數據 |
ACTION_CHOOSER | android.intent.action.CHOOSER | 顯示一個Activity選擇器 |
ACTION_GET_CONTENT | android.intent.action.GET_CONTENT | 讓用戶選擇數據,並返回所選數據 |
ACTION_DIAL | android.intent.action.DIAL | 顯示撥號面板 |
ACTION_CALL | android.intent.action.CALL | 直接向指定用戶打電話 |
ACTION_SEND | android.intent.action.SEND | 向其他人發送數據 |
ACTION_SENDTO | android.intent.action.SENDTO | 向其他人發送消息 |
ACTION_ANSWER | android.intent.action.ANSWER | 應答電話 |
ACTION_INSERT | android.intent.action.INSERT | 插入數據 |
ACTION_DELETE | android.intent.action.DELETE | 刪除數據 |
ACTION_RUN | android.intent.action.RUN | 運行數據 |
ACTION_SYNC | android.intent.action.SYNC | 執行數據同步 |
ACTION_PICK_ACTIVITY | android.intent.action.PICK_ACTIVITY | 用於選擇Activity |
ACTION_SEARCH | android.intent.action.SEARCH | 執行搜索 |
ACTION_WEB_SEARCH | android.intent.action.WEB_SEARCH | 執行Web搜索 |
ACTION_FACTORY_TEST | android.intent.action.FACTORY_TEST | 工廠測試的入口點 |
6.標準的Category常量及對應的字符串
Category常量 | 對應字符串 | 簡單說明 |
---|---|---|
CATEGORY_DEFAULT | android.intent.category.DEFAULT | 默認的Category |
CATEGORY_BROWSABLE | android.intent.category.BROWSABLE | 指定該Activity能被瀏覽器安全調用 |
CATEGORY_TAB | android.intent.category.TAB | 指定該Activity作爲TabActivity的Tab頁 |
CATEGORY_LAUNCHER | android.intent.category.LAUNCHER | Activity顯示頂級程序列表中 |
CATEGORY_INFO | android.intent.category.INFO | 用於提供包信息 |
CATEGORY_HOME | android.intent.category.HOME | 設置該Activity隨系統啓動而運行 |
CATEGORY_PREFERENCE | android.intent.category.PREFERENCE | 該Activity是參數面板 |
CATEGORY_TEST | android.intent.category.TEST | 該Activity是一個測試 |
CATEGORY_CAR_DOCK | android.intent.category.CAR_DOCK | 指定手機被插入汽車底座(硬件)時運行該Activity |
CATEGORY_DESK_DOCK | android.intent.category.DESK_DOCK | 指定手機被插入桌面底座(硬件)時運行該Activity |
CATEGORY_CAR_MODE | android.intent.category.CAR_MODE | 設置該Activity可在車載環境下使用 |
Category常量 | 對應字符串 | 簡單說明 |
CATEGORY_DEFAULT | android.intent.category.DEFAULT | 默認的Category |
CATEGORY_BROWSABLE | android.intent.category.BROWSABLE | 指定該Activity能被瀏覽器安全調用 |
CATEGORY_TAB | android.intent.category.TAB | 指定該Activity作爲TabActivity的Tab頁 |
CATEGORY_LAUNCHER | android.intent.category.LAUNCHER | Activity顯示頂級程序列表中 |
CATEGORY_INFO | android.intent.category.INFO | 用於提供包信息 |
CATEGORY_HOME | android.intent.category.HOME | 設置該Activity隨系統啓動而運行 |
CATEGORY_PREFERENCE | android.intent.category.PREFERENCE | 該Activity是參數面板 |
CATEGORY_TEST | android.intent.category.TEST | 該Activity是一個測試 |
CATEGORY_CAR_DOCK | android.intent.category.CAR_DOCK | 指定手機被插入汽車底座(硬件)時運行該Activity |
參考文章
Android開發之InstanceState詳解
獲取Activity棧,判斷當前Activity位置
《Android開發藝術探索》