Activity 的初級,中級,高級問法,你都掌握了嗎?
轉(zhuǎn)自:傷心的豬大腸?
Activity是我們常用App開發(fā)中最重要的組件,主要用于展示界面和用戶交互。本文分為三個部分:
Activity源碼和常見的問題
Activity的生命周期,正常情況和異常情況?
Activity的四種啟動模式,啟動頁設(shè)置singleTask/singleInstance可能造成什么后果?
任務(wù),任務(wù)棧,前臺任務(wù)棧,后臺任務(wù)棧,返回棧分別是什么?
startActivityForResult導(dǎo)致的一系列問題?
清除返回棧(Clearing the back stack)的一些概念
allowTaskReparenting的使用
Activity的隱式啟動
Activity啟動流程
Activity深層次問題
Activity生命周期的變化對進(jìn)程的優(yōu)先級有什么影響?
如果App還存在緩存進(jìn)程,這個時候啟動App,應(yīng)用Application的onCreate方法會執(zhí)行嗎?
一個Activity A啟動另一個Activity B,為何會先走A的onPause方法,等到B執(zhí)行完onResume方法后,才會走A的onStop方法呢?
為什么要這么設(shè)計(jì)Activity生命周期?
第三方App中一些Activity的設(shè)置
今日頭條極速版-新聞界面打開的一些限制和首頁
Activity源碼和常見的問題
1.Activity的生命周期,正常情況和異常情況?
首先來看看官網(wǎng)上Activity的生命周期,如下圖所示

生命周期在開發(fā)中會常常被我們用到,比如在界面的恢復(fù)和銷毀等回調(diào)具體的方法,我們在這些方法做一些數(shù)據(jù)的處理等。當(dāng)然這里面還少了方法onSaveInstance和onRestoreInstance方法用于狀態(tài)的保存和恢復(fù),還有一個方法onConfigurationChanged()用于配置變更后的回調(diào)。
下面是一些常用的生命周期回調(diào)流程:
啟動Activity:
onCreate()->onStart()->onResume()點(diǎn)擊返回鍵:
onPause()->onStop()->onDestroy()點(diǎn)擊Home鍵:
onPause()->onSaveInstanceState()->onStop(),注意在API28之后onSaveInstanceState()方法的執(zhí)行放在了onStop()之后。用戶再次回到原Activity:
onRestart()->onStart()->onResume()A Activity啟動B Activity:
A#onPause()->B#onCreate()->B#onStart()->B#onResume()->A#onStop()
再來看一下異常情況下的生命周期分析:
系統(tǒng)配置發(fā)配置變化時生命周期的回調(diào)(API28+)
onPause()->onStop()->onSaveInstanceState()->onDestroy(),然后當(dāng)Activity被重新創(chuàng)建后執(zhí)行onCreate()->onStart()->onRestoreInstanceState()->onResume()這里的配置發(fā)生變化可以指屏幕發(fā)生旋轉(zhuǎn)或者切換到多窗口模式等等。
系統(tǒng)配置發(fā)生改變時,如果不想重新創(chuàng)建Activity,可以通過在AndroidManifest.xml中配置
android:configChanges屬性,如果想做一些額外的操作可以在onConfigurationChanged回調(diào)中處理。資源內(nèi)存不足導(dǎo)致低優(yōu)先級進(jìn)程被回收,當(dāng)系統(tǒng)資源不足時,會殺死低優(yōu)先級進(jìn)程,此時會調(diào)用
onSaveInstanceState()和onRestoreInstanceState()進(jìn)行數(shù)據(jù)的存儲和恢復(fù)。
2.Activity的四種啟動模式,啟動頁設(shè)置SingleTask/SingleInstance可能造成什么后果?
在清單文件中聲明 Activity 時,可以使用?Activity?元素的?launchMode?屬性指定 Activity 應(yīng)該如何與任務(wù)關(guān)聯(lián)。
stardard:默認(rèn)模式,系統(tǒng)在啟動該 Activity 的任務(wù)中創(chuàng)建 Activity 的新實(shí)例,并將 intent 傳送給該實(shí)例。Activity 可以多次實(shí)例化,每個實(shí)例可以屬于不同的任務(wù),一個任務(wù)可以擁有多個實(shí)例。注意在該模式下配合FLAG_ACTIVITY_NEW_TASK 與 FLAG_ACTIVITY_CLEAR_TOP,單獨(dú)/一起配合,都會重新創(chuàng)建實(shí)例。
singleTop:棧頂復(fù)用模式,如果當(dāng)前任務(wù)的頂部已存在 Activity 的實(shí)例,則系統(tǒng)會通過調(diào)用其?
onNewIntent()?方法來將 intent 轉(zhuǎn)送給該實(shí)例,而不是創(chuàng)建 Activity 的新實(shí)例。在該模式下配合FLAG_ACTIVITY_CLEAR_TOP是用哪個,不會重新創(chuàng)建實(shí)例,會有類似SingleTask的效果,但是如果再加上FLAG_ACTIVITY_NEW_TASK,還是會創(chuàng)建新實(shí)例。singleTask:棧內(nèi)復(fù)用模式,系統(tǒng)會創(chuàng)建新任務(wù),并實(shí)例化新任務(wù)的根 Activity。但是,如果另外的任務(wù)中已存在該 Activity 的實(shí)例,則系統(tǒng)會通過調(diào)用其?
onNewIntent()?方法將 intent 轉(zhuǎn)送到該現(xiàn)有實(shí)例,而不是創(chuàng)建新實(shí)例。Activity 一次只能有一個實(shí)例存在。該模式默認(rèn)具有clearTop的效果。singleInstance:單實(shí)例模式,與?
"singleTask"?相似,唯一不同的是系統(tǒng)不會將任何其他 Activity 啟動到包含該實(shí)例的任務(wù)中。該 Activity 始終是其任務(wù)唯一的成員;由該 Activity 啟動的任何 Activity 都會在其他的任務(wù)中打開。關(guān)于singleInstance有個特殊的情況,如果一個A Activity(standard)啟動B Activity(singleInstance),這個時候用戶點(diǎn)擊了手機(jī)最近訪問列表,然后在再點(diǎn)擊該App所在的界面(卡片),然后這個時候點(diǎn)擊返回鍵竟然就直接退出了App,而不是我們預(yù)期的退到A Activity界面。其實(shí)最近訪問列表也是一個Activity(假設(shè)為C Activity),當(dāng)我們從這個C Activity點(diǎn)擊App卡片顯示我們的singleInstance所在的界面B,這個時候就相當(dāng)于C啟動了B,所以我們點(diǎn)擊返回鍵,就直接回到了桌面(有興趣可以自己看看源碼)。
還有一個特殊的情況(來自扔物線大佬的文章),就是在最近任務(wù)里看見的 Task 未必還活著,最近任務(wù)里看不見的 Task,也未必就死了,比如 singleInstance。當(dāng)我們查看最近任務(wù)的時候,不同的 Task 會并列展示出來,但有一個前提:它們的 taskAffinity 需要不一樣。在 Android 里,同一個 taskAffinity 可以被創(chuàng)建出多個 Task,但它們最多只能有一個顯示在最近任務(wù)列表。這也就是為什么剛才例子里 singleInstance 的那個 Activity 會從最近任務(wù)里消失了:因?yàn)樗涣硪粋€相同 taskAffinity 的 Task 搶了排面。
同理,你在一個App從首頁Activity新建一個Activity(singletask/singleInstance),如果沒有指定taskAffinity,這個Activity的taskAffinity和其他界面一樣,所以在最近的范圍列表,你也只能看到一個App的卡片,但是如果你taskAffinity設(shè)置的不一樣,就可以看到在最近列表中看到兩個了。
上面講到的任務(wù)對應(yīng)的是TaskRecord(棧結(jié)構(gòu)),其內(nèi)部維護(hù)了一個ArrayList用來保存和管理ActivityRecord,ActivityRecord包含了一個Activity的所有信息。
通常我們的App都會設(shè)置啟動頁(SplashActivity通常是一張圖片),然后進(jìn)入我們的主界面(MainActivity),在主界面中通常有很多邏輯會導(dǎo)致該界面異常龐大,占據(jù)的內(nèi)存很大,所以很多時候我們都會給該界面設(shè)置為SingleTask棧內(nèi)復(fù)用模式。
場景一:如果為了達(dá)到快速啟動的效果,將我們的App的閃屏頁(SplashActivity顯示固定圖片)移除掉,換成MainActivity(SingleTask/SingleInstance)的背景(windowBackground),最后再替換成App的主題,給用戶快速響應(yīng)的體驗(yàn);
場景二:如果給啟動頁SplashActivity設(shè)置為SingleTask/SingleInstance模式,同時你的啟動頁沒有及時的關(guān)閉。
以上兩種場景會導(dǎo)致你的App無論冷啟動還是熱啟動,每次點(diǎn)擊圖標(biāo)都是從啟動頁開始啟動的,具體的原理可以看我這篇文章的分析和解決方案。切記,不要在你的App啟動界面設(shè)置SingleTask/SingleInstance。
3.任務(wù),任務(wù)棧,前臺任務(wù)棧,后臺任務(wù)棧,返回棧分別是什么?
首先來看官網(wǎng)的說明Understand Tasks and Back Stack,(A task is a collection of activities that users interact with when performing a certain job. The activities are arranged in a stack—the?back stack)—in the order in which each activity is opened. )任務(wù)是用戶在執(zhí)行某項(xiàng)工作時與之互動的一系列 Activity 的集合。這些 Activity 按照每個 Activity 打開的順序排列在一個返回堆棧中。前面說過任務(wù)對應(yīng)的是TaskRecord(棧結(jié)構(gòu)),其內(nèi)部維護(hù)了一個ArrayList用來保存和管理ActivityRecord,ActivityRecord包含了一個Activity的所有信息。所以其實(shí)任務(wù)就是任務(wù)棧(TaskRecord是棧結(jié)構(gòu))。
那么返回棧是什么,首先展示一張Gityuan博客的圖片。
一般地,對于沒有分屏功能以及虛擬屏的情況下,ActivityStackSupervisor與ActivityDisplay都是系統(tǒng)唯一;
ActivityDisplay主要有Home Stack和App Stack這兩個棧;
每個ActivityStack中可以有若干個TaskRecord對象,當(dāng)前只會有一個獲得了焦點(diǎn)的ActivityStack;
每個TaskRecord包含如果若干個ActivityRecord對象;
每個ActivityRecord記錄一個Activity信息。
一個返回棧可能只包含一個任務(wù),但在特殊情況下,可能引入多個任務(wù)。這個概念非常重要,這里引用官方的圖

這里先說一下操作流程,依次啟動ActivityX,ActivityY,Activity1,Activity2;ActivityY,ActivityX(這兩個都是SingleTask)在后臺任務(wù)中,Activity2,Activity1在前臺任務(wù)中,這兩個任務(wù)的taskAffinity不同,當(dāng)從Activity2中啟動ActivityY的時候,返回棧如第二列所示,然后點(diǎn)擊返回鍵可以一個個退出。
再普及一個概念在 Android 里,每個 Activity 都有一個 taskAffinity,它就相當(dāng)于是對每個 Activity 預(yù)先進(jìn)行的分組。它的值默認(rèn)取自它所在的 Application 的 taskAffinity,而 Application 的 taskAffinity 默認(rèn)是 App 的包名。當(dāng)然也可以手動指定taskAffinity。
但是圖中并沒有指明Activity2,Activity1是什么啟動模式,實(shí)際上我如果我們指定為standard標(biāo)準(zhǔn)模式根本模擬不出這個場景,這一點(diǎn)有點(diǎn)坑,因?yàn)檫@四個Activity分別按2,1,X,Y排列,也即是說啟動是從Y,X,1,2一個個啟動的,如果Activity1為standard,就算你指定了Activity1的taskAffinity和ActivityY的不同也沒有用,Activity1還是會和ActivityY在同一個任務(wù)(TaskRecord)中,也就是說standard 和 singleTop 的 Activity 在哪個 TaskRecord 啟動,全憑啟動它的 Activity 在哪個 TaskRecord,taskAffinity在同時指定為singleTask模式下才有意義(只有一種例外,standard 和 singleTop在 allowTaskReparenting 為 true,且被其他應(yīng)用以 DeepLink 的方式喚起時,才會在指定的任務(wù)中)。
所以我們將Activity2,Activity1也設(shè)置為singleTask,同時taskAffinity也相同,才會模擬出上面的場景,點(diǎn)擊Activity2啟動ActivityY,才會將后臺任務(wù)棧ActivityY,ActivityX都帶到前臺任務(wù)棧中,也就是都帶到返回棧中。
小結(jié)
任務(wù)就是任務(wù)棧(TaskRecord是棧結(jié)構(gòu)),TaskRecord內(nèi)部維護(hù)了一個ArrayList用來保存和管理ActivityRecord,ActivityRecord包含了一個Activity的所有信息。
一個返回棧可能只包含一個任務(wù),但特殊情況下,可能引入多個任務(wù)。返回棧,前臺任務(wù)棧,后臺任務(wù)棧其實(shí)在源碼中并沒有明確的定義,而是在我們操作任務(wù)棧過程中提出的一些“概念”,為了便于描述和區(qū)分。
前臺棧比如現(xiàn)在下圖A中的Activity2,Activity1所在的任務(wù),后臺任務(wù)棧是ActivityY,ActivityX所在的任務(wù)。

但是問題來了,當(dāng)Activity2啟動ActivityY的時候,返回棧中的內(nèi)容如下圖B所示,這個時候前臺任務(wù)棧是什么呢?

這個時候后臺的任務(wù)棧(ActivityY,ActivityX)已經(jīng)返回到前臺,四個Activity都在前臺,此時返回堆棧中包含了轉(zhuǎn)到前臺任務(wù)中的所有Activity(這句話來自官網(wǎng)對這一場景的說明)。
問題又來了,比如我們前面說的后臺任務(wù)棧是在后臺等待恢復(fù)(比如ActivityX,ActivityY所在的棧),依次啟動ActivityX,ActivityY,Activity1,Activity2,如果你這個時候什么都不做,不斷點(diǎn)擊返回鍵,這四個Activity會一個個退出,這個時候你會不會覺得返回棧包含前臺任務(wù)棧和后臺任務(wù)棧。但是一開始圖A中返回棧(Back Stack)只標(biāo)明了Activity1,Activity2,這就出現(xiàn)矛盾了,但我的感覺返回棧就是字面上的含義,點(diǎn)擊返回鍵,能退出多少個Activity,那么這些Activity就都在返回棧中,返回棧就是一個概念,當(dāng)然你也可以理解它的大小動態(tài)變化的(點(diǎn)擊返回鍵的過程中可能大小可能新增)。
4.startActivityForResult導(dǎo)致的一系列問題?
在使用Activity的startActivityForResult啟動新界面時,在Api20以下調(diào)整時會直接返回Activity.RESULT_CANCELED,官方覺得不應(yīng)該在兩個任務(wù)之間setResult。在Api20及以上,對于非startActivity跳轉(zhuǎn),也就是reqeusetCode>=0,singleTask和SingleInstance模式啟動的Activity都不會新建一個任務(wù),還是在原來的棧中。同時官方也建議:
雖然所有 API 級別的?Activity?類均提供底層 [startActivityForResult()](developer.android.com/reference/a…, int)) 和 [onActivityResult()](developer.android.com/reference/a…, int, android.content.Intent)) API,但我們強(qiáng)烈建議您使用 AndroidX?Activity?1.2.0-alpha02?和?Fragment?1.3.0-alpha02?中引入的 Activity Result API。
5.清除返回棧(Clearing the back stack)的一些概念
如果用戶離開任務(wù)較長時間,系統(tǒng)會清除任務(wù)中除根 Activity 以外的所有 Activity。當(dāng)用戶再次返回到該任務(wù)時,只有根 Activity 會恢復(fù)。系統(tǒng)之所以采取這種行為方式是因?yàn)椋?jīng)過一段時間后,用戶可能已經(jīng)放棄了之前執(zhí)行的操作,現(xiàn)在返回任務(wù)是為了開始某項(xiàng)新的操作。
您可以使用一些 Activity 屬性來修改此行為:
alwaysRetainTaskState如果在任務(wù)的根 Activity 中將該屬性設(shè)為?
"true",則不會發(fā)生上述默認(rèn)行為。即使經(jīng)過很長一段時間后,任務(wù)仍會在其堆棧中保留所有 Activity。clearTaskOnLaunch如果在任務(wù)的根 Activity 中將該屬性設(shè)為?
"true",那么只要用戶離開任務(wù)再返回,堆棧就會被清除到只剩根 Activity。也就是說,它與?alwaysRetainTaskState?正好相反。用戶始終會返回到任務(wù)的初始狀態(tài),即便只是短暫離開任務(wù)也是如此。finishOnTaskLaunch該屬性與?
clearTaskOnLaunch?類似,但它只會作用于單個 Activity 而非整個任務(wù)。它還可導(dǎo)致任何 Activity 消失,包括根 Activity。如果將該屬性設(shè)為?"true",則 Activity 僅在當(dāng)前會話中歸屬于任務(wù)。如果用戶離開任務(wù)再返回,則該任務(wù)將不再存在。
6.allowTaskReparenting的使用
Activity 默認(rèn)情況下只會歸屬于一個 Task,不會在多個 Task 之間跳來跳去,但你可以通過設(shè)置來改變這個邏輯。把它的 allowTaskReparenting 屬性設(shè)置為 true。如果未設(shè)置該屬性,則由??元素的相應(yīng)?allowTaskReparenting?屬性所設(shè)置的值。默認(rèn)值為“false”。
正常情況下,Activity 啟動時會與啟動它的任務(wù)關(guān)聯(lián),并在其整個生命周期中一直留在該任務(wù)處。當(dāng)不再顯示現(xiàn)有任務(wù)時,您可以使用該屬性強(qiáng)制 Activity 將其父項(xiàng)更改為與其有相似性的任務(wù)。該屬性通常用于將應(yīng)用的 Activity 轉(zhuǎn)移至與該應(yīng)用關(guān)聯(lián)的主任務(wù)。
例如,如果電子郵件消息包含網(wǎng)頁鏈接,則點(diǎn)擊該鏈接會調(diào)出可顯示該網(wǎng)頁的 Activity。該 Activity 由瀏覽器應(yīng)用定義,但作為電子郵件任務(wù)的一部分啟動。如果將該 Activity 的父項(xiàng)更改為瀏覽器任務(wù),則它會在瀏覽器下一次轉(zhuǎn)至前臺時顯示,在電子郵件任務(wù)再次轉(zhuǎn)至前臺時消失。
7.Activity的隱式啟動
Activity分為顯示啟動和隱式啟動,顯示啟動就是我們平時調(diào)用的一些startActivityXXX()方法,隱式啟動可以通過action來啟動,啟動時調(diào)用如下,同時要記得添加category為"android.intent.category.DEFAULT"。
Intent implicitIntent = new Intent();implicitIntent.setAction("com.test.image");implicitIntent.addCategory("android.intent.category.DEFAULT");MainActivity.this.startActivity(implicitIntent);
具體界面的配置如下:
<activity android:name=".ImageActivity"android:exported="true"><intent-filter><action android:name="android.intent.action.MAIN" /><action android:name="com.test.image" /><category android:name="android.intent.category.DEFAULT" /><category android:name="android.intent.category.LAUNCHER" />intent-filter>activity>
注意如果是其他App的Activity,需要添加android:exported="true"才能被調(diào)用。
8.Activity的啟動流程(中級問題)
對很多開發(fā)者來說,這可能都是個很沉重的問題,原因很簡單,因?yàn)榛卮鸩缓茫吘估锩嫔婕暗降臇|西很多,需要你擁有很大知識存儲量。下面來嘗試回答這個問題(基于源碼9.0)
首先先普及一些常見的概念
Instrumentation
Android Instrumentation是Android系統(tǒng)中的一套控制方法或者“鉤子”,這些鉤子可以在正常的生命周期(正常是由操作系統(tǒng)控制的)之外控制Android控件的運(yùn)行,其實(shí)指的就是Instrumentation類提供的各種流程控制方法。
app->instrumentation->ams->app,自動化測試可以通過Instrumentation來操作Activity等,這個Instrumentation相當(dāng)于設(shè)計(jì)了一個統(tǒng)一的入口。
ActivityThread
ActivityThread不是線程類(Thread),只不過它會跑在ActivityThread.main()方法中,安卓程序的入口就是該方法,同時在該方法中一個Looper不斷循環(huán)的在消息隊(duì)列中處理消息。管理應(yīng)用程序進(jìn)程中主線程的執(zhí)行,根據(jù)Activity管理者的請求調(diào)度和執(zhí)行activities、broadcasts及其相關(guān)的操作。
public static void main(String[] args) {// 看源碼很重要的一個能力就是‘眼中只有你’,認(rèn)不到的都忽略,看認(rèn)得到的···// 創(chuàng)建主線程的Looper對象,發(fā)現(xiàn)和工作線程創(chuàng)建Looper對象調(diào)用的方法不一樣,這里先記下,以后在詳解。// 主線程原來也有Looper對象啊Looper.prepareMainLooper();//創(chuàng)建ActivityThreadActivityThread thread = new ActivityThread();thread.attach(false);// 如果主線程的Handler為空(可以看出,一個好的命名可讀性是多么高),那就為主線程創(chuàng)建一個Handler。// 然后我們還可以在主線程創(chuàng)建Handler,說明一個線程對應(yīng)多個Handler。多讀源碼,很多問題都得到了解決啊。if (sMainThreadHandler == null) {sMainThreadHandler = thread.getHandler();}Looper.loop();// 這里拋了個異常,主線程loop異常退出。說明主線程loop不能退出,這里和前面建立Looper對象的調(diào)用方法有關(guān)throw new RuntimeException("Main thread loop unexpectedly exited");}
ActivityManagerService
Android中最核心的服務(wù),主要負(fù)責(zé)系統(tǒng)中四大組件的啟動、切換、調(diào)度及應(yīng)用程序的管理和調(diào)度等工作。
ActivityManager
該類提供與Activity、Service和Process相關(guān)的信息以及交互方法, 可以被看作是ActivityManagerService的輔助類。
ActivityStackSupervisor
負(fù)責(zé)所有Activity棧的管理。內(nèi)部管理了mHomeStack、mFocusedStack和mLastFocusedStack三個Activity棧。其中,mHomeStack管理的是Launcher相關(guān)的Activity棧;mFocusedStack管理的是當(dāng)前顯示在前臺Activity的Activity棧;mLastFocusedStack管理的是上一次顯示在前臺Activity的Activity棧。下面是大致的關(guān)系圖,對于沒有分屏功能以及虛擬屏的情況下,ActivityStackSupervisor與ActivityDisplay都是系統(tǒng)唯一。

ActivityStack
ActivityStack負(fù)責(zé)“Activity棧”的狀態(tài)和管理,ActivityStack內(nèi)部包含了多個任務(wù)棧(TaskRecord),TaskRecord內(nèi)部維護(hù)了一個ArrayList用來保存和管理ActivityRecord,ActivityRecord包含了一個Activity的所有信息
如果我們從桌面點(diǎn)擊啟動app,桌面就是一個Activity,點(diǎn)擊app(按鈕)啟動我們的啟動頁Activity,從這里分析Activity的啟動流程更加全面,而不是在app中去啟動一個普通的Activity。可以分為如下幾個流程
Launcher通知AMS啟動App的啟動頁Activity,AMS記錄要啟動的Activity信息,并且通知Launcher進(jìn)入pause狀態(tài)。
Launcher進(jìn)入pause狀態(tài)后,通知AMS已經(jīng)paused了,可以啟動App了
如果App未開啟過,AMS發(fā)送創(chuàng)建進(jìn)程請求,Zogyte進(jìn)程接受AMS請求并孵化應(yīng)用進(jìn)程,應(yīng)用進(jìn)程調(diào)用ActivityThread并調(diào)用mian()方法,并且main()方法中創(chuàng)建ActivityThread對象,
activityThread.attach()方法中進(jìn)行綁定(應(yīng)用進(jìn)程綁定到AMS),傳入applicationThread以便通訊。AMS通知App綁定Application(bindApplication)并啟動Activity,并且創(chuàng)建和關(guān)聯(lián)Context,最后調(diào)用onCreate等方法。
靈魂拷問:AMS,Zogyte,App進(jìn)程,Launcher如何通信?
這個問題一旦問出來,能干翻一大堆開發(fā)人員,下面來仔細(xì)講講:
App進(jìn)程和AMS是如何通信的?
Zogyte去fork一個App進(jìn)程,后面就是應(yīng)用進(jìn)程和AMS兩者的事情了,我們知道Android的跨進(jìn)程通信是通過Binder服務(wù)的,AMS所在的進(jìn)程和應(yīng)用進(jìn)程在通過Binder互相通信時,實(shí)際上都是通過兩者的代理類進(jìn)行通信的。
ActivityManagerService(AMS)在手機(jī)開機(jī)后時就已經(jīng)啟動了,應(yīng)用進(jìn)程去調(diào)用AMS的方法,比如startActivity,很容易調(diào)用,因?yàn)锳MS是一個有名稱的Binder服務(wù),在任意地方都可以通過在ServiceManger(SM)里面查詢拿到代理類,調(diào)用代理類的對應(yīng)方法,然后再去調(diào)用AMS的真正方法。

因?yàn)锽inder通信是通過代理類來通信的,如果拿不到代理類,其他進(jìn)程就不知道如何和我們的App通信,系統(tǒng)服務(wù)中的AMS也就不知道如何和我們App通信了,所以當(dāng)App進(jìn)程創(chuàng)建完成后,會進(jìn)行設(shè)置代理,代理的設(shè)置過程如圖

就是在ActivityThread.attach(false)方法中,AMS綁定ApplicationThread對象,即應(yīng)用進(jìn)程綁定到AMS,通過調(diào)用AMS的attachApplication來將ActivityThread的內(nèi)部類ApplicationThread對象綁定至AMS,這樣AMS就可以通過這個代理對象來控制應(yīng)用進(jìn)程。
AMS和Launcher是怎么通信的?
其實(shí)Launcher也是一個App,調(diào)用startActivity方法,然后調(diào)用的是Instrumentation的execStartActivity方法
public ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token, Activity target,Intent intent, int requestCode, Bundle options) {...try {...//獲取AMS的代理對象int result = ActivityManager.getService().startActivity(whoThread, who.getBasePackageName(), intent,intent.resolveTypeIfNeeded(who.getContentResolver()),token, target != null ? target.mEmbeddedID : null,requestCode, 0, null, options);checkStartActivityResult(result, intent);} catch (RemoteException e) {throw new RuntimeException("Failure from system", e);}return null;}
在這個方法會調(diào)用ActivityManager的getService方法來得到AMS的代理對象,然后調(diào)用這個代理對象的startActivity方法
public static IActivityManager getService() {return IActivityManagerSingleton.get();}private static final SingletonIActivityManagerSingleton = new Singleton() { protected IActivityManager create() {//得到activity的service引用,即IBinder類型的AMS引用final IBinder b = ServiceManager.getService(Context.ACTIVITY_SERVICE);//轉(zhuǎn)換成IActivityManager對象final IActivityManager am = IActivityManager.Stub.asInterface(b);return am;}};
可以發(fā)現(xiàn)在Singleton中的create方法中由于b是AMS引用作為服務(wù)端處于SystemServer進(jìn)程中,與當(dāng)前Launcher進(jìn)程作為客戶端與服務(wù)端不在同一個進(jìn)程,所以am返回的是IActivityManager.Stub的代理對象,此時如果要實(shí)現(xiàn)客戶端與服務(wù)端進(jìn)程間的通信,只需要在AMS繼承了IActivityManager.Stub類并實(shí)現(xiàn)了相應(yīng)的方法,而通過下面的代碼可以發(fā)現(xiàn)AMS剛好是繼承了IActivityManager.Stub類的,這樣Launcher進(jìn)程作為客戶端就擁有了服務(wù)端AMS的代理對象,然后就可以調(diào)用AMS的方法來實(shí)現(xiàn)具體功能了,就這樣Launcher的工作就交給AMS實(shí)現(xiàn)了。
public class ActivityManagerService extends IActivityManager.Stubimplements Watchdog.Monitor, BatteryStatsImpl.BatteryCallback {}
Zygote和AMS是如何通信的?
AMS和Zygote建立Socket連接,然后發(fā)送創(chuàng)建應(yīng)用進(jìn)程的請求。具體可以參考這里。
最后我們再來看看流程圖,看下方的App進(jìn)程啟動過程和Activity.startActivity這兩個流程

這里還要提到一點(diǎn),Hook Activity的啟動流程是一個很重要的運(yùn)用場景,我們需要欺騙AMS,然后啟動真正的TargetActivity,Hook有起始點(diǎn)和終點(diǎn)。這里需要尋找兩個地方的hook點(diǎn),一個是對Intent中Activity的替換(hookIActivityTaskManager方法),一個是對Intent中Activity的還原(hookHandler)。
在回答Activity的啟動流程時,具體的方法如何調(diào)用并不重要,所以我才會在最后放出整個流程,各個進(jìn)程之間如何建立通信,如何通信很重要,同時一些Activity相關(guān)概念也很重要,熟悉這些,你就很容易把整個流程串起來了。
Activity深層次問題
1.Activity生命周期的變化對進(jìn)程的優(yōu)先級有什么影響?
這里先看一下官網(wǎng)上Activity生命周期上對onStart的一段描述,onStart時候Activity就對用戶可見了

同時你也可以在《Android開發(fā)藝術(shù)探索》上看到類似的描述

但是了解Activity啟動流程源碼的朋友都知道,ActivityThread的handleResumeActivity方法中,首先調(diào)用Activity的onResume方法,接著會調(diào)用Activity.makeVisible()在該方法中,DecorView真正完成了添加和顯示這兩個過程,到這里Activity的視圖才能被看到。DecoreView和Window進(jìn)行關(guān)聯(lián)。有興趣可以看看我這篇文章的分析。
void makeVisible() {if (!mWindowAdded) {ViewManager wm = getWindowManager();//DecoreView和WindowManager進(jìn)行關(guān)聯(lián)。wm.addView(mDecor, getWindow().getAttributes());mWindowAdded = true;}//設(shè)置DecorView可見mDecor.setVisibility(View.VISIBLE);}
也就是說在onResume方法執(zhí)行之后再調(diào)用Activity.makeVisible()方法,我們才能真正用肉眼看到我們的DecoreView,看到這里你這里不禁會產(chǎn)生一個疑問,那上面官網(wǎng)上的說法(onStart()?調(diào)用使 Activity 對用戶可見)難道是錯誤的嗎?
帶著疑問我們繼續(xù)在官網(wǎng)上找答案,在進(jìn)程和生命周期這一章節(jié)上可以看到:
為了確定在內(nèi)存不足時應(yīng)該終止哪些進(jìn)程,Android 會根據(jù)每個進(jìn)程中運(yùn)行的組件以及這些組件的狀態(tài),將它們放入“重要性層次結(jié)構(gòu)”。這些進(jìn)程類型包括(按重要性排序):
前臺進(jìn)程是用戶目前執(zhí)行操作所需的進(jìn)程。在不同的情況下,進(jìn)程可能會因?yàn)槠渌母鞣N應(yīng)用組件而被視為前臺進(jìn)程。如果以下任一條件成立,則進(jìn)程會被認(rèn)為位于前臺:
它正在用戶的互動屏幕上運(yùn)行一個?
Activity(其?onResume()?方法已被調(diào)用)。它有一個?
BroadcastReceiver?目前正在運(yùn)行(其?BroadcastReceiver.onReceive()?方法正在執(zhí)行)。它有一個?
Service?目前正在執(zhí)行其某個回調(diào)(Service.onCreate()、Service.onStart()?或?Service.onDestroy())中的代碼。
系統(tǒng)中只有少數(shù)此類進(jìn)程,而且除非內(nèi)存過低,導(dǎo)致連這些進(jìn)程都無法繼續(xù)運(yùn)行,才會在最后一步終止這些進(jìn)程。通常,此時設(shè)備已達(dá)到內(nèi)存分頁狀態(tài),因此必須執(zhí)行此操作才能使用戶界面保持響應(yīng)。
可見進(jìn)程正在進(jìn)行用戶當(dāng)前知曉的任務(wù),因此終止該進(jìn)程會對用戶體驗(yàn)造成明顯的負(fù)面影響。在以下條件下,進(jìn)程將被視為可見:
相比前臺進(jìn)程,系統(tǒng)中運(yùn)行的這些進(jìn)程數(shù)量較不受限制,但仍相對受控。這些進(jìn)程被認(rèn)為非常重要,除非系統(tǒng)為了使所有前臺進(jìn)程保持運(yùn)行而需要終止它們,否則不會這么做。
它正在運(yùn)行的?
Activity?在屏幕上對用戶可見,但不在前臺(其?onPause()?方法已被調(diào)用)。舉例來說,如果前臺 Activity 顯示為一個對話框,而這個對話框允許在其后面看到上一個 Activity,則可能會出現(xiàn)這種情況。它有一個?
Service?正在通過?Service.startForeground()(要求系統(tǒng)將該服務(wù)視為用戶知曉或基本上對用戶可見的服務(wù))作為前臺服務(wù)運(yùn)行。系統(tǒng)正在使用其托管的服務(wù)實(shí)現(xiàn)用戶知曉的特定功能,例如動態(tài)壁紙、輸入法服務(wù)等。
服務(wù)進(jìn)程包含一個已使用?
startService()?方法啟動的?Service?。雖然用戶無法直接看到這些進(jìn)程,但它們通常正在執(zhí)行用戶關(guān)心的任務(wù)(例如后臺網(wǎng)絡(luò)數(shù)據(jù)上傳或下載),因此系統(tǒng)會始終使此類進(jìn)程保持運(yùn)行,除非沒有足夠的內(nèi)存來保留所有前臺和可見進(jìn)程。已經(jīng)運(yùn)行了很長時間(例如 30 分鐘或更長時間)的服務(wù)的重要性可能會降位,以使其進(jìn)程降至下文所述的緩存 LRU 列表。這有助于避免超長時間運(yùn)行的服務(wù)因內(nèi)存泄露或其他問題占用大量內(nèi)存,進(jìn)而妨礙系統(tǒng)有效利用緩存進(jìn)程。
緩存進(jìn)程是目前不需要的進(jìn)程,因此,如果其他地方需要內(nèi)存,系統(tǒng)可以根據(jù)需要自由地終止該進(jìn)程。在正常運(yùn)行的系統(tǒng)中,這些是內(nèi)存管理中涉及的唯一進(jìn)程:運(yùn)行良好的系統(tǒng)將始終有多個緩存進(jìn)程可用(為了更高效地切換應(yīng)用),并根據(jù)需要定期終止最早的進(jìn)程。只有在非常危急(且具有不良影響)的情況下,系統(tǒng)中的所有緩存進(jìn)程才會被終止,此時系統(tǒng)必須開始終止服務(wù)進(jìn)程。
這些進(jìn)程通常包含用戶當(dāng)前不可見的一個或多個?
Activity?實(shí)例(onStop()?方法已被調(diào)用并返回)。只要它們正確實(shí)現(xiàn)其 Activity 生命周期(詳情請見?Activity),那么當(dāng)系統(tǒng)終止此類流程時,就不會影響用戶返回該應(yīng)用時的體驗(yàn),因?yàn)楫?dāng)關(guān)聯(lián)的 Activity 在新的進(jìn)程中重新創(chuàng)建時,它可以恢復(fù)之前保存的狀態(tài)。這些進(jìn)程保存在偽 LRU 列表中,列表中的最后一個進(jìn)程是為了回收內(nèi)存而終止的第一個進(jìn)程。此列表的確切排序政策是平臺的實(shí)現(xiàn)細(xì)節(jié),但它通常會先嘗試保留更多有用的進(jìn)程(比如托管用戶的主屏幕應(yīng)用、用戶最后看到的 Activity 的進(jìn)程等),再保留其他類型的進(jìn)程。還可以針對終止進(jìn)程應(yīng)用其他政策:比如對允許的進(jìn)程數(shù)量的硬限制,對進(jìn)程可持續(xù)保持緩存狀態(tài)的時間長短的限制等。
可以看到在屏幕上運(yùn)行時一個Activity的onResume的方法已被調(diào)用,此時處于前臺進(jìn)
程;可見進(jìn)程的一個符合條件:它正在運(yùn)行的?Activity?在屏幕上對用戶可見,但不在前臺,然后再對比上面對onStart的描述(onStart()?調(diào)用使 Activity 對用戶可見,因?yàn)閼?yīng)用會為 Activity 進(jìn)入前臺并支持互動做準(zhǔn)備),這下子你就豁然開朗了,這里的onStart的可見指的是可見進(jìn)程的可見,而不是真正意義上的肉眼可見。
“onPause此方法表示 Activity 不再位于前臺(盡管在用戶處于多窗口模式時 Activity 仍然可見)”,“如果您的 Activity 不再對用戶可見,說明其已進(jìn)入“已停止”狀態(tài),因此系統(tǒng)將調(diào)用?onStop()?回調(diào)”,以上都是官方的描述,我們可以打印一下手機(jī)中的這些進(jìn)程,使用adb shell dumpsys meminfo命令,設(shè)備是android 10華為手機(jī)。

可以看到分別對應(yīng)我們的前臺進(jìn)程,可見進(jìn)程,服務(wù)進(jìn)程和緩存進(jìn)程,其中服務(wù)進(jìn)程還分為A Services和B Services。其實(shí)遠(yuǎn)遠(yuǎn)不止這么多的進(jìn)程級別區(qū)分,我自己的App打開后,然后點(diǎn)擊home鍵退到后臺,此時屬于Previous進(jìn)程(后臺進(jìn)程)級別(com.jackie.testdialog),如果我打開App后,點(diǎn)擊返回鍵退出,這個時候我的App進(jìn)程就變成了Cached進(jìn)程級別了。
講了這么多,你可能覺得一直沒有一個量化的數(shù)字,進(jìn)程的級別(oom_adj)的取值范圍是多少,在Android7.0之后,ADJ采用100,200,300等數(shù)字。下面是基于android9的區(qū)分:
| ADJ級別 | 取值 | 含義 |
|---|---|---|
| NATIVE_ADJ | -1000 | native進(jìn)程 |
| SYSTEM_ADJ | -900 | 僅指system_server進(jìn)程 |
| PERSISTENT_PROC_ADJ | -800 | 系統(tǒng)persistent進(jìn)程 |
| PERSISTENT_SERVICE_ADJ | -700 | 關(guān)聯(lián)著系統(tǒng)或persistent進(jìn)程 |
FOREGROUND_APP_ADJ | 0 | 前臺進(jìn)程 |
VISIBLE_APP_ADJ | 100 | 可見進(jìn)程 |
PERCEPTIBLE_APP_ADJ | 200 | 可感知進(jìn)程,比如后臺音樂播放 |
| BACKUP_APP_ADJ | 300 | 備份進(jìn)程 |
| HEAVY_WEIGHT_APP_ADJ | 400 | 重量級進(jìn)程 |
SERVICE_ADJ | 500 | 服務(wù)進(jìn)程 |
| HOME_APP_ADJ | 600 | Home進(jìn)程 |
| PREVIOUS_APP_ADJ | 700 | 上一個進(jìn)程 |
SERVICE_B_ADJ | 800 | B List中的Service |
CACHED_APP_MIN_ADJ | 900 | 不可見進(jìn)程的adj最小值 |
| CACHED_APP_MAX_ADJ | 906 | 不可見進(jìn)程的adj最大值 |
開發(fā)者應(yīng)該減少在保活上花心思,更應(yīng)該在優(yōu)化內(nèi)存上下功夫,因?yàn)樵谙嗤珹DJ級別的情況下,系統(tǒng)會選擇優(yōu)先殺內(nèi)存占用的進(jìn)程。當(dāng)然你也可以手動去測試App的進(jìn)程級別,不過過程可能有點(diǎn)麻煩,可以參考這篇文章。
小結(jié)
當(dāng)界面只有一個Activity時,它進(jìn)入onStart和onPause時是可見進(jìn)程,進(jìn)入onResume時是前臺進(jìn)程,打開后點(diǎn)擊Home鍵退到后臺這個時候是Previous進(jìn)程(后臺進(jìn)程),如果直接點(diǎn)擊返回鍵退出Activity,這個時候是緩存進(jìn)程;如果有多個Activity(注意這個時候只有app從后臺任務(wù)進(jìn)入前臺,或者點(diǎn)擊Home鍵退到后臺這兩種場景;因?yàn)閍pp在前臺運(yùn)行時都是前臺進(jìn)程),棧頂?shù)牡腁ctivity進(jìn)入onStart和onPause時是可見進(jìn)程,進(jìn)入onResume后是前臺進(jìn)程,點(diǎn)擊Home鍵退到后臺時是Previous進(jìn)程(大家常說的后臺進(jìn)程)。
2.如果App還存在緩存進(jìn)程,這個時候啟動App,應(yīng)用Application的onCreate方法會執(zhí)行嗎?
如果你點(diǎn)擊主界面MainActivity,點(diǎn)擊返回鍵后系統(tǒng)執(zhí)行MainActivity的onDestory方法,這個時候App進(jìn)程為緩存進(jìn)程,下次啟動App你會發(fā)現(xiàn)Application的onCreate方法并不會執(zhí)行,當(dāng)然MainActivity的生命周期都會正常執(zhí)行,這是因?yàn)閺木彺孢M(jìn)程啟動App,系統(tǒng)已經(jīng)緩存了很多信息,很多數(shù)據(jù)并不會被銷毀,onCreate中初始化的那些內(nèi)容還在,方便用戶下次快速啟動。利用這一特性,我們的App首次啟動速度一般為500600ms,退出App后存在緩存進(jìn)程的情況下,每次啟動的速度一般為200300ms,算是某種程度上提升了App的啟動時間。
需要注意的是,很多App在退出主界面的時候,會手動調(diào)用如下代碼去退出App
System.exit(0);一旦調(diào)用了如下代碼,就會徹底的退出并不會利用緩存進(jìn)程的優(yōu)勢,也失去了系統(tǒng)提供給我們的優(yōu)化了。
3.一個Activity A啟動另一個Activity B,為何會先走A的onPause方法,等到B執(zhí)行完onResume方法后,才會走A的onStop方法呢?
如果你看過前面兩個問題,這個問題你可能已經(jīng)有答案了。手機(jī)之所以進(jìn)行進(jìn)程的管理,用不同的優(yōu)先級對進(jìn)程進(jìn)行區(qū)分,首先肯定是為了保證用戶的流暢體驗(yàn),對于優(yōu)先級低且占用內(nèi)存高的進(jìn)程及時清理,保證前臺進(jìn)程有足夠的運(yùn)行空間。前面我們講到處于前臺的(獲取焦點(diǎn))界面只有一個,onPause時當(dāng)前進(jìn)程離開了前臺,當(dāng)然可能也要進(jìn)行一些數(shù)據(jù)的保存,所以肯定需要先執(zhí)行當(dāng)前界面的某個方法,然后再執(zhí)行B界面的onCreate,onStart,onResume是為了新的界面能夠被快速呈現(xiàn)(獲取焦點(diǎn)),然后再走舊界面A的onStop方法。
這里也需要注意,onPause方法中盡量不要去做耗時的操作,如果過于耗時,新界面會很久才能顯示出來,盡量放在onStop方法中去做。當(dāng)然onStop中也不能做過于耗時的操作中,前面我們也試過,點(diǎn)擊Home鍵會執(zhí)行onStop方法,此時App進(jìn)程處于后臺進(jìn)程,此時進(jìn)程的優(yōu)先級的很低的,當(dāng)內(nèi)存不足時,onStop中保存數(shù)據(jù)的操作可能就未完成,然后App進(jìn)程就被系統(tǒng)回收了。
關(guān)于狀態(tài)保存和恢復(fù),在API28之前,onSaveInstanceState執(zhí)行在onStop之前,但不限于在onPause之前或之后;在API28之后,onSaveInstanceState 執(zhí)行時機(jī)已確定為在?onStop?之后。而onRestoreInstanceState確定執(zhí)行在onStart之后。
4.為什么要這么設(shè)計(jì)Activity生命周期
假如你自己設(shè)計(jì)界面的生命周期:
界面啟動時候用需要設(shè)計(jì)一個方法
界面完全渲染完畢顯示需要一個方法
界面被部分遮蓋時/跳到其他界面/退到后臺需要一個方法
界面完全退出銷毀時需要一個方法
這么看來,我們好像只需要onCreate,onResume,onPause,onDestroy這四個方法,但是這只是一個很粗糙的界面創(chuàng)建~退出流程的回調(diào),但是你看看IOS的UIViewController的生命周期,看起來就是個精致的豬豬女孩

這樣一對比,連Android的生命周期顯得有點(diǎn)粗糙了,其實(shí)不全是,Activity還有一系列的onPostXXX方法以及onContentChanged等,但還是沒有IOS細(xì)膩。其實(shí)我覺得,這些生命周期的回調(diào)是基于一些場景設(shè)計(jì)的,從視圖的顯示到銷毀,考慮到不同的需求,我們需要不同程度級別的設(shè)計(jì),如果Android是一個非常簡單的系統(tǒng),也不會實(shí)現(xiàn)那么多的特殊需求,可能只需要前面我說的那四個方法就夠了,我感覺在生命周期的設(shè)計(jì)方面,IOS做的更好一些,對開發(fā)者更加友好。
也有一些人在回答生命周期為什么要這么設(shè)計(jì)時,可能會這么回答,因?yàn)榻缑嫘枰袀€創(chuàng)建/銷毀過程,onCreate/onDestroy肯定需要,onStart時進(jìn)程為可見進(jìn)程,提升進(jìn)程的優(yōu)先級,或者做一些特殊場景的操作,onResume在界面啟動完成或者恢復(fù)時需要,界面在被透明Activity的覆蓋時會執(zhí)行onPause(),需要有個方法在這個時候做狀態(tài)保存或特殊操作等,onStop時可以進(jìn)行狀態(tài)保存。這樣想問題完全是一種結(jié)果倒推的想法,經(jīng)不起仔細(xì)的推敲,一定不要從具體的方法去推場景,而是應(yīng)該從需求場景開始推導(dǎo),切記,這一切都是需求或可能的需求引起的!
第三方App中一些Activity的設(shè)置
今日頭條極速版-新聞界面打開的一些限制
NewDetailActivity就是我們看到的普通新聞界面,最多只能打開四個,超過四個就會將之前最早的NewDetailActivity關(guān)閉,原因很簡單,如果無限制的話Activity會越建越多,整個應(yīng)用越來越卡,影響用戶體驗(yàn)。
TaskRecord{8636d7b #6564 A=com.ss.android.article.lite U=0 StackId=282 sz=5}Run #4: ActivityRecord{8794744 u0 com.ss.android.article.lite/com.ss.android.article.base.feature.detail2.view.NewDetailActivity t6564}Run #3: ActivityRecord{8be5248 u0 com.ss.android.article.lite/com.ss.android.article.base.feature.detail2.view.NewDetailActivity t6564}Run #2: ActivityRecord{8bd6a09 u0 com.ss.android.article.lite/com.ss.android.article.base.feature.detail2.view.NewDetailActivity t6564}Run #1: ActivityRecord{87cc383 u0 com.ss.android.article.lite/com.ss.android.article.base.feature.detail2.view.NewDetailActivity t6564}Run #0: ActivityRecord{8bd6b44 u0 com.ss.android.article.lite/.activity.SplashActivity t6564}
而且還可以發(fā)現(xiàn)這個今日頭條極速版的主頁叫SplashActivity,真他么牛逼~,估計(jì)是原來有個SplashActivity界面和MainActivity界面,為了優(yōu)化快速啟動,給用戶一個秒開的感覺,移除原來的SplashActivity,直接把MainActivity改名為SplashActivity,然后做主題的替換。
然后我們看看它的啟動模式,啟動模式是standard。
? adb shell dumpsys activity | grep SplashActivity jackie@JackieLindeMacBook-ProbaseIntent=Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10200000 cmp=com.ss.android.article.lite/.activity.SplashActivity }baseIntent=Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10200000 cmp=com.sina.weibo/.SplashActivit }baseIntent=Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10200000 cmp=cmccwm.mobilemusic/.ui.base.SplashActivity }baseIntent=Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10200000 cmp=com.daimajia.gold/im.juejin.android.ui.SplashActivity }Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10200000 cmp=com.ss.android.article.lite/.activity.SplashActivity bnds=[544,149][796,458] }mActivityComponent=com.ss.android.article.lite/.activity.SplashActivitymIntent=Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10200000 cmp=com.ss.android.article.lite/.activity.SplashActivity bnds=[544,149][796,458] }#0 ActivityRecord{8e43505 u0 com.ss.android.article.lite/.activity.SplashActivity t6684} type=standard mode=fullscreen override-mode=undefined //啟動模式是standardintent={act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10200000 cmp=com.ss.android.article.lite/.activity.SplashActivity}mActivityComponent=com.ss.android.article.lite/.activity.SplashActivityActivities=[ActivityRecord{8e43505 u0 com.ss.android.article.lite/.activity.SplashActivity t6684}]Hist #0: ActivityRecord{8e43505 u0 com.ss.android.article.lite/.activity.SplashActivity t6684}Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10200000 cmp=com.ss.android.article.lite/.activity.SplashActivity bnds=[544,149][796,458] }Run #0: ActivityRecord{8e43505 u0 com.ss.android.article.lite/.activity.SplashActivity t6684}mResumedActivity: ActivityRecord{8e43505 u0 com.ss.android.article.lite/.activity.SplashActivity t6684}intent={act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10200000 cmp=com.sina.weibo/.SplashActivity}mActivityComponent=com.sina.weibo/.SplashActivityintent={act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10200000 cmp=cmccwm.mobilemusic/.ui.base.SplashActivity}mActivityComponent=cmccwm.mobilemusic/.ui.base.SplashActivityResumedActivity:ActivityRecord{8e43505 u0 com.ss.android.article.lite/.activity.SplashActivity t6684}ResumedActivity: ActivityRecord{8e43505 u0 com.ss.android.article.lite/.activity.SplashActivity t6684}
再來看今日頭條的首頁的啟動模式,它的首頁叫MainActivity,用的也是standard。
adb shell dumpsys activity | grep MainActivity jackie@JackieLindeMacBook-Intent { flg=0x24008000 cmp=com.ss.android.article.news/.activity.MainActivity (has extras) }mActivityComponent=com.ss.android.article.news/.activity.MainActivitymIntent=Intent { flg=0x24008000 cmp=com.ss.android.article.news/.activity.MainActivity (has extras) }#0 ActivityRecord{8fadec6 u0 com.ss.android.article.news/.activity.MainActivity t6685} type=standard mode=fullscreen override-mode=undefined //standard啟動模式#0 ActivityRecord{9130583 u0 cmccwm.mobilemusic/.ui.base.MainActivity t6681} type=standard mode=fullscreen override-mode=undefinedActivities=[ActivityRecord{8fadec6 u0 com.ss.android.article.news/.activity.MainActivity t6685}]Hist #0: ActivityRecord{8fadec6 u0 com.ss.android.article.news/.activity.MainActivity t6685}Intent { flg=0x24008000 cmp=com.ss.android.article.news/.activity.MainActivity (has extras) }Run #0: ActivityRecord{8fadec6 u0 com.ss.android.article.news/.activity.MainActivity t6685}mResumedActivity: ActivityRecord{8fadec6 u0 com.ss.android.article.news/.activity.MainActivity t6685}Activities=[ActivityRecord{9130583 u0 cmccwm.mobilemusic/.ui.base.MainActivity t6681}, ActivityRecord{9822b05 u0 cmccwm.mobilemusic/com.migu.music.ui.local.LocalSongsActivity t6681}]Hist #0: ActivityRecord{9130583 u0 cmccwm.mobilemusic/.ui.base.MainActivity t6681}Intent { flg=0x10000000 cmp=cmccwm.mobilemusic/.ui.base.MainActivity (has extras) }Run #0: ActivityRecord{9130583 u0 cmccwm.mobilemusic/.ui.base.MainActivity t6681}ResumedActivity:ActivityRecord{8fadec6 u0 com.ss.android.article.news/.activity.MainActivity t6685}ResumedActivity: ActivityRecord{8fadec6 u0 com.ss.android.article.news/.activity.MainActivity t6685}
我在今日頭條和今日頭條極速版的app中尋找從某個界面點(diǎn)擊某個按鈕返回到主頁的場景,沒有發(fā)現(xiàn)有這樣的場景,或者說很少(可能是我沒有發(fā)現(xiàn)),前面也說過使用standard標(biāo)準(zhǔn)模式只要是每次被啟動都會創(chuàng)建一個新的實(shí)例,如果其他界面回到主頁的場景多的話,我覺得可能會用singleTop(當(dāng)要實(shí)現(xiàn)類似SingleTask的效果時可以配合flag實(shí)現(xiàn))。場景極少或者沒有是它使用standard的原因吧。
到此,我們對Activity的介紹和分析已經(jīng)完畢了,喜歡的點(diǎn)個贊和關(guān)注吧。
