色哟哟视频在线观看-色哟哟视频在线-色哟哟欧美15最新在线-色哟哟免费在线观看-国产l精品国产亚洲区在线观看-国产l精品国产亚洲区久久

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

帶大家探究Activity啟動前的一項重要的工作—棧校驗

OSC開源社區 ? 來源:vivo互聯網技術 ? 2023-04-19 09:36 ? 次閱讀

本文從一例業務中遇到的問題出發,以FLAG_ACTIVITY_NEW_TASK這一flag作為切入點,帶大家探究Activity啟動前的一項重要的工作——棧校驗。

文中列舉一系列業務中可能遇到的異常狀況,詳細描述了使用FLAG_ACTIVITY_NEW_TASK時可能遇到的“坑”,并從源碼中探究其根源。只有合理使用flag、launchMode,才能避免因為棧機制的特殊性,導致一系列與預期不符的啟動問題。

一、問題及背景

應用間相互聯動、相互跳轉,是實現系統整體性、體驗一致性的重要手段,也是最簡單的一種方法。

當我們用最常用的方法去startActivity時,竟也會遇到失敗的情況。在真實業務中,就遇到了這樣一例異常:用戶點擊某個按鈕時,想要“簡簡單單”跳轉另一個應用,卻沒有任何反應。

經驗豐富的你,腦海中是否涌現出了各種猜想:是不是目標Activity甚至目標App不存在?是不是目標Activty沒有對外開放?是不是有權限的限制或者跳轉的action/uri錯了……

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

本文將從源碼出發,探究前因后果,展開講講在startActivity()真正準備啟動一個Activity前,需要經過哪些“磨難”,怎樣有據可依地解決由棧問題導致的啟動異常。

1.1 業務中遇到的問題

業務中的場景是這樣的,存在A、B、C三個應用。

(1)從應用A-Activity1跳轉至應用B-Activity2;

(2)應用B-Activity2繼續跳轉到應用C-Activity3;

(3)C內某個按鈕,會再次跳轉B-Activity2,但點擊后沒有任何反應。如果不經過前面A到B的跳轉,C直接跳到B是可以的。

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

1.2 問題代碼

3個Activity的Androidmanifest配置如下,均可通過各自的action拉起,launchMode均為標準模式。

  
      
            
                
                
            
        
 
 
        
            
                
                
            
        
 
 
        
            
                
                
            
        

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 代碼初步分析

仔細查看問題代碼,在實現上非常簡單,有兩個特征:

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

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

依據經驗,我們推測與棧有關,嘗試將跳轉前棧的狀態打印出來,如下圖。

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

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

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

C-3跳轉B-2一般有3種可能的預期,如下圖:預想1,新建一個Task,在新Task中啟動一個B-2;預想2,復用已經存在的B-2;預想3,在已有Task中新建一個實例B-2。

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

但實際上3種預期都沒有實現,所有Activity的任何聲明周期都沒有變化,界面始終停留在C-3。

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

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

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

重點關注這一段:

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時,如果你正在啟動的Activity已經在一個Task中運行,那么一個新Activity不會被啟動;相反,當前Task將簡單地顯示在界面的前面,并顯示其最后的狀態。

——顯然,官方文檔與代碼注釋的表述與我們的異常現象是一致的,目標Activity2已經在Task中存在,則不會被啟動;Task直接顯示在前面,并展示最后的狀態。由于目標Activty3就是來源Activity3,所以頁面沒有任何變化。

看起來官方還是很靠譜的,但實際效果真的能一直與官方描述一致嗎?我們通過幾個場景來看一下。

二、場景拓展與驗證

2.1 場景拓展

在筆者依據官方描述進行調整、復現的過程中,發現了幾個比較有意思的場景。

PS:上面業務的案例中,B-2和C-3在不同應用內,又在相同的Task內,但實際上是否是同一個應用,對結果的影響并不大。為了避免不同應用和不同Task造成閱讀混亂,同一個棧的跳轉,我們都在本應用內進行,故業務中的場景等價于下面的【場景0】

【場景0】把業務中B-2到C-3的應用間跳轉改為B-2到B-3的應用內跳轉

// B-2跳轉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設置NEW_TASK跳轉B-2,再跳轉B-3,最終設置NEW_TASK想跳轉B-2。雖然跳C-3改為了跳B-3,但與之前問題的表現一致,沒有反應,停留在B-3。

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

有的讀者會指出這樣的問題:如果同一個應用內使用NEW_TASK跳轉,而不指定目標的taskAffinity屬性,實際是無法在新Task中啟動的。請大家忽略該問題,可以認為筆者的操作是已經加了taskAffinity的,這對最終結果并沒有影響。

【場景1】如果目標Task和來源Task不是同一個,情況是否會如官方文檔所說復用已有的Task并展示最近狀態?我們改為B-3啟動一個新Task的新Activity C-4,再通過C-4跳回B-2

// B-3跳轉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跳轉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設置NEW_TASK跳轉B-2,再跳轉B-3,再設置NEW_TASK跳轉C-4,最終設置NEW_TASK想跳轉B-2。

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

預想的結果是:不會跳到B-2,而是跳到它所在Task的頂層B-3。

實際的結果是:與預期一致,確實是跳到了B-3。

【場景2】把場景1稍做修改:C-4到B-2時,我們不通過action來跳,改為通過setClassName跳轉

// C-4跳轉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"); // 直接設置classname,不通過action
    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    context.startActivity(intent);
}

如下圖,A-1設置NEW_TASK跳轉B-2,再跳轉B-3,再設置NEW_TASK跳轉C-4,最終設置NEW_TASK想跳轉B-2。

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

預想的結果是:與場景0一致,會跳到B-2所在Task的已有頂層B-3。

實際的結果是:在已有的Task2中,產生了一個新的B-2實例。

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

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

// C-4跳轉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設置NEW_TASK跳轉B-2,再跳轉B-3,再設置NEW_TASK跳轉C-4,最終設置NEW_TASK想跳轉B-3。

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

預想的結果是:與場景0一致,會跳到B-2所在Task的頂層B-3。

實際的結果是:在已有的Task2中,產生了一個新的B-3實例。

不是說好的,Activity已經存在時,展示其所在Task的最新狀態嗎?明明Task2中已經有了B-3,并沒有直接展示它,而是生成了新的B-3實例。

【場景4】既然Activity沒有被復用,那Task一定會被復用嗎?把場景3稍做修改,直接給B-3指定一個單獨的affinity。

 
    
        
        
    

如下圖,A-1設置NEW_TASK跳轉B-2,再跳轉B-3,再設置NEW_TASK跳轉C-4,最終設置NEW_TASK想跳轉B-3。

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

——這次,連Task也不會再被復用了……Activity3在一個新的棧中被實例化了。

再回看官方的注釋,就會顯得非常不準確,甚至會讓開發者對該部分的認知產生嚴重錯誤!稍微改變過程中的某個毫無關聯的屬性(如跳轉目標、跳轉方式……),就會產生很大差異。

在看flag相關注釋時,我們要樹立一個意識:Task和Activity跳轉的實際效果,是launchMode、taskAffinity、跳轉方式、Activity在Task中的層級等屬性綜合作用的結果,不要相信“一面之詞”。

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

三、場景分析與源碼探索

本文以Android 12.0源碼為基礎,進行探究。上述場景在不同Android版本上的表現是一致的。

3.1 源碼調試注意事項

源碼的調試方法,許多文章已經有了詳細的教學,本文不再贅述。此處只簡單總結其中需要注意的事項

下載模擬器時,不要使用Google Play版本,該版本類似user版本,無法選擇system_process進程進行斷點。

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

3.2 初步斷點,明確啟動結果

以【場景0】為例,我們初步確認一下,為什么B-3跳轉B-2會無反應,系統是否告知了原因。

3.2.1 明確啟動結果及其來源

在Android源碼的斷點調試中,常見的有兩類進程:應用進程和system_process進程。

在應用進程中,我們能獲取到應用啟動結果的狀態碼result,這個result用來告訴我們啟動是否成功。涉及堆棧如下圖(標記1)所示:

Activity類::startActivity()

→ startActivityForResult()

Instrumentation類::execStartActivity(),

返回值result則是ATMS

(ActivityTaskManagerService)執行的結果。

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

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

在system_process進程中,我們看一下這個result=3是怎樣被賦值的。略去詳細斷點步驟,實際堆棧如下圖(標注1)所示:

ATMS類::startActivity() →startActivityAsUser()

ActivityStarter類::execute()

→executeRequest()

→startActivityUnchecked()

→ startActivityInner()

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

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

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

看一下源碼中對START_DELIVERED_TO_TOP的說明,如下圖:

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的結果:Activity并未真正啟動,但給定的Intent已提供給現有的頂層Activity。)

“Activity并未真正啟動”——是的,因為可以復用

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

滿足什么條件,才會造成

START_DELIVERED_TO_TOP的結果呢?筆者的思路是,通過與正常啟動流程對比,找出差異點。

3.3 過程斷點,探索啟動流程

一般來說,在定位問題時,我們習慣通過結果反推原因,但反推的過程只能關注到與問題強關聯的代碼分支,并不能能使我們很好地了解全貌。

所以,本節內容我們通過順序閱讀的方法,正向介紹startActivity過程中與上述【場景01234】強相關的邏輯。再次簡述一下:

【場景0】同一個Task內,從頂部B-3跳轉B-2——停留在B-3

【場景1】從另一個Task內的C-4,跳轉B-2——跳轉到B-3

【場景2】把場景1中,C-4跳轉B-2的方式改為setClassName()——創建新B-2實例

【場景3】把場景1中,C-4跳轉B-2改為跳轉B-3——創建新B-3實例

【場景4】給場景3中的B-3,指定taskAffinity——創建新Task和新B-3實例

3.3.1 流程源碼概覽

源碼中,整個啟動流程很長,涉及的方法和邏輯也很多,為了便于大家理清方法調用順序,方便后續內容的閱讀,筆者將本文涉及到的關鍵類及方法調用關系整理如下。

后續閱讀中如果不清楚調用關系,可以返回這里查看:

// 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; //【場景2】【場景3】從recycleTask()返回
                }
                resumeFocusedTasksTopActivities()
                return mMovedToFront ? START_TASK_TO_FRONT : START_DELIVERED_TO_TOP;//【場景1】【場景0】從recycleTask()返回
            }
        } else {
            mAddingToTask = true;
        }
        if (startResult != START_SUCCESS) {
            return startResult;//【場景1】【場景0】從startActivityInner()返回
        }
        deliverToCurrentTopIfNeeded();
        resumeFocusedTasksTopActivities();
        return startResult;
    }

3.3.2 關鍵流程分析

(1)初始化

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

在此過程中,我們記下兩個關鍵變量mMovedToFront和mAddingToTask,它們均在此被重置為false。

其中,mMovedToFront代表當Task可復用時,是否需要將目標Task移動到前臺;mAddingToTask代表是否要將Activity加入到Task中。

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

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

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

(2)計算確認啟動時的flag

該步驟會通過computeLaunchingTaskFlags()方法,根據launchMode、來源Activity的屬性等進行初步計算,確認LaunchFlags。

此處重點處理來源Activity為空的各類場景,與我們上文中的幾種場景無關,故不再展開講解。

(3)獲取可以復用的Task

該步驟通過調用getReusableTask()實現,用來查找有沒有可以復用的Task。

先說結論:場景0123中,都能獲取到可以復用的Task,而場景4中,未獲取到可復用的Task。

為什么場景4不可以復用?我們看一下getReusableTask()的關鍵實現。

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

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

然后,上圖(標注2)通過findTask()查找可以復用的Task,并將過程中找到的棧頂Activity賦值給intentActivity。最終,上圖(標注3)將intentActivity對應的Task作為結果。

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

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

主要是確認兩種結果mIdealRecord——“理想的ActivityRecord” 和 mCandidateRecord——"候選的ActivityRecord",作為intentActivity,并取intentActivity對應的Task作為復用Task。

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

在mTmpFindTaskResult.process()中確認。

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

程序會將當前系統中所有的Task進行遍歷,在每個Task中,進行如上圖所示的工作——將Task的底部Activity realActivity與目標Activity cls進行對比。

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

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

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

由此,我們就能解釋【場景4】中,新建了Task的現象。

(4)確定是否需要將目標Task移動到前臺

如果存在可復用的Task,場景0123會執行recycleTask(),該方法中會相繼進行幾個操作:setTargetRootTaskIfNeeded()、

complyActivityFlags()。

首先,程序會執行

setTargetRootTaskIfNeeded(),用來確定是否需要將目標Task移動到前臺,使用mMovedToFront作為標識。

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

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

在【場景123】中,來源Task和目標Task是不同的,differentTopTask為true,再經過一系列Task屬性對比,能夠得出mMovedToFront為true;

而場景0中,來源Task和目標Task相同,differentTopTask為false,mMovedToFront保持初始的false。

由此,我們就能解釋【場景0】中,Task不會發生切換的現象。

(5)通過對比flag、Intent、Component等確認是否要將Activity加入到Task中

還是在【場景0123】中,recycleTask()會繼續執行complyActivityFlags(),用來確認是否要將Activity加入到Task中,使用mAddingToTask作為標識。

該方法會對FLAG_ACTIVITY_NEW_TASK、

FLAG_ACTIVITY_CLEAR_TASK、

FLAG_ACTIVITY_CLEAR_TOP等諸多flag、Intent信息進行一系列判斷。

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

上圖(標注1)中,會先判斷后續是否需要重置Task,resetTask,判斷條件則是FLAG_ACTIVITY_RESET_TASK_IF_NEEDED,顯然,場景0123的resetTask都為false。繼續執行。

接著,會有多種條件判斷按順序執行。

在【場景3】中,目標Component(mActivityComponent)是B-3,目標Task的realActivity則是B-2,兩者不相同,進入了resetTask相關的判斷(標注2)。

之前resetTask已經是false,故【場景3】的mAddingToTask脫離原始值,被置為true。

在【場景012】中,相對比的兩個Activity都是B-2(標注3),可以進入下一級判斷——isSameIntentFilter()。

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

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

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

這一步判斷的內容就很明顯了,目標Activity2的已有Intent 與 新的Intent做對比。很顯然,場景2中由于改為了setClassName跳轉,Intent自然不一樣了。

故【場景2】的mAddingToTask脫離原始值,被置為true。

總結看一下:

【場景123】的mMovedToFront最先被置為true,而【場景0】經歷重重考驗,保持初始值為false。

——這意味著當有可復用Task時,【場景0】不需要把Task切換到前列;【場景123】需要切換到目標Task。

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

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

(6)實際啟動Activity或直接返回結果

被啟動的各個Activity會通過resumeFocusedTasksTopActivities()等一系列操作,開始真正的啟動與生命周期的調用。

我們關于上述各個場景的探索已經得到答案,后續流程便不再關注。

四、問題修復及遺留問題解答

4.1 問題修復

既然前面總結了這么多必要條件,我們只需要破壞其中的某些條件,就可以修復業務中遇到的問題了,簡單列舉幾個的方案。

方案一:修改flag。B-3跳轉B-2時,增加FLAG_ACTIVITY_CLEAR_TASK或FLAG_ACTIVITY_CLEAR_TOP,或者直接不設置flag。經驗證可行。

方案二:修改intent屬性,即【場景2】。A-1通過action方式隱式跳轉B-2,則B-3可以通過setClassName方式,或修改action內屬性的方式跳轉B-2。經驗證可行。

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

4.2 遺留問題

還記得我們在文章開端的某個疑惑嗎,為什么沒有回調onNewIntent()?

onNewIntent() 會通過deliverNewIntent()觸發,而deliverNewIntent()僅通過以下兩個方法調用。

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

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

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

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

mLaunchFlags和mLaunchMode,無法滿足條件,導致dontStart為false,無緣

deliverNewIntent()。

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

五、結語

通過一系列場景假設,我們發現了許多出乎意料的現象:

文檔提到FLAG_ACTIVITY_NEW_TASK等價于singleTask,與事實并不完全如此,只有與其他flag搭配才能達到相似的效果。這一flag的注釋非常片面,甚至會引發誤解,單一因素無法決定整體表現。

官方文檔提到

START_DELIVERED_TO_TOP會將新的Intent傳遞給頂層Activity,但事實上,并不是每一種START_DELIVERED_TO_TOP都會把新的Intent重新分發。

同一個棧底Activity,前后兩次都通過action或都通過setClassName跳轉到時,第二次跳轉竟然會失敗,而兩次用不同方式跳轉時,則會成功。

單純使用FLAG_ACTIVITY_NEW_TASK時,跳棧底Activity和跳同棧內其他Activity的效果大相徑庭。

業務中遇到的問題,歸根結底就是對Android棧機制不夠了解造成的。

在面對棧相關的編碼時,開發者務必要想清楚,承擔新開應用棧的Activty在應用全局承擔怎樣的使命,要對Task歷史、flag屬性、launchMode屬性、Intent內容等全面評估,謹慎參考官方文檔,才能避免棧陷阱,達成理想可靠的效果。






審核編輯:劉清

聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • Android
    +關注

    關注

    12

    文章

    3935

    瀏覽量

    127354
  • Flag
    +關注

    關注

    0

    文章

    12

    瀏覽量

    8134
  • 模擬器
    +關注

    關注

    2

    文章

    875

    瀏覽量

    43211
  • ATMS
    +關注

    關注

    0

    文章

    4

    瀏覽量

    8523

原文標題:明修"棧"道——越過Android啟動棧陷阱

文章出處:【微信號:OSC開源社區,微信公眾號:OSC開源社區】歡迎添加關注!文章轉載請注明出處。

收藏 人收藏

    評論

    相關推薦

    帶大家認識什么是USB.PD協議

    帶大家認識 USB PD協議
    的頭像 發表于 08-19 11:29 ?2.4w次閱讀
    <b class='flag-5'>帶大家</b>認識什么是USB.PD協議

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

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

    請教Ring控件刪除最后一項

    請問:Ring控件,刪除其下拉內容最后一項,顯示不正常(如顯示為:),怎樣糾正?
    發表于 04-12 17:09

    關于電機驅動原理的動畫,哪位大佬可以分享一項嗎?

    關于電機驅動原理的動畫,哪位大佬可以分享一項嗎?
    發表于 10-13 06:17

    利用stm32來探究下程序運行時的空間是怎么分布的

    今天我們利用stm32來探究下程序運行時的空間是怎么分布的,為什么空間設置不合理時會有溢出導致程序崩潰下面是我們要使用的測試代碼,先
    發表于 01-20 08:20

    漆包線標準中的一項差距

    漆包線標準中的一項差距:我國漆包線標準是根據IEC標準制定的, 而IEC標準在國際上并不是最先進的標準。在這里只討論IEC漆包線標準與先進標準相比的一項差距在西德的些公司
    發表于 06-12 20:55 ?13次下載

    節能減排是一項持續性工作

    節能減排是一項持續性工作 PCB行業是個有污染的行業,生產中會消耗大量的水、電等資源和能源,產生廢水、廢氣和廢渣,這些污染物只有得到很好的處理,才能減少
    發表于 11-26 10:47 ?462次閱讀

    Android Activity啟動模式的詳解

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

    全自動并聯水表耐壓校驗檢定裝置的工作原理及設計

    今天為大家介紹一項國家發明授權專利——全自動并聯水表耐壓校驗檢定裝置。該專利由杭州水表有限公司申請,并于2017年5月24日獲得授權公告。
    發表于 08-26 09:51 ?2526次閱讀
    全自動并聯水表耐壓<b class='flag-5'>校驗</b>檢定裝置的<b class='flag-5'>工作</b>原理及設計

    如何進行Android中Task任務的分配

    只是針對Activity而言的。 Activity有不同的啟動模式, 可以影響到task的分配 Task,簡單的說,就是組以的模式聚集在
    發表于 07-03 17:42 ?0次下載
    如何進行Android中Task任務<b class='flag-5'>棧</b>的分配

    沃爾瑪在休斯頓啟動一項試點計劃 測試無人駕駛送貨服務

    作為全美最大的零售商,沃爾瑪正在休斯頓啟動一項試點計劃:使用Nuro的自動駕駛R1車將食品從“特定”商店運送到選擇加入該計劃的顧客手上。沃爾瑪并沒有說明客戶的注冊方式,但是休斯頓市民期望這項服務能在“未來幾周”就開始。
    發表于 12-13 14:00 ?438次閱讀

    一項新的研究表明,免費上網應該成為一項基本人權

    一項新的研究表明,免費上網必須被視為一項人權,因為無法上網的人們(尤其是在發展中國家)缺乏有意義的方式來影響全球參與者塑造他們的日常生活。
    的頭像 發表于 04-21 17:35 ?2992次閱讀

    關于一項改進Transformer的工作

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

    android的Activity應用

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

    Activity初學乍練

    本節開始講解Android的四大組件之Activity(活動),先來看下官方對于Activity的介紹:PS:官網文檔:Activity
    的頭像 發表于 04-01 22:28 ?1235次閱讀
     <b class='flag-5'>Activity</b>初學乍練
    主站蜘蛛池模板: 欧美极品尿交| 嫩草影院精品视频在线观看| 爽爽窝窝午夜精品一区二区| 成人中文字幕在线观看| 日本激情网址| 国产精品免费小视频| 亚洲大爷操| 久久久久久久网站| 99re热有精品国产| 日日操天天操夜夜操| 国产美女裸身网站免费观看视频 | 少妇厨房愉情理9伦片视频| 国产高清在线观看| 亚洲人成网站在线播放| 邻家美姨在线观看全集免费| MM131亚洲精品久久安然| 甜性涩爱dvd| 久久久97人妻无码精品蜜桃| 国产99视频在线观看| 亚洲中文在线偷拍| 欧美乱码伦视频免费66网| 久久超碰国产精品最新| 超碰97超碰在线视频哦| 亚洲免费人成 久久| 欧美午夜精品一区区电影| 国产午夜亚洲精品一区| 97超碰在线视频人人av| 午夜福利免费院| 蜜桃成熟时33D在线嘟嘟网| 国产精品观看视频免费完整版| a级毛片高清免费视频| 亚洲成人精品久久| 忘忧草在线影院WWW日本动漫| 久久久久久久电影| 精品国产乱码久久久久久夜深人妻| GOGOGO高清免费播放| 亚洲欧美中文字幕网站大全| 亚洲AV无码一区二区色情蜜芽| 无码毛片内射白浆视频| 污污内射在线观看一区二区少妇| 色四房播播|