日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪(fǎng)問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

activity 启动模式_Intent#FLAG_ACTIVITY_CLEAR_TOP 真的会 clear top 吗

發(fā)布時(shí)間:2025/3/21 编程问答 43 豆豆
生活随笔 收集整理的這篇文章主要介紹了 activity 启动模式_Intent#FLAG_ACTIVITY_CLEAR_TOP 真的会 clear top 吗 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

一、背景

前段時(shí)間處理了一個(gè) App 內(nèi)草稿丟失的反饋,很多用戶(hù)反饋連續(xù)存了多個(gè)草稿之后,草稿箱都只有一個(gè)草稿,顯然是發(fā)生了草稿丟失。從用戶(hù)反饋的數(shù)據(jù)來(lái)看,反饋用戶(hù)的系統(tǒng)版本都在 Android 7.0 以下。

經(jīng)過(guò)一段時(shí)間的排查,最后發(fā)現(xiàn)是草稿被覆蓋了,直接原因是:存草稿后拍攝鏈路頁(yè)面(錄制頁(yè)、編輯頁(yè)、發(fā)布頁(yè))未被關(guān)閉,再次進(jìn)拍攝之后錄制頁(yè)復(fù)用(狀態(tài)也復(fù)用了)導(dǎo)致存草稿的時(shí)候發(fā)生了草稿替換。

那么問(wèn)題來(lái)了,為什么會(huì)發(fā)生這樣的情況呢?這得從我們存草稿那一刻說(shuō)起。

二、發(fā)現(xiàn)問(wèn)題:為什么 clear top 不生效?

正常來(lái)說(shuō),存草稿之后我們會(huì)關(guān)閉拍攝鏈路的頁(yè)面,把拍攝鏈路上的一串 Activity 都關(guān)閉掉。從代碼中可以看到,我們是使用 Intent#FLAG_ACTIVITY_CLEAR_TOP | Intent#FLAG_ACTIVITY_NEW_TASK 來(lái)實(shí)現(xiàn)這一目的的:

邏輯簡(jiǎn)單明了,就是加個(gè) clear_top 的 flag,然后 setClass 指定跳轉(zhuǎn)到首頁(yè),這里的 getPublishContainerActivityClass 返回的是 MainActivity,看起來(lái)沒(méi)什么異常。

試著打了一個(gè)包在小米10 Pro 上試一下,存草稿,沒(méi)有復(fù)現(xiàn)問(wèn)題,回到首頁(yè)之后按返回,直接退出 App 了,說(shuō)明拍攝鏈路的頁(yè)面被關(guān)閉了。萬(wàn)幸,在換了好幾臺(tái)手機(jī)之后,終于在一臺(tái) Android 5.1 上復(fù)現(xiàn)了這個(gè) case,發(fā)現(xiàn)存完草稿回到首頁(yè)如果再按返回鍵,就回到了發(fā)布頁(yè),拍攝鏈路還在。

所以核心問(wèn)題就是:為什么在這臺(tái) Android 5.1 上 clear_top 這個(gè) flag 沒(méi)生效?

三、提出問(wèn)題:FLAG_ACTIVITY_CLEAR_TOP 真的會(huì) clear top 嗎?

關(guān)于 Intent#FLAG_ACTIVITY_CLEAR_TOP,簡(jiǎn)單地概括一下就是:設(shè)置這個(gè) flag 后,如果發(fā)現(xiàn)目標(biāo) Activity 已經(jīng)存在,會(huì)將目標(biāo) Activity 所在的 Task 移到前臺(tái),然后 finish 掉目標(biāo) Activity 上層的所有 Activity,最后打開(kāi)目標(biāo) Activity。至于如何判斷目標(biāo) Activity 已經(jīng)存在了,注釋中并沒(méi)有提到。

回到 Aweme 工程。一般情況下,首頁(yè)、錄制頁(yè)、編輯頁(yè)、發(fā)布頁(yè)是在同一個(gè)任務(wù)棧里,從 adb 打印的 activity 堆棧信息也可以看出這一點(diǎn)。

adb shell dumpsys activity activities | grep 'com.zhiliaoapp.musically'

看了下這幾個(gè) Activity 的啟動(dòng)模式,并沒(méi)發(fā)現(xiàn)什么不一樣的地方: VideoRecordNewActivity: singleTask VideoPublishEditActivity: standard(default) VideoPublishActivity: singleTask

大膽猜想一下,難道是不同版本的 ROM 對(duì) Intent#FLAG_ACTIVITY_CLEAR_TOP 的處理有差異導(dǎo)致這個(gè) flag 沒(méi)生效?

四、嘗試在 Demo 上復(fù)現(xiàn)問(wèn)題

建了個(gè) Demo 工程,創(chuàng)建了 A, B, C 三個(gè)空頁(yè)面,分別對(duì)應(yīng)首頁(yè)、拍攝頁(yè)、發(fā)布頁(yè),跳轉(zhuǎn)路徑是:A --> B --> C --> A。其中 A、B、C 的啟動(dòng)模式分別為 singleTop, singleTask, singleTask,與 App 內(nèi)的拍攝鏈路一致。

在線(xiàn)上問(wèn)題復(fù)現(xiàn)的這臺(tái) Android 5.1 上測(cè)試發(fā)現(xiàn),C 用 clear_top 回到 A 之后,整個(gè)棧就清空了,在 A 點(diǎn)返回直接退出 App 了,沒(méi)有發(fā)現(xiàn)任何異常。

陷入沉思,Demo 上沒(méi)復(fù)現(xiàn),難道是 Aweme 工程里對(duì) Activity 或者 Intent 這塊做了騷操作?(盲猜一手)。打印一下復(fù)現(xiàn)問(wèn)題的這臺(tái) Android 5.1 的 App 存草稿之后的任務(wù)棧,發(fā)現(xiàn)了一些不一樣的東西:

從上圖我們可以看到,任務(wù)棧里最底層的竟然是 SplashActivity,那我們的首頁(yè)呢?MainActivity 哪去了,我們明明是從首頁(yè)進(jìn)拍攝然后到發(fā)布頁(yè)的......隨便在 MainActivity 中打個(gè)斷點(diǎn)可以發(fā)現(xiàn)斷點(diǎn)能生效,說(shuō)明展示的確實(shí)是 MainActivity,但是任務(wù)棧中指向的是 SplashActivity,這是什么操作?是的,這就是 activity-alias,Android 1.0 開(kāi)始就支持的一個(gè)機(jī)制。

在 AndroidManifest 中查看 SplashActivity 和 MainActivity 的聲明,可以發(fā)現(xiàn) SplashActivity 被聲明成了 activity-alias,其 targetActivity 指向的是 MainActivity。

關(guān)于 activity-alias 的細(xì)節(jié)暫且不深入,先改下 Demo,加個(gè) Splash,聲明為 activity-alias?,F(xiàn)在 Demo 的啟動(dòng)流程變成了:Splash --> A --> B --> C --> A。

修改好之后跑起來(lái)試試,結(jié)果,在 Android 5.1 的測(cè)試機(jī)上竟然復(fù)現(xiàn)了:C 用 clear top 打開(kāi) A 之后并沒(méi)有將 B 和 C 關(guān)閉掉!那么有沒(méi)有可能是這個(gè) ROM 的問(wèn)題?在模擬器上運(yùn)行試試,發(fā)現(xiàn)也復(fù)現(xiàn)了,難道是 Android 的 Bug?不會(huì)吧,不會(huì)吧,不會(huì)吧......

五、解決問(wèn)題

Talk is cheap. Show me the code.

既然在模擬器中運(yùn)行 Demo App 也復(fù)現(xiàn)了,可以嘗試從 AOSP 源碼中追溯這個(gè)問(wèn)題。

5.1 先回顧一下啟動(dòng)流程中的幾個(gè)概念

5.1.1 ActivityRecord

An entry in the history stack, representing an activity.

Activity 以 ActivityRecord 對(duì)象的形式存放在任務(wù)棧中。在 Activity 的啟動(dòng)過(guò)程中會(huì)創(chuàng)建 ActivityRecord,代表待啟動(dòng)的 Activity。

  • ActivityRecord resultTo
    • who started this entry, so will get our reply

    5.1.2 ActivityInfo

    Information you can retrieve about a particular application activity or receiver. This corresponds to information collected from the AndroidManifest.xml's and tags.

    存放的是我們?cè)?AndroidManifest 中聲明 Activity 時(shí)指定的一堆 Activity 配置。

  • taskAffinity
  • The affinity this activity has for another task in the system. The string here is the name of the task, often the package name of the overall package.
  • 5.1.3 TaskRecord

    代表一個(gè)任務(wù)棧,棧中可能有一堆同棧的 Activity。

  • Intent intent
    • The original intent that started the task
  • ArrayList mActivities
    • List of all activities in the task arranged in history order
  • ActivityStack stack
    • The ActivityStack it belongs to.

    5.1.4 ActivityStack

    State and management of a single stack of activities.

    任務(wù)棧由 ActivityStack 持有。

  • ArrayList mTaskHistory
    • The back history of all previous (and possibly still running) activities. It contains #TaskRecord objects.
    • 0 ~ size - 1, the last is the top task.

    ActivityRecord, TaskRecord, ActivityStack 的關(guān)系可以簡(jiǎn)單用下面這個(gè)圖來(lái)表示:

    5.1.5 activity-alias

    Activity 別名,這是 AndroidManifest 中支持的一個(gè)標(biāo)簽,使用方式和 activity 標(biāo)簽類(lèi)似,用于表示某個(gè) Activity 是另一個(gè) Activity 的別名 (targetActivity)。通過(guò) Intent 啟動(dòng) activity-alias 類(lèi)型的 Activity 時(shí),最終只會(huì)啟動(dòng) targetActivity,不會(huì)走 activity-alias 這個(gè) Activity 本身的任何生命周期。

    5.2 從源碼看 Activity 啟動(dòng)時(shí) clear top 的處理邏輯

    5.2.1 Android 6.0

    通過(guò)查閱 Android 6.0 的源碼,我們可以整理出如下的 Activity 啟動(dòng)鏈路調(diào)用時(shí)序圖。

    注意:

  • 因?yàn)橹环治?clear top 的邏輯,這里的調(diào)用鏈路只考慮了設(shè)置了 clear top flag 的場(chǎng)景
  • TaskRecord

    從上面的時(shí)序圖可以看到,啟動(dòng) Activity 時(shí)會(huì)先幫它找到一個(gè)任務(wù)棧,找到這個(gè)任務(wù)棧之后,會(huì)根據(jù) Intent 的 flag 對(duì)這個(gè)任務(wù)棧進(jìn)行處理。如果設(shè)置了 Intent#FLAG_ACTIVITY_CLEAR_TASK,則會(huì)清空這個(gè)任務(wù)棧中已有的 Activity。

    如果設(shè)置了 Intent#FLAG_ACTIVITY_CLEAR_TOP,則會(huì)執(zhí)行 clear top 操作,將任務(wù)棧中目標(biāo) Activity 之上的其他 Activity 給 finish 掉。具體的處理邏輯如下:

    TaskRecord#performClearTaskLocked

    在執(zhí)行 performClearTaskLocked 的過(guò)程中,會(huì)對(duì)任務(wù)棧中的 Activity 進(jìn)行遍歷,如果判斷某個(gè) Activity 的 realActivity 屬性和待啟動(dòng) Activity 的 realActivity 是同一個(gè),就會(huì)執(zhí)行 clear top 操作,將任務(wù)棧中這個(gè) Activity 之上的 Activity 都干掉。

    ActivityRecord

    那么 ActivityRecord#realActivity 是在哪里設(shè)置的呢?繼續(xù)看 ActivityRecord 的源碼,可以發(fā)現(xiàn) realActivity 是 ActivityRecord 中的一個(gè) final 成員變量,事情貌似變得簡(jiǎn)單了。

    在 Activity 的啟動(dòng)過(guò)程中,在執(zhí)行到 ActivityStackSupervisor#startActivityLocked 的時(shí)候,會(huì)創(chuàng)建一個(gè) ActivityRecord 對(duì)象,這是待啟動(dòng) Activity 的 ActivityRecord 對(duì)象。在初始化這個(gè) ActivityRecord 的時(shí)候,會(huì)對(duì)其 realActivity 進(jìn)行賦值:

    ActivityRecord.java

    在對(duì) realActivity 賦值的時(shí)候,滿(mǎn)足三個(gè)條件之一就會(huì)將傳入 Intent 的 component 設(shè)置給 realActivity,這三個(gè)條件是:

  • targetActivity 為空。說(shuō)明不是 activity-alias
    • AndroidManifest 中指定 activity-alias 時(shí)才會(huì)指定 targetActivity
  • launchMode 是 LAUNCH_MULTIPLE。說(shuō)明是 standard 啟動(dòng)模式
  • launchMode 是 LAUNCH_SINGLE_TOP。說(shuō)明是 singleTop 啟動(dòng)模式
  • 到此為止,我們已經(jīng)知道了 Android 6.0 是如何處理 clear top 的。此處的 realActivity 的賦值邏輯很關(guān)鍵,下面會(huì)重新提到。

    5.2.2 Android 7.0

    Android 7.0 和 Android 6.0 的調(diào)用鏈路有一定變化,主要是把原先 ActivityStackSupervisor 中的啟動(dòng)邏輯拆到了一個(gè)新的 ActivityStarter 類(lèi)中。

    依舊是在 TaskRecord#performClearTaskLocked 里處理 clear top。

    TaskRecord

    這里執(zhí)行 clear top 的邏輯沒(méi)有變化,依舊是對(duì) realActivity 的判斷。

    TaskRecord#performClearTaskLocked

    ActivityRecord

    也可以看到,realActivity 依舊是 final 變量。

    ActivityRecord.java

    但是!Android 7.0 對(duì) ActivityRecord#realActivity 的賦值邏輯做了調(diào)整,新加了個(gè)判斷:aInfo.targetActivity.equals(_intent.getComponent().getClassName()

    Android 7.0 新加的這個(gè)判斷導(dǎo)致的差異可以用如下這個(gè)例子來(lái)說(shuō)明:

  • 第一步:直接啟動(dòng) activity-alias 這個(gè) Activity,創(chuàng)建的 ActivityRecord 插入到任務(wù)棧中時(shí),該 ActivityRecord 的 realActivity 屬性指向該 activity-alias 對(duì)應(yīng)的 targetActivity
  • 第二步:在同一個(gè)任務(wù)棧中再打開(kāi)幾個(gè)中間 Activity,然后通過(guò) clear_top 的方式直接啟動(dòng) targetActivity (指定 class 是 targetActivity),clear top 會(huì)生效,任務(wù)棧會(huì)被清空
  • 需要說(shuō)明的是聲明為 activity-alias 的 Activity 不支持指定 launchMode,所以它的 launchMode 是默認(rèn)值 standard,也很容易理解,畢竟只是個(gè)占位符。

    為什么 clear top 會(huì)生效呢,因?yàn)樵趩?dòng) targetActivity 時(shí)創(chuàng)建的 ActivityRecord 的 realActivity 也是指向的 targetActivity,所以當(dāng)執(zhí)行到 TaskRecord#performClearTaskLocked 的時(shí)候,就會(huì)發(fā)現(xiàn)和啟動(dòng) activity-alias 時(shí)創(chuàng)建的 ActivityRecord 的 realActivity 相等,因?yàn)槎际侵赶虻?targetActivity,從而 clear top 正常生效。

    在 Android 7.0 之前,執(zhí)行上述同樣的兩步操作,clear top 不會(huì)生效,任務(wù)棧不會(huì)被清理。因?yàn)橹苯訂?dòng) activity-alias 時(shí)所創(chuàng)建 ActivityRecord 對(duì)象的 realActivity 指向 activity-alias 本身,而再次直接啟動(dòng) targetActivity 時(shí)創(chuàng)建的 ActivityRecord#realActivity 指向的是 targetActivity。

    5.2.3 Android 10.0

    Android 10.0 的啟動(dòng)流程有很多變化,但是 clear_top 執(zhí)行的核心邏輯與 Android 7.0 沒(méi)有區(qū)別。

    TaskRecord

    TaskRecord#performClearTaskLocked

    ActivityRecord

    那么 ActivityRecord#mActivityComponent 是在哪里設(shè)置的呢?

    ActivityRecord.java

    5.2.4 總結(jié)一下

    用 Intent 啟動(dòng)某個(gè) launchMode 為 singleTop 的 Activity 時(shí),如果在 Intent 中設(shè)置了 clear_top 的 flag,Android 在處理 clear_top 的時(shí)候,會(huì)遍歷整個(gè)任務(wù)棧,通過(guò)判斷 ActivityRecord#realActivity 在任務(wù)棧中尋找已經(jīng)存在的 Activity 實(shí)例。如果找到了目標(biāo) Activity,就會(huì)將目標(biāo) Activity 之上的 Activity 全部 finish 掉。

    ActivityRecord#realActivity 是在 ActivityRecord 的構(gòu)造方法中初始化的,其初始化邏輯在 Android 6.0 以下(包含 6.0)和 Android 7.0+ 有差異。

    Android 5.0/6.0(含以下)在初始化 ActivityRecord 的時(shí)候,未對(duì) activity-alias 做判斷,realActivity 指向的是 activity-alias 這個(gè) Activity 自身。Android 7.0+ 以后對(duì) activity-alias 進(jìn)行了判斷,realActivity 指向的是 activity-alias 的 targetActivity。

    上述這個(gè)差異也就導(dǎo)致了:如果待啟動(dòng) Activity 是某個(gè) activity-alias 的 targetActivity,Android 6.0 和 Android 7.0+ 在處理 Intent 中 clear_top flag 時(shí)可能會(huì)有不同表現(xiàn),前者 clear top 不生效,后者 clear top 生效。

    5.3 真相大白

    回到我們最初的問(wèn)題:為什么我們的 App 在 Android 7.0 以下的版本上,發(fā)布頁(yè)加 clear top flag 跳轉(zhuǎn)到首頁(yè) MainActivity 沒(méi)能清空任務(wù)棧?

    再來(lái)看下我們是怎么跳首頁(yè)的。從下圖可以看到跳首頁(yè)是指定的 class 是 AVEnv.APPLICATION_SERVICE.getPublishContainerActivityClass(),看了下具體的實(shí)現(xiàn)發(fā)現(xiàn)這個(gè)類(lèi)就是 MainActivity,所以確實(shí)是直接跳到首頁(yè)的。

    在前面我們有提到,我們 App 的 Launch Activity 是 SplashActivity,但是它只是個(gè) activity-alias,其 targetActivity 指向 MainActivity,MainActivity 的 launchMode 是 singleTop。經(jīng)過(guò)上面對(duì) Android 6.0, Android 7.0, Android 10.0 啟動(dòng)流程的分析,我想答案已經(jīng)比較明顯了。

    在 Android 5.1 上啟動(dòng) App 時(shí),啟動(dòng) Launch Activity 實(shí)際啟動(dòng)的是 MainActivity,但是插入到任務(wù)棧中的 ActivityRecord 的 realActivity 指向的是 SplashActivity 。當(dāng)走完發(fā)布流程,在發(fā)布頁(yè)點(diǎn)擊存草稿時(shí),再次啟動(dòng) MainActivity,創(chuàng)建的這個(gè) ActivityRecord 的 realActivity 指向 MainActivity,在任務(wù)棧中找不到 realActivity 等于 MainActivity 的,所以 clear top 不會(huì)生效。

    在 Android 7.0+ 上的版本 clear top 會(huì)生效,原因不再贅述。

    如果要避免上述這種差異導(dǎo)致的 clear_top flag 不生效的問(wèn)題,將 clear_top 的 Intent 指向 activity-alias 這個(gè) Activity 即可。所以修復(fù)(準(zhǔn)確地說(shuō)是適配)這個(gè)問(wèn)題的話(huà)可以把存草稿后打開(kāi)首頁(yè)的 intent 的 class 設(shè)置成 SplashActivity,這么改動(dòng)之后在 Android 5.1 的測(cè)試機(jī)上運(yùn)行發(fā)現(xiàn)問(wèn)題確實(shí)修復(fù)了。

    當(dāng)然,既然 AOSP 是從 Android 7.0+ 做出的邏輯改動(dòng),也就解釋了為什么反饋用戶(hù)都是 Android 7.0 以下的系統(tǒng)。

    5.4 在 Demo 上驗(yàn)證

    在 Android 5.1 上啟動(dòng) Demo App,然后用 adb 打印一下任務(wù)??纯?。打印出來(lái)發(fā)現(xiàn)任務(wù)棧中 ActivityRecord#realActivity 指向 Splash

    adb shell dumpsys activity activities | grep 'com.yongf.android.myapplication'

    同樣的包在 Android 7.0 上打印的信息如下,可以看到任務(wù)棧中 ActivityRecord#realActivity 已經(jīng)不再指向 Splash,而是指向 A 了。

    adb shell dumpsys activity activities | grep 'com.yongf.android.myapplication'

    將 Demo 上的啟動(dòng)鏈路改成:Splash --> A --> B --> C --> Splash,可以發(fā)現(xiàn)在 Android 5.1 的測(cè)試機(jī)和模擬器上 B 和 C 都被干掉了,clear top 生效了。

    行文到最后,再提一個(gè)有意思的點(diǎn),估計(jì)是 6.0 上才發(fā)現(xiàn)這個(gè)問(wèn)題,所以在 Android 7.0 上修復(fù)了這個(gè)問(wèn)題,因?yàn)樵?ActivityRecord 構(gòu)造方法里 realActivity 的賦值邏輯上源碼里 7.0 新增了這樣一段注釋:

    最后,回到本文的標(biāo)題,Intent#FLAG_ACTIVITY_CLEAR_TOP 真的會(huì) clear top 嗎,你知道了嗎?^_^

    六、Demo 工程

    待上傳。

    七、推薦幾個(gè)工具

  • Carbon
    • Carbon is the easiest way to create and share beautiful images of your source code.
  • Android Source Code Viewer
    • Android Code Search

    八、關(guān)于作者

  • Github
  • 知乎
  • 掘金
  • 個(gè)人網(wǎng)站
  • 微信公眾號(hào):

    《新程序員》:云原生和全面數(shù)字化實(shí)踐50位技術(shù)專(zhuān)家共同創(chuàng)作,文字、視頻、音頻交互閱讀

    總結(jié)

    以上是生活随笔為你收集整理的activity 启动模式_Intent#FLAG_ACTIVITY_CLEAR_TOP 真的会 clear top 吗的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

    如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。