Android 四大組件(一) —— Activity 知識體系

一、生命週期

Google官方公佈的Activity生命週期圖:

1.正常流程

Created with Raphaël 2.2.0onCreateonStartonResume程序運行中onPauseonStoponDestroy

PS:打印生命週期的代碼時,可以在當前方法中使用Thread.currentThread().stackTrace[2].methodName獲取當前方法名。或提取出方法,使用stackTrace[3]:

fun getCurrentMethod(): String {
    return Thread.currentThread().stackTrace[3].methodName
}

2.跳轉到另一個頁面再返回

Created with Raphaël 2.2.0onResume跳轉到第二個頁面onPause第二個頁面onCreate第二個頁面onStart第二個頁面onResumeonSaveInstanceStateonStop回到當前頁面第二個頁面onPauseonRestartonStartonResume第二個頁面onStop第二個頁面onDestory

需要注意的是,onPause是在頁面銷燬時立即執行,onPause執行完後第二個頁面才能執行onCreate -> onStart -> onResume流程,onStop是在第二個頁面onResume之後才執行。
由此可知:onPause中不應做太耗時的操作,如果onPause太耗時會影響新Activity的顯示。

3.旋轉手機

如果當前Activity沒有在AndroidManifest中配置android:configChanges="orientation",旋轉手機時頁面將重建,生命週期如下:

Created with Raphaël 2.2.0onResume旋轉手機onPauseonSaveInstanceStateonStoponDestoryonCreateonStartonRestoreInstanceStateonResume

這和調用Activity的recreate方法的生命週期是一樣的。
如果當前Activity在AndroidManifest中配置了android:configChanges="orientation",旋轉手機時頁面不會重建,生命週期如下:

Created with Raphaël 2.2.0onResume旋轉手機onConfigurationChanged

每旋轉一次,就會調用一次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()觸發時機

onSaveInstanceStateonRestoreInstanceState並不是生命週期方法,不一定被觸發。以下是onSaveInstanceState調用時機:

(1)、按下HOME鍵時;
(2)、查看最近運行的程序時;
(3)、息屏時;
(4)、跳轉到另一個Activity時;
    這四個調用時機都屬於同一種情況:當前 Activity 可能因爲長時間不使用或者系統內存不足被銷燬,如果Activity確實被銷燬了,再次回到當前頁面時Activity會重建,onRestoreInstanceState纔會被調用,否則不會被調用。
(5)、Activity重建時。
    此時 Activity 必然被銷燬,onRestoreInstanceState 必然被調用。

總而言之,onSaveInstanceState 的調用遵循一個重要原則,即當系統存在 未經你許可銷燬你的activity的可能時, onSaveInstanceState 會被系統調用,因爲這是系統的責任,它必須要提供一個機會讓你保存你的數據。如果Activity確實被銷燬了,再次回到當前頁面時Activity會重建, onRestoreInstanceState 會被調用,並且把onSaveInstanceState 中保存的Bundle對象作爲參數傳遞給 onRestoreInstanceStateonCreate 方法。

如果是用戶正常銷燬Activity,onSaveInstanceState不會被調用。例如用戶按下返回鍵或程序調用finish方法銷燬Activity時,不會觸發onSaveInstanceState

另外,和Activity一樣,每個View都有onSaveInstanceStateonRestoreInstanceState 方法,android自帶的UI控件都恰當的實現了onSaveInstanceStateonRestoreInstanceState 方法。因此,當activity被摧毀和重建時,這些UI控件會自動保存和恢復狀態數據。比如EditText控件會自動保存和恢復輸入的數據,CheckBox控件會自動保存和恢復選中狀態。開發者只需要爲這些控件指定一個唯一的ID(通過設置android:id屬性即可),剩餘的事情就可以自動完成了。如果沒有爲控件指定ID,這個控件不會進行自動的數據保存和恢復。

二、啓動模式

1.標準模式:android:launchMode="standard"

這是默認的啓動模式,每次啓動Activity時都會創建一個新的Activity,無論同樣的Activity是否已經存在。

2.棧頂複用模式:android:launchMode="singleTop"

如果要啓動的新Activity已經在棧頂,此Activity不會被重建,生命週期回調如下:

Created with Raphaël 2.2.0onPauseonNewIntentonResume

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開發藝術探索》

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章