精品国产人成在线_亚洲高清无码在线观看_国产在线视频国产永久2021_国产AV综合第一页一个的一区免费影院黑人_最近中文字幕MV高清在线视频

0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

帶大家探究Activity啟動(dòng)前的一項(xiàng)重要的工作—棧校驗(yàn)

OSC開源社區(qū) ? 來(lái)源:vivo互聯(lián)網(wǎng)技術(shù) ? 2023-04-19 09:36 ? 次閱讀

本文從一例業(yè)務(wù)中遇到的問(wèn)題出發(fā),以FLAG_ACTIVITY_NEW_TASK這一flag作為切入點(diǎn),帶大家探究Activity啟動(dòng)前的一項(xiàng)重要的工作——棧校驗(yàn)。

文中列舉一系列業(yè)務(wù)中可能遇到的異常狀況,詳細(xì)描述了使用FLAG_ACTIVITY_NEW_TASK時(shí)可能遇到的“坑”,并從源碼中探究其根源。只有合理使用flag、launchMode,才能避免因?yàn)闂C(jī)制的特殊性,導(dǎo)致一系列與預(yù)期不符的啟動(dòng)問(wèn)題。

一、問(wèn)題及背景

應(yīng)用間相互聯(lián)動(dòng)、相互跳轉(zhuǎn),是實(shí)現(xiàn)系統(tǒng)整體性、體驗(yàn)一致性的重要手段,也是最簡(jiǎn)單的一種方法。

當(dāng)我們用最常用的方法去startActivity時(shí),竟也會(huì)遇到失敗的情況。在真實(shí)業(yè)務(wù)中,就遇到了這樣一例異常:用戶點(diǎn)擊某個(gè)按鈕時(shí),想要“簡(jiǎn)簡(jiǎn)單單”跳轉(zhuǎn)另一個(gè)應(yīng)用,卻沒有任何反應(yīng)。

經(jīng)驗(yàn)豐富的你,腦海中是否涌現(xiàn)出了各種猜想:是不是目標(biāo)Activity甚至目標(biāo)App不存在?是不是目標(biāo)Activty沒有對(duì)外開放?是不是有權(quán)限的限制或者跳轉(zhuǎn)的action/uri錯(cuò)了……

真實(shí)的原因被flag、launchMode、Intent等特性層層藏匿,可能超出你此時(shí)的思考。

本文將從源碼出發(fā),探究前因后果,展開講講在startActivity()真正準(zhǔn)備啟動(dòng)一個(gè)Activity前,需要經(jīng)過(guò)哪些“磨難”,怎樣有據(jù)可依地解決由棧問(wèn)題導(dǎo)致的啟動(dòng)異常。

1.1 業(yè)務(wù)中遇到的問(wèn)題

業(yè)務(wù)中的場(chǎng)景是這樣的,存在A、B、C三個(gè)應(yīng)用。

(1)從應(yīng)用A-Activity1跳轉(zhuǎn)至應(yīng)用B-Activity2;

(2)應(yīng)用B-Activity2繼續(xù)跳轉(zhuǎn)到應(yīng)用C-Activity3;

(3)C內(nèi)某個(gè)按鈕,會(huì)再次跳轉(zhuǎn)B-Activity2,但點(diǎn)擊后沒有任何反應(yīng)。如果不經(jīng)過(guò)前面A到B的跳轉(zhuǎn),C直接跳到B是可以的。

8596f8dc-ddf5-11ed-bfe3-dac502259ad0.gif

1.2 問(wèn)題代碼

3個(gè)Activity的Androidmanifest配置如下,均可通過(guò)各自的action拉起,launchMode均為標(biāo)準(zhǔn)模式。

  
      
            
                
                
            
        
 
 
        
            
                
                
            
        
 
 
        
            
                
                
            
        

A-1到B-2的代碼,指定flag為

FLAG_ACTIVITY_NEW_TASK

private void jumpTo_B_Activity2_ByAction_NewTask() {
    Intent intent = new Intent();
    intent.setAction("com.zkp.task.ACTION_TO_B_PAGE2");
    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    startActivity(intent);
}

B-2到C-3的代碼,未指定flag

private void jumpTo_C_Activity3_ByAction_NoTask() {
    Intent intent = new Intent();
    intent.setAction("com.zkp.task.ACTION_TO_C_PAGE3");
    startActivity(intent);
}

C-3到B-2的代碼,與A-1到B-2的完全一致,指定flag為 FLAG_ACTIVITY_NEW_TASK

private void jumpTo_B_Activity2_ByAction_NewTask() {
    Intent intent = new Intent();
    intent.setAction("com.zkp.task.ACTION_TO_B_PAGE2");
    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    startActivity(intent);
}

1.3 代碼初步分析

仔細(xì)查看問(wèn)題代碼,在實(shí)現(xiàn)上非常簡(jiǎn)單,有兩個(gè)特征:

(1)如果直接通過(guò)C-3跳B-2,沒有任何問(wèn)題,但A-1已經(jīng)跳過(guò)B-2后,C-3就失敗了。

(2)在A-1和C-3跳到B-2時(shí),都設(shè)置了flag為FLAG_ACTIVITY_NEW_TASK。

依據(jù)經(jīng)驗(yàn),我們推測(cè)與棧有關(guān),嘗試將跳轉(zhuǎn)前棧的狀態(tài)打印出來(lái),如下圖。

85bf2226-ddf5-11ed-bfe3-dac502259ad0.png

由于A-1跳到B-2時(shí)設(shè)置了FLAG_ACTIVITY_NEW_TASK,B-2跳到C-3時(shí)未設(shè)置,所以1在獨(dú)立棧中,2、3在另一個(gè)棧中。示意如下圖。

85ccfef0-ddf5-11ed-bfe3-dac502259ad0.png

C-3跳轉(zhuǎn)B-2一般有3種可能的預(yù)期,如下圖:預(yù)想1,新建一個(gè)Task,在新Task中啟動(dòng)一個(gè)B-2;預(yù)想2,復(fù)用已經(jīng)存在的B-2;預(yù)想3,在已有Task中新建一個(gè)實(shí)例B-2。

85d358ae-ddf5-11ed-bfe3-dac502259ad0.png

但實(shí)際上3種預(yù)期都沒有實(shí)現(xiàn),所有Activity的任何聲明周期都沒有變化,界面始終停留在C-3。

看一下FLAG_ACTIVITY_NEW_TASK的官方注釋和代碼注釋,如下圖:

85d9cfcc-ddf5-11ed-bfe3-dac502259ad0.png

85e44588-ddf5-11ed-bfe3-dac502259ad0.png

重點(diǎn)關(guān)注這一段:

When using this flag, if a task is already running for the activity you are now starting, then a new activity will not be started; instead, the current task will simply be brought to the front of the screen with the state it was last in.

使用此flag時(shí),如果你正在啟動(dòng)的Activity已經(jīng)在一個(gè)Task中運(yùn)行,那么一個(gè)新Activity不會(huì)被啟動(dòng);相反,當(dāng)前Task將簡(jiǎn)單地顯示在界面的前面,并顯示其最后的狀態(tài)。

——顯然,官方文檔與代碼注釋的表述與我們的異常現(xiàn)象是一致的,目標(biāo)Activity2已經(jīng)在Task中存在,則不會(huì)被啟動(dòng);Task直接顯示在前面,并展示最后的狀態(tài)。由于目標(biāo)Activty3就是來(lái)源Activity3,所以頁(yè)面沒有任何變化。

看起來(lái)官方還是很靠譜的,但實(shí)際效果真的能一直與官方描述一致嗎?我們通過(guò)幾個(gè)場(chǎng)景來(lái)看一下。

二、場(chǎng)景拓展與驗(yàn)證

2.1 場(chǎng)景拓展

在筆者依據(jù)官方描述進(jìn)行調(diào)整、復(fù)現(xiàn)的過(guò)程中,發(fā)現(xiàn)了幾個(gè)比較有意思的場(chǎng)景。

PS:上面業(yè)務(wù)的案例中,B-2和C-3在不同應(yīng)用內(nèi),又在相同的Task內(nèi),但實(shí)際上是否是同一個(gè)應(yīng)用,對(duì)結(jié)果的影響并不大。為了避免不同應(yīng)用和不同Task造成閱讀混亂,同一個(gè)棧的跳轉(zhuǎn),我們都在本應(yīng)用內(nèi)進(jìn)行,故業(yè)務(wù)中的場(chǎng)景等價(jià)于下面的【場(chǎng)景0】

【場(chǎng)景0】把業(yè)務(wù)中B-2到C-3的應(yīng)用間跳轉(zhuǎn)改為B-2到B-3的應(yīng)用內(nèi)跳轉(zhuǎn)

// B-2跳轉(zhuǎn)B-3
public static void jumpTo_B_3_ByAction_Null(Context context) {
    Intent intent = new Intent();
    intent.setAction("com.zkp.task.ACTION_TO_B_PAGE3");
    context.startActivity(intent);
}

如下圖,A-1設(shè)置NEW_TASK跳轉(zhuǎn)B-2,再跳轉(zhuǎn)B-3,最終設(shè)置NEW_TASK想跳轉(zhuǎn)B-2。雖然跳C-3改為了跳B-3,但與之前問(wèn)題的表現(xiàn)一致,沒有反應(yīng),停留在B-3。

85ee18c4-ddf5-11ed-bfe3-dac502259ad0.png

有的讀者會(huì)指出這樣的問(wèn)題:如果同一個(gè)應(yīng)用內(nèi)使用NEW_TASK跳轉(zhuǎn),而不指定目標(biāo)的taskAffinity屬性,實(shí)際是無(wú)法在新Task中啟動(dòng)的。請(qǐng)大家忽略該問(wèn)題,可以認(rèn)為筆者的操作是已經(jīng)加了taskAffinity的,這對(duì)最終結(jié)果并沒有影響。

【場(chǎng)景1】如果目標(biāo)Task和來(lái)源Task不是同一個(gè),情況是否會(huì)如官方文檔所說(shuō)復(fù)用已有的Task并展示最近狀態(tài)?我們改為B-3啟動(dòng)一個(gè)新Task的新Activity C-4,再通過(guò)C-4跳回B-2

// B-3跳轉(zhuǎn)C-4
public static void jumpTo_C_4_ByAction_New(Context context) {
    Intent intent = new Intent("com.zkp.task.ACTION_TO_C_PAGE4");
    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    context.startActivity(intent);
}
// C-4跳轉(zhuǎn)B-2
public static void jumpTo_B_2_ByAction_New(Context context) {
    Intent intent = new Intent();
    intent.setAction("com.zkp.task.ACTION_TO_B_PAGE2");
    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    context.startActivity(intent);
}

如下圖,A-1設(shè)置NEW_TASK跳轉(zhuǎn)B-2,再跳轉(zhuǎn)B-3,再設(shè)置NEW_TASK跳轉(zhuǎn)C-4,最終設(shè)置NEW_TASK想跳轉(zhuǎn)B-2。

85f5b91c-ddf5-11ed-bfe3-dac502259ad0.png

預(yù)想的結(jié)果是:不會(huì)跳到B-2,而是跳到它所在Task的頂層B-3。

實(shí)際的結(jié)果是:與預(yù)期一致,確實(shí)是跳到了B-3。

【場(chǎng)景2】把場(chǎng)景1稍做修改:C-4到B-2時(shí),我們不通過(guò)action來(lái)跳,改為通過(guò)setClassName跳轉(zhuǎn)

// C-4跳轉(zhuǎn)B-2
public static void jumpTo_B_2_ByPath_New(Context context) {
    Intent intent = new Intent();
    intent.setClassName("com.zkp.b", "com.zkp.b.Activity2"); // 直接設(shè)置classname,不通過(guò)action
    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    context.startActivity(intent);
}

如下圖,A-1設(shè)置NEW_TASK跳轉(zhuǎn)B-2,再跳轉(zhuǎn)B-3,再設(shè)置NEW_TASK跳轉(zhuǎn)C-4,最終設(shè)置NEW_TASK想跳轉(zhuǎn)B-2。

85fe5db0-ddf5-11ed-bfe3-dac502259ad0.png

預(yù)想的結(jié)果是:與場(chǎng)景0一致,會(huì)跳到B-2所在Task的已有頂層B-3。

實(shí)際的結(jié)果是:在已有的Task2中,產(chǎn)生了一個(gè)新的B-2實(shí)例。

僅僅是改變了一下重新跳轉(zhuǎn)B-2的方式,效果就完全不一樣了!這與官方文檔中提到該flag與"singleTask" launchMode值產(chǎn)生的行為并不一致!

【場(chǎng)景3】把場(chǎng)景1再做修改:這次C-4不跳棧底的B-2,改為跳轉(zhuǎn)B-3,且還是通過(guò)action方式。

// C-4跳轉(zhuǎn)B-3
public static void jumpTo_B_3_ByAction_New(Context context) {
    Intent intent = new Intent();
    intent.setAction("com.zkp.task.ACTION_TO_B_PAGE3");
    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    context.startActivity(intent);
}

如下圖,A-1設(shè)置NEW_TASK跳轉(zhuǎn)B-2,再跳轉(zhuǎn)B-3,再設(shè)置NEW_TASK跳轉(zhuǎn)C-4,最終設(shè)置NEW_TASK想跳轉(zhuǎn)B-3。

860403b4-ddf5-11ed-bfe3-dac502259ad0.png

預(yù)想的結(jié)果是:與場(chǎng)景0一致,會(huì)跳到B-2所在Task的頂層B-3。

實(shí)際的結(jié)果是:在已有的Task2中,產(chǎn)生了一個(gè)新的B-3實(shí)例。

不是說(shuō)好的,Activity已經(jīng)存在時(shí),展示其所在Task的最新狀態(tài)嗎?明明Task2中已經(jīng)有了B-3,并沒有直接展示它,而是生成了新的B-3實(shí)例。

【場(chǎng)景4】既然Activity沒有被復(fù)用,那Task一定會(huì)被復(fù)用嗎?把場(chǎng)景3稍做修改,直接給B-3指定一個(gè)單獨(dú)的affinity。

 
    
        
        
    

如下圖,A-1設(shè)置NEW_TASK跳轉(zhuǎn)B-2,再跳轉(zhuǎn)B-3,再設(shè)置NEW_TASK跳轉(zhuǎn)C-4,最終設(shè)置NEW_TASK想跳轉(zhuǎn)B-3。

8611a4f6-ddf5-11ed-bfe3-dac502259ad0.png

——這次,連Task也不會(huì)再被復(fù)用了……Activity3在一個(gè)新的棧中被實(shí)例化了。

再回看官方的注釋,就會(huì)顯得非常不準(zhǔn)確,甚至?xí)?a target="_blank">開發(fā)者對(duì)該部分的認(rèn)知產(chǎn)生嚴(yán)重錯(cuò)誤!稍微改變過(guò)程中的某個(gè)毫無(wú)關(guān)聯(lián)的屬性(如跳轉(zhuǎn)目標(biāo)、跳轉(zhuǎn)方式……),就會(huì)產(chǎn)生很大差異。

在看flag相關(guān)注釋時(shí),我們要樹立一個(gè)意識(shí):Task和Activity跳轉(zhuǎn)的實(shí)際效果,是launchMode、taskAffinity、跳轉(zhuǎn)方式、Activity在Task中的層級(jí)等屬性綜合作用的結(jié)果,不要相信“一面之詞”。

回到問(wèn)題本身,究竟是哪些原因造就了上面的不同效果呢?只有源碼最值得信賴了。

三、場(chǎng)景分析與源碼探索

本文以Android 12.0源碼為基礎(chǔ),進(jìn)行探究。上述場(chǎng)景在不同Android版本上的表現(xiàn)是一致的。

3.1 源碼調(diào)試注意事項(xiàng)

源碼的調(diào)試方法,許多文章已經(jīng)有了詳細(xì)的教學(xué),本文不再贅述。此處只簡(jiǎn)單總結(jié)其中需要注意的事項(xiàng)

下載模擬器時(shí),不要使用Google Play版本,該版本類似user版本,無(wú)法選擇system_process進(jìn)程進(jìn)行斷點(diǎn)。

即使是Google官方模擬器和源碼,在斷點(diǎn)時(shí),也會(huì)有行數(shù)嚴(yán)重不對(duì)應(yīng)的情況(比如:模擬器實(shí)際會(huì)運(yùn)行到方法A,但在源碼中打斷點(diǎn)時(shí),實(shí)際不能定位到方法A的對(duì)應(yīng)行數(shù)),該問(wèn)題并沒有很好的處理方法,只能盡量規(guī)避,如使模擬器版本與源碼版本保持一致、多打一些斷點(diǎn)增加關(guān)鍵行數(shù)被定位到的幾率。

3.2 初步斷點(diǎn),明確啟動(dòng)結(jié)果

以【場(chǎng)景0】為例,我們初步確認(rèn)一下,為什么B-3跳轉(zhuǎn)B-2會(huì)無(wú)反應(yīng),系統(tǒng)是否告知了原因。

3.2.1 明確啟動(dòng)結(jié)果及其來(lái)源

在Android源碼的斷點(diǎn)調(diào)試中,常見的有兩類進(jìn)程:應(yīng)用進(jìn)程和system_process進(jìn)程。

在應(yīng)用進(jìn)程中,我們能獲取到應(yīng)用啟動(dòng)結(jié)果的狀態(tài)碼result,這個(gè)result用來(lái)告訴我們啟動(dòng)是否成功。涉及堆棧如下圖(標(biāo)記1)所示:

Activity類::startActivity()

→ startActivityForResult()

Instrumentation類::execStartActivity(),

返回值result則是ATMS

(ActivityTaskManagerService)執(zhí)行的結(jié)果。

8618e9aa-ddf5-11ed-bfe3-dac502259ad0.png

如上圖(標(biāo)記2)標(biāo)注,ATMS類::startActivity()方法,返回了result=3。

在system_process進(jìn)程中,我們看一下這個(gè)result=3是怎樣被賦值的。略去詳細(xì)斷點(diǎn)步驟,實(shí)際堆棧如下圖(標(biāo)注1)所示:

ATMS類::startActivity() →startActivityAsUser()

ActivityStarter類::execute()

→executeRequest()

→startActivityUnchecked()

→ startActivityInner()

→ recycleTask(),在recycleTask()中返回了結(jié)果。

862c2d1c-ddf5-11ed-bfe3-dac502259ad0.png

如上圖(標(biāo)注2)所示,result在mMovedToFront=false時(shí)被賦值,即result=START_DELIVERED_TO_TOP=3,而START_SUCCESS=0才代表創(chuàng)建成功。

看一下源碼中對(duì)START_DELIVERED_TO_TOP的說(shuō)明,如下圖:

863cd3d8-ddf5-11ed-bfe3-dac502259ad0.png

Result for IActivityManaqer.startActivity: activity wasn't really started, but the given Intent was given to the existing top activity.

(IActivityManaqer.startActivityActivity的結(jié)果:Activity并未真正啟動(dòng),但給定的Intent已提供給現(xiàn)有的頂層Activity。)

“Activity并未真正啟動(dòng)”——是的,因?yàn)榭梢詮?fù)用

“給定的Intent已提供給現(xiàn)有的頂層Activity”——實(shí)際沒有,頂層Activity3并沒有收到任何回調(diào),onNewIntent()未執(zhí)行,甚至嘗試通過(guò)Intent::putExtra()傳入新的參數(shù),Activity3也沒有收到。官方文檔又帶給了我們一個(gè)疑問(wèn)點(diǎn)?我們把這個(gè)問(wèn)題記錄下來(lái),在后面分析。

滿足什么條件,才會(huì)造成

START_DELIVERED_TO_TOP的結(jié)果呢?筆者的思路是,通過(guò)與正常啟動(dòng)流程對(duì)比,找出差異點(diǎn)。

3.3 過(guò)程斷點(diǎn),探索啟動(dòng)流程

一般來(lái)說(shuō),在定位問(wèn)題時(shí),我們習(xí)慣通過(guò)結(jié)果反推原因,但反推的過(guò)程只能關(guān)注到與問(wèn)題強(qiáng)關(guān)聯(lián)的代碼分支,并不能能使我們很好地了解全貌。

所以,本節(jié)內(nèi)容我們通過(guò)順序閱讀的方法,正向介紹startActivity過(guò)程中與上述【場(chǎng)景01234】強(qiáng)相關(guān)的邏輯。再次簡(jiǎn)述一下:

【場(chǎng)景0】同一個(gè)Task內(nèi),從頂部B-3跳轉(zhuǎn)B-2——停留在B-3

【場(chǎng)景1】從另一個(gè)Task內(nèi)的C-4,跳轉(zhuǎn)B-2——跳轉(zhuǎn)到B-3

【場(chǎng)景2】把場(chǎng)景1中,C-4跳轉(zhuǎn)B-2的方式改為setClassName()——?jiǎng)?chuàng)建新B-2實(shí)例

【場(chǎng)景3】把場(chǎng)景1中,C-4跳轉(zhuǎn)B-2改為跳轉(zhuǎn)B-3——?jiǎng)?chuàng)建新B-3實(shí)例

【場(chǎng)景4】給場(chǎng)景3中的B-3,指定taskAffinity——?jiǎng)?chuàng)建新Task和新B-3實(shí)例

3.3.1 流程源碼概覽

源碼中,整個(gè)啟動(dòng)流程很長(zhǎng),涉及的方法和邏輯也很多,為了便于大家理清方法調(diào)用順序,方便后續(xù)內(nèi)容的閱讀,筆者將本文涉及到的關(guān)鍵類及方法調(diào)用關(guān)系整理如下。

后續(xù)閱讀中如果不清楚調(diào)用關(guān)系,可以返回這里查看:

// ActivityStarter.java
 
    ActivityStarter::execute() {
        executeRequest(intent) {
            startActivityUnchecked() {
                startActivityInner();
        }
    }
    ActivityStarter::startActivityInner() {
        setInitialState();
        computeLaunchingTaskFlags();
        Task targetTask = getReusableTask(){
            findTask();
        }
        ActivityRecord targetTaskTop = targetTask.getTopNonFinishingActivity();
        if (targetTaskTop != null) {
            startResult = recycleTask() {
                setTargetRootTaskIfNeeded();
                complyActivityFlags();
                if (mAddingToTask) {
                    return START_SUCCESS; //【場(chǎng)景2】【場(chǎng)景3】從recycleTask()返回
                }
                resumeFocusedTasksTopActivities()
                return mMovedToFront ? START_TASK_TO_FRONT : START_DELIVERED_TO_TOP;//【場(chǎng)景1】【場(chǎng)景0】從recycleTask()返回
            }
        } else {
            mAddingToTask = true;
        }
        if (startResult != START_SUCCESS) {
            return startResult;//【場(chǎng)景1】【場(chǎng)景0】從startActivityInner()返回
        }
        deliverToCurrentTopIfNeeded();
        resumeFocusedTasksTopActivities();
        return startResult;
    }

3.3.2 關(guān)鍵流程分析

(1)初始化

startActivityInner()是最主要的方法,如下列幾張圖所示,該方法會(huì)率先調(diào)用setInitialState(),初始化各類全局變量,并調(diào)用reset(),重置ActivityStarter中各種狀態(tài)。

在此過(guò)程中,我們記下兩個(gè)關(guān)鍵變量mMovedToFront和mAddingToTask,它們均在此被重置為false。

其中,mMovedToFront代表當(dāng)Task可復(fù)用時(shí),是否需要將目標(biāo)Task移動(dòng)到前臺(tái);mAddingToTask代表是否要將Activity加入到Task中。

864253e4-ddf5-11ed-bfe3-dac502259ad0.png

864919c2-ddf5-11ed-bfe3-dac502259ad0.png

8653659e-ddf5-11ed-bfe3-dac502259ad0.png

(2)計(jì)算確認(rèn)啟動(dòng)時(shí)的flag

該步驟會(huì)通過(guò)computeLaunchingTaskFlags()方法,根據(jù)launchMode、來(lái)源Activity的屬性等進(jìn)行初步計(jì)算,確認(rèn)LaunchFlags。

此處重點(diǎn)處理來(lái)源Activity為空的各類場(chǎng)景,與我們上文中的幾種場(chǎng)景無(wú)關(guān),故不再展開講解。

(3)獲取可以復(fù)用的Task

該步驟通過(guò)調(diào)用getReusableTask()實(shí)現(xiàn),用來(lái)查找有沒有可以復(fù)用的Task。

先說(shuō)結(jié)論:場(chǎng)景0123中,都能獲取到可以復(fù)用的Task,而場(chǎng)景4中,未獲取到可復(fù)用的Task。

為什么場(chǎng)景4不可以復(fù)用?我們看一下getReusableTask()的關(guān)鍵實(shí)現(xiàn)。

8659d1b8-ddf5-11ed-bfe3-dac502259ad0.png

上圖(標(biāo)注1)中,putIntoExistingTask代表是否能放入已經(jīng)存在的Task。當(dāng)flag含有NEW_TASK且不含MULTIPLE_TASK時(shí),或指定了singleInstance或singleTask的launchMode等條件,且沒有指定Task或要求返回結(jié)果 時(shí),場(chǎng)景01234均滿足了條件。

然后,上圖(標(biāo)注2)通過(guò)findTask()查找可以復(fù)用的Task,并將過(guò)程中找到的棧頂Activity賦值給intentActivity。最終,上圖(標(biāo)注3)將intentActivity對(duì)應(yīng)的Task作為結(jié)果。

findTask()是怎樣查找哪個(gè)Task可以復(fù)用呢?

866f43a4-ddf5-11ed-bfe3-dac502259ad0.png

主要是確認(rèn)兩種結(jié)果mIdealRecord——“理想的ActivityRecord” 和 mCandidateRecord——"候選的ActivityRecord",作為intentActivity,并取intentActivity對(duì)應(yīng)的Task作為復(fù)用Task。

什么ActivityRecord才是理想或候選的ActivityRecord呢?

在mTmpFindTaskResult.process()中確認(rèn)。

868a0702-ddf5-11ed-bfe3-dac502259ad0.png

程序會(huì)將當(dāng)前系統(tǒng)中所有的Task進(jìn)行遍歷,在每個(gè)Task中,進(jìn)行如上圖所示的工作——將Task的底部Activity realActivity與目標(biāo)Activity cls進(jìn)行對(duì)比。

場(chǎng)景012中,我們想跳轉(zhuǎn)Activity2,即cls是Activity2,與Task底部的realActivity2相同,則將該Task頂部的Activity3 r作為“理想的Activity”;

場(chǎng)景3中,我們想跳轉(zhuǎn)Activity3,即cls是Activity3,與Task底部的realActivity2不同,則進(jìn)一步判斷task底部Activity2與目標(biāo)Activity3的棧親和行,具有相同親和性,則將Task的頂部Activity3作為“候選Activity”;

場(chǎng)景4中,所有條件都不滿足,最終沒能找到可復(fù)用的Task。在執(zhí)行完getReusableTask()后將mAddingToTask賦值為true

由此,我們就能解釋【場(chǎng)景4】中,新建了Task的現(xiàn)象。

(4)確定是否需要將目標(biāo)Task移動(dòng)到前臺(tái)

如果存在可復(fù)用的Task,場(chǎng)景0123會(huì)執(zhí)行recycleTask(),該方法中會(huì)相繼進(jìn)行幾個(gè)操作:setTargetRootTaskIfNeeded()、

complyActivityFlags()。

首先,程序會(huì)執(zhí)行

setTargetRootTaskIfNeeded(),用來(lái)確定是否需要將目標(biāo)Task移動(dòng)到前臺(tái),使用mMovedToFront作為標(biāo)識(shí)。

86b002a4-ddf5-11ed-bfe3-dac502259ad0.png

86b80166-ddf5-11ed-bfe3-dac502259ad0.png

在【場(chǎng)景123】中,來(lái)源Task和目標(biāo)Task是不同的,differentTopTask為true,再經(jīng)過(guò)一系列Task屬性對(duì)比,能夠得出mMovedToFront為true;

而場(chǎng)景0中,來(lái)源Task和目標(biāo)Task相同,differentTopTask為false,mMovedToFront保持初始的false。

由此,我們就能解釋【場(chǎng)景0】中,Task不會(huì)發(fā)生切換的現(xiàn)象。

(5)通過(guò)對(duì)比f(wàn)lag、Intent、Component等確認(rèn)是否要將Activity加入到Task中

還是在【場(chǎng)景0123】中,recycleTask()會(huì)繼續(xù)執(zhí)行complyActivityFlags(),用來(lái)確認(rèn)是否要將Activity加入到Task中,使用mAddingToTask作為標(biāo)識(shí)。

該方法會(huì)對(duì)FLAG_ACTIVITY_NEW_TASK、

FLAG_ACTIVITY_CLEAR_TASK、

FLAG_ACTIVITY_CLEAR_TOP等諸多flag、Intent信息進(jìn)行一系列判斷。

86d388e6-ddf5-11ed-bfe3-dac502259ad0.png

上圖(標(biāo)注1)中,會(huì)先判斷后續(xù)是否需要重置Task,resetTask,判斷條件則是FLAG_ACTIVITY_RESET_TASK_IF_NEEDED,顯然,場(chǎng)景0123的resetTask都為false。繼續(xù)執(zhí)行。

接著,會(huì)有多種條件判斷按順序執(zhí)行。

在【場(chǎng)景3】中,目標(biāo)Component(mActivityComponent)是B-3,目標(biāo)Task的realActivity則是B-2,兩者不相同,進(jìn)入了resetTask相關(guān)的判斷(標(biāo)注2)。

之前resetTask已經(jīng)是false,故【場(chǎng)景3】的mAddingToTask脫離原始值,被置為true。

在【場(chǎng)景012】中,相對(duì)比的兩個(gè)Activity都是B-2(標(biāo)注3),可以進(jìn)入下一級(jí)判斷——isSameIntentFilter()。

86ecde0e-ddf5-11ed-bfe3-dac502259ad0.png

86ecde0e-ddf5-11ed-bfe3-dac502259ad0.png

86fb41a6-ddf5-11ed-bfe3-dac502259ad0.png

這一步判斷的內(nèi)容就很明顯了,目標(biāo)Activity2的已有Intent 與 新的Intent做對(duì)比。很顯然,場(chǎng)景2中由于改為了setClassName跳轉(zhuǎn),Intent自然不一樣了。

故【場(chǎng)景2】的mAddingToTask脫離原始值,被置為true。

總結(jié)看一下:

【場(chǎng)景123】的mMovedToFront最先被置為true,而【場(chǎng)景0】經(jīng)歷重重考驗(yàn),保持初始值為false。

——這意味著當(dāng)有可復(fù)用Task時(shí),【場(chǎng)景0】不需要把Task切換到前列;【場(chǎng)景123】需要切換到目標(biāo)Task。

【場(chǎng)景234】的mAddingToTask分別在不同階段被置為true,而【場(chǎng)景01】,始終保持初始值false。

——這意味著,【場(chǎng)景234】需要將Activity加入到Task中,而【場(chǎng)景01】不再需要。

(6)實(shí)際啟動(dòng)Activity或直接返回結(jié)果

被啟動(dòng)的各個(gè)Activity會(huì)通過(guò)resumeFocusedTasksTopActivities()等一系列操作,開始真正的啟動(dòng)與生命周期的調(diào)用。

我們關(guān)于上述各個(gè)場(chǎng)景的探索已經(jīng)得到答案,后續(xù)流程便不再關(guān)注。

四、問(wèn)題修復(fù)及遺留問(wèn)題解答

4.1 問(wèn)題修復(fù)

既然前面總結(jié)了這么多必要條件,我們只需要破壞其中的某些條件,就可以修復(fù)業(yè)務(wù)中遇到的問(wèn)題了,簡(jiǎn)單列舉幾個(gè)的方案。

方案一:修改flag。B-3跳轉(zhuǎn)B-2時(shí),增加FLAG_ACTIVITY_CLEAR_TASK或FLAG_ACTIVITY_CLEAR_TOP,或者直接不設(shè)置flag。經(jīng)驗(yàn)證可行。

方案二:修改intent屬性,即【場(chǎng)景2】。A-1通過(guò)action方式隱式跳轉(zhuǎn)B-2,則B-3可以通過(guò)setClassName方式,或修改action內(nèi)屬性的方式跳轉(zhuǎn)B-2。經(jīng)驗(yàn)證可行。

方案三:提前移除B-2。B-2跳轉(zhuǎn)B-3時(shí),finish掉B-2。需要注意的是,finish()要在startActivity()之前執(zhí)行,以避免遺留的ActivityRecord和Intent信息對(duì)后續(xù)跳轉(zhuǎn)的影響。尤其是當(dāng)你把B-2作為自己應(yīng)用的deeplink分發(fā)Activity時(shí),更值得警惕。

4.2 遺留問(wèn)題

還記得我們?cè)谖恼麻_端的某個(gè)疑惑嗎,為什么沒有回調(diào)onNewIntent()?

onNewIntent() 會(huì)通過(guò)deliverNewIntent()觸發(fā),而deliverNewIntent()僅通過(guò)以下兩個(gè)方法調(diào)用。

870a4ba6-ddf5-11ed-bfe3-dac502259ad0.png

complyActivityFlags()就是上文3.3.1.5中我們著重探討的方法,可以發(fā)現(xiàn)complyActivityFlags()中所有可能調(diào)用deliverNewIntent()的條件均被完美避開了。

而deliverToCurrentTopIfNeeded()方法則如下圖所示。

8713bd26-ddf5-11ed-bfe3-dac502259ad0.png

mLaunchFlags和mLaunchMode,無(wú)法滿足條件,導(dǎo)致dontStart為false,無(wú)緣

deliverNewIntent()。

至此,onNewIntent()的問(wèn)題得到解答。

五、結(jié)語(yǔ)

通過(guò)一系列場(chǎng)景假設(shè),我們發(fā)現(xiàn)了許多出乎意料的現(xiàn)象:

文檔提到FLAG_ACTIVITY_NEW_TASK等價(jià)于singleTask,與事實(shí)并不完全如此,只有與其他flag搭配才能達(dá)到相似的效果。這一flag的注釋非常片面,甚至?xí)l(fā)誤解,單一因素?zé)o法決定整體表現(xiàn)。

官方文檔提到

START_DELIVERED_TO_TOP會(huì)將新的Intent傳遞給頂層Activity,但事實(shí)上,并不是每一種START_DELIVERED_TO_TOP都會(huì)把新的Intent重新分發(fā)。

同一個(gè)棧底Activity,前后兩次都通過(guò)action或都通過(guò)setClassName跳轉(zhuǎn)到時(shí),第二次跳轉(zhuǎn)竟然會(huì)失敗,而兩次用不同方式跳轉(zhuǎn)時(shí),則會(huì)成功。

單純使用FLAG_ACTIVITY_NEW_TASK時(shí),跳棧底Activity和跳同棧內(nèi)其他Activity的效果大相徑庭。

業(yè)務(wù)中遇到的問(wèn)題,歸根結(jié)底就是對(duì)Android棧機(jī)制不夠了解造成的。

在面對(duì)棧相關(guān)的編碼時(shí),開發(fā)者務(wù)必要想清楚,承擔(dān)新開應(yīng)用棧的Activty在應(yīng)用全局承擔(dān)怎樣的使命,要對(duì)Task歷史、flag屬性、launchMode屬性、Intent內(nèi)容等全面評(píng)估,謹(jǐn)慎參考官方文檔,才能避免棧陷阱,達(dá)成理想可靠的效果。






審核編輯:劉清

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問(wèn)題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • Android
    +關(guān)注

    關(guān)注

    12

    文章

    3925

    瀏覽量

    127150
  • Flag
    +關(guān)注

    關(guān)注

    0

    文章

    12

    瀏覽量

    8125
  • 模擬器
    +關(guān)注

    關(guān)注

    2

    文章

    869

    瀏覽量

    43167
  • ATMS
    +關(guān)注

    關(guān)注

    0

    文章

    4

    瀏覽量

    8515

原文標(biāo)題:明修"棧"道——越過(guò)Android啟動(dòng)棧陷阱

文章出處:【微信號(hào):OSC開源社區(qū),微信公眾號(hào):OSC開源社區(qū)】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。

收藏 人收藏

    評(píng)論

    相關(guān)推薦

    帶大家認(rèn)識(shí)什么是USB.PD協(xié)議

    帶大家認(rèn)識(shí) USB PD協(xié)議
    的頭像 發(fā)表于 08-19 11:29 ?2.4w次閱讀
    <b class='flag-5'>帶大家</b>認(rèn)識(shí)什么是USB.PD協(xié)議

    下圖中的與打開文件相連的枚舉常量是自己一項(xiàng)一項(xiàng)編輯...

    下圖中的與打開文件相連的枚舉常量是自己一項(xiàng)一項(xiàng)編輯的還是自動(dòng)就有的?
    發(fā)表于 03-17 21:39

    請(qǐng)教Ring控件刪除最后一項(xiàng)

    請(qǐng)問(wèn):Ring控件,刪除其下拉內(nèi)容最后一項(xiàng),顯示不正常(如顯示為:),怎樣糾正?
    發(fā)表于 04-12 17:09

    關(guān)于電機(jī)驅(qū)動(dòng)原理的動(dòng)畫,哪位大佬可以分享一項(xiàng)嗎?

    關(guān)于電機(jī)驅(qū)動(dòng)原理的動(dòng)畫,哪位大佬可以分享一項(xiàng)嗎?
    發(fā)表于 10-13 06:17

    利用stm32來(lái)探究下程序運(yùn)行時(shí)的空間是怎么分布的

    今天我們利用stm32來(lái)探究下程序運(yùn)行時(shí)的空間是怎么分布的,為什么空間設(shè)置不合理時(shí)會(huì)有溢出導(dǎo)致程序崩潰下面是我們要使用的測(cè)試代碼,先
    發(fā)表于 01-20 08:20

    漆包線標(biāo)準(zhǔn)中的一項(xiàng)差距

    漆包線標(biāo)準(zhǔn)中的一項(xiàng)差距:我國(guó)漆包線標(biāo)準(zhǔn)是根據(jù)IEC標(biāo)準(zhǔn)制定的, 而IEC標(biāo)準(zhǔn)在國(guó)際上并不是最先進(jìn)的標(biāo)準(zhǔn)。在這里只討論IEC漆包線標(biāo)準(zhǔn)與先進(jìn)標(biāo)準(zhǔn)相比的一項(xiàng)差距在西德的些公司
    發(fā)表于 06-12 20:55 ?13次下載

    節(jié)能減排是一項(xiàng)持續(xù)性工作

    節(jié)能減排是一項(xiàng)持續(xù)性工作 PCB行業(yè)是個(gè)有污染的行業(yè),生產(chǎn)中會(huì)消耗大量的水、電等資源和能源,產(chǎn)生廢水、廢氣和廢渣,這些污染物只有得到很好的處理,才能減少
    發(fā)表于 11-26 10:47 ?455次閱讀

    Android Activity啟動(dòng)模式的詳解

    singleInstance:和singleTask差不多,唯不同的是singleInstance Activity實(shí)例的Task只能存放個(gè)該模式的Activity實(shí)例,例如Qac
    的頭像 發(fā)表于 04-18 15:47 ?3958次閱讀

    全自動(dòng)并聯(lián)水表耐壓校驗(yàn)檢定裝置的工作原理及設(shè)計(jì)

    今天為大家介紹一項(xiàng)國(guó)家發(fā)明授權(quán)專利——全自動(dòng)并聯(lián)水表耐壓校驗(yàn)檢定裝置。該專利由杭州水表有限公司申請(qǐng),并于2017年5月24日獲得授權(quán)公告。
    發(fā)表于 08-26 09:51 ?2511次閱讀
    全自動(dòng)并聯(lián)水表耐壓<b class='flag-5'>校驗(yàn)</b>檢定裝置的<b class='flag-5'>工作</b>原理及設(shè)計(jì)

    如何進(jìn)行Android中Task任務(wù)的分配

    只是針對(duì)Activity而言的。 Activity有不同的啟動(dòng)模式, 可以影響到task的分配 Task,簡(jiǎn)單的說(shuō),就是組以的模式聚集在
    發(fā)表于 07-03 17:42 ?0次下載
    如何進(jìn)行Android中Task任務(wù)<b class='flag-5'>棧</b>的分配

    沃爾瑪在休斯頓啟動(dòng)一項(xiàng)試點(diǎn)計(jì)劃 測(cè)試無(wú)人駕駛送貨服務(wù)

    作為全美最大的零售商,沃爾瑪正在休斯頓啟動(dòng)一項(xiàng)試點(diǎn)計(jì)劃:使用Nuro的自動(dòng)駕駛R1車將食品從“特定”商店運(yùn)送到選擇加入該計(jì)劃的顧客手上。沃爾瑪并沒有說(shuō)明客戶的注冊(cè)方式,但是休斯頓市民期望這項(xiàng)服務(wù)能在“未來(lái)幾周”就開始。
    發(fā)表于 12-13 14:00 ?432次閱讀

    一項(xiàng)新的研究表明,免費(fèi)上網(wǎng)應(yīng)該成為一項(xiàng)基本人權(quán)

    一項(xiàng)新的研究表明,免費(fèi)上網(wǎng)必須被視為一項(xiàng)人權(quán),因?yàn)闊o(wú)法上網(wǎng)的人們(尤其是在發(fā)展中國(guó)家)缺乏有意義的方式來(lái)影響全球參與者塑造他們的日常生活。
    的頭像 發(fā)表于 04-21 17:35 ?2960次閱讀

    關(guān)于一項(xiàng)改進(jìn)Transformer的工作

    NAACL2021中,復(fù)旦大學(xué)大學(xué)數(shù)據(jù)智能與社會(huì)計(jì)算實(shí)驗(yàn)室(Fudan DISC)和微軟亞洲研究院合作進(jìn)行了一項(xiàng)改進(jìn)Transformer的工作,論文的題目為:Mask Attention
    的頭像 發(fā)表于 04-22 10:46 ?3279次閱讀
    關(guān)于<b class='flag-5'>一項(xiàng)</b>改進(jìn)Transformer的<b class='flag-5'>工作</b>

    android的Activity應(yīng)用

    android的Activity應(yīng)用(電力電子電源技術(shù)及應(yīng)用課后答案)-android的Activity應(yīng)用,有需要的可以參考!
    發(fā)表于 08-31 13:22 ?1次下載
    android的<b class='flag-5'>Activity</b>應(yīng)用

    Activity初學(xué)乍練

    本節(jié)開始講解Android的四大組件之Activity(活動(dòng)),先來(lái)看下官方對(duì)于Activity的介紹:PS:官網(wǎng)文檔:Activity
    的頭像 發(fā)表于 04-01 22:28 ?1213次閱讀
     <b class='flag-5'>Activity</b>初學(xué)乍練