日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 >

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

發布時間:2025/3/21 66 豆豆
生活随笔 收集整理的這篇文章主要介紹了 activity 启动模式_Intent#FLAG_ACTIVITY_CLEAR_TOP 真的会 clear top 吗 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

一、背景

前段時間處理了一個 App 內草稿丟失的反饋,很多用戶反饋連續存了多個草稿之后,草稿箱都只有一個草稿,顯然是發生了草稿丟失。從用戶反饋的數據來看,反饋用戶的系統版本都在 Android 7.0 以下。

經過一段時間的排查,最后發現是草稿被覆蓋了,直接原因是:存草稿后拍攝鏈路頁面(錄制頁、編輯頁、發布頁)未被關閉,再次進拍攝之后錄制頁復用(狀態也復用了)導致存草稿的時候發生了草稿替換。

那么問題來了,為什么會發生這樣的情況呢?這得從我們存草稿那一刻說起。

二、發現問題:為什么 clear top 不生效?

正常來說,存草稿之后我們會關閉拍攝鏈路的頁面,把拍攝鏈路上的一串 Activity 都關閉掉。從代碼中可以看到,我們是使用 Intent#FLAG_ACTIVITY_CLEAR_TOP | Intent#FLAG_ACTIVITY_NEW_TASK 來實現這一目的的:

邏輯簡單明了,就是加個 clear_top 的 flag,然后 setClass 指定跳轉到首頁,這里的 getPublishContainerActivityClass 返回的是 MainActivity,看起來沒什么異常。

試著打了一個包在小米10 Pro 上試一下,存草稿,沒有復現問題,回到首頁之后按返回,直接退出 App 了,說明拍攝鏈路的頁面被關閉了。萬幸,在換了好幾臺手機之后,終于在一臺 Android 5.1 上復現了這個 case,發現存完草稿回到首頁如果再按返回鍵,就回到了發布頁,拍攝鏈路還在。

所以核心問題就是:為什么在這臺 Android 5.1 上 clear_top 這個 flag 沒生效?

三、提出問題:FLAG_ACTIVITY_CLEAR_TOP 真的會 clear top 嗎?

關于 Intent#FLAG_ACTIVITY_CLEAR_TOP,簡單地概括一下就是:設置這個 flag 后,如果發現目標 Activity 已經存在,會將目標 Activity 所在的 Task 移到前臺,然后 finish 掉目標 Activity 上層的所有 Activity,最后打開目標 Activity。至于如何判斷目標 Activity 已經存在了,注釋中并沒有提到。

回到 Aweme 工程。一般情況下,首頁、錄制頁、編輯頁、發布頁是在同一個任務棧里,從 adb 打印的 activity 堆棧信息也可以看出這一點。

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

看了下這幾個 Activity 的啟動模式,并沒發現什么不一樣的地方: VideoRecordNewActivity: singleTask VideoPublishEditActivity: standard(default) VideoPublishActivity: singleTask

大膽猜想一下,難道是不同版本的 ROM 對 Intent#FLAG_ACTIVITY_CLEAR_TOP 的處理有差異導致這個 flag 沒生效?

四、嘗試在 Demo 上復現問題

建了個 Demo 工程,創建了 A, B, C 三個空頁面,分別對應首頁、拍攝頁、發布頁,跳轉路徑是:A --> B --> C --> A。其中 A、B、C 的啟動模式分別為 singleTop, singleTask, singleTask,與 App 內的拍攝鏈路一致。

在線上問題復現的這臺 Android 5.1 上測試發現,C 用 clear_top 回到 A 之后,整個棧就清空了,在 A 點返回直接退出 App 了,沒有發現任何異常。

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

從上圖我們可以看到,任務棧里最底層的竟然是 SplashActivity,那我們的首頁呢?MainActivity 哪去了,我們明明是從首頁進拍攝然后到發布頁的......隨便在 MainActivity 中打個斷點可以發現斷點能生效,說明展示的確實是 MainActivity,但是任務棧中指向的是 SplashActivity,這是什么操作?是的,這就是 activity-alias,Android 1.0 開始就支持的一個機制。

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

關于 activity-alias 的細節暫且不深入,先改下 Demo,加個 Splash,聲明為 activity-alias?,F在 Demo 的啟動流程變成了:Splash --> A --> B --> C --> A。

修改好之后跑起來試試,結果,在 Android 5.1 的測試機上竟然復現了:C 用 clear top 打開 A 之后并沒有將 B 和 C 關閉掉!那么有沒有可能是這個 ROM 的問題?在模擬器上運行試試,發現也復現了,難道是 Android 的 Bug?不會吧,不會吧,不會吧......

五、解決問題

Talk is cheap. Show me the code.

既然在模擬器中運行 Demo App 也復現了,可以嘗試從 AOSP 源碼中追溯這個問題。

5.1 先回顧一下啟動流程中的幾個概念

5.1.1 ActivityRecord

An entry in the history stack, representing an activity.

Activity 以 ActivityRecord 對象的形式存放在任務棧中。在 Activity 的啟動過程中會創建 ActivityRecord,代表待啟動的 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.

    存放的是我們在 AndroidManifest 中聲明 Activity 時指定的一堆 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

    代表一個任務棧,棧中可能有一堆同棧的 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.

    任務棧由 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 的關系可以簡單用下面這個圖來表示:

    5.1.5 activity-alias

    Activity 別名,這是 AndroidManifest 中支持的一個標簽,使用方式和 activity 標簽類似,用于表示某個 Activity 是另一個 Activity 的別名 (targetActivity)。通過 Intent 啟動 activity-alias 類型的 Activity 時,最終只會啟動 targetActivity,不會走 activity-alias 這個 Activity 本身的任何生命周期。

    5.2 從源碼看 Activity 啟動時 clear top 的處理邏輯

    5.2.1 Android 6.0

    通過查閱 Android 6.0 的源碼,我們可以整理出如下的 Activity 啟動鏈路調用時序圖。

    注意:

  • 因為只分析 clear top 的邏輯,這里的調用鏈路只考慮了設置了 clear top flag 的場景
  • TaskRecord

    從上面的時序圖可以看到,啟動 Activity 時會先幫它找到一個任務棧,找到這個任務棧之后,會根據 Intent 的 flag 對這個任務棧進行處理。如果設置了 Intent#FLAG_ACTIVITY_CLEAR_TASK,則會清空這個任務棧中已有的 Activity。

    如果設置了 Intent#FLAG_ACTIVITY_CLEAR_TOP,則會執行 clear top 操作,將任務棧中目標 Activity 之上的其他 Activity 給 finish 掉。具體的處理邏輯如下:

    TaskRecord#performClearTaskLocked

    在執行 performClearTaskLocked 的過程中,會對任務棧中的 Activity 進行遍歷,如果判斷某個 Activity 的 realActivity 屬性和待啟動 Activity 的 realActivity 是同一個,就會執行 clear top 操作,將任務棧中這個 Activity 之上的 Activity 都干掉。

    ActivityRecord

    那么 ActivityRecord#realActivity 是在哪里設置的呢?繼續看 ActivityRecord 的源碼,可以發現 realActivity 是 ActivityRecord 中的一個 final 成員變量,事情貌似變得簡單了。

    在 Activity 的啟動過程中,在執行到 ActivityStackSupervisor#startActivityLocked 的時候,會創建一個 ActivityRecord 對象,這是待啟動 Activity 的 ActivityRecord 對象。在初始化這個 ActivityRecord 的時候,會對其 realActivity 進行賦值:

    ActivityRecord.java

    在對 realActivity 賦值的時候,滿足三個條件之一就會將傳入 Intent 的 component 設置給 realActivity,這三個條件是:

  • targetActivity 為空。說明不是 activity-alias
    • AndroidManifest 中指定 activity-alias 時才會指定 targetActivity
  • launchMode 是 LAUNCH_MULTIPLE。說明是 standard 啟動模式
  • launchMode 是 LAUNCH_SINGLE_TOP。說明是 singleTop 啟動模式
  • 到此為止,我們已經知道了 Android 6.0 是如何處理 clear top 的。此處的 realActivity 的賦值邏輯很關鍵,下面會重新提到。

    5.2.2 Android 7.0

    Android 7.0 和 Android 6.0 的調用鏈路有一定變化,主要是把原先 ActivityStackSupervisor 中的啟動邏輯拆到了一個新的 ActivityStarter 類中。

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

    TaskRecord

    這里執行 clear top 的邏輯沒有變化,依舊是對 realActivity 的判斷。

    TaskRecord#performClearTaskLocked

    ActivityRecord

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

    ActivityRecord.java

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

    Android 7.0 新加的這個判斷導致的差異可以用如下這個例子來說明:

  • 第一步:直接啟動 activity-alias 這個 Activity,創建的 ActivityRecord 插入到任務棧中時,該 ActivityRecord 的 realActivity 屬性指向該 activity-alias 對應的 targetActivity
  • 第二步:在同一個任務棧中再打開幾個中間 Activity,然后通過 clear_top 的方式直接啟動 targetActivity (指定 class 是 targetActivity),clear top 會生效,任務棧會被清空
  • 需要說明的是聲明為 activity-alias 的 Activity 不支持指定 launchMode,所以它的 launchMode 是默認值 standard,也很容易理解,畢竟只是個占位符。

    為什么 clear top 會生效呢,因為在啟動 targetActivity 時創建的 ActivityRecord 的 realActivity 也是指向的 targetActivity,所以當執行到 TaskRecord#performClearTaskLocked 的時候,就會發現和啟動 activity-alias 時創建的 ActivityRecord 的 realActivity 相等,因為都是指向的 targetActivity,從而 clear top 正常生效。

    在 Android 7.0 之前,執行上述同樣的兩步操作,clear top 不會生效,任務棧不會被清理。因為直接啟動 activity-alias 時所創建 ActivityRecord 對象的 realActivity 指向 activity-alias 本身,而再次直接啟動 targetActivity 時創建的 ActivityRecord#realActivity 指向的是 targetActivity。

    5.2.3 Android 10.0

    Android 10.0 的啟動流程有很多變化,但是 clear_top 執行的核心邏輯與 Android 7.0 沒有區別。

    TaskRecord

    TaskRecord#performClearTaskLocked

    ActivityRecord

    那么 ActivityRecord#mActivityComponent 是在哪里設置的呢?

    ActivityRecord.java

    5.2.4 總結一下

    用 Intent 啟動某個 launchMode 為 singleTop 的 Activity 時,如果在 Intent 中設置了 clear_top 的 flag,Android 在處理 clear_top 的時候,會遍歷整個任務棧,通過判斷 ActivityRecord#realActivity 在任務棧中尋找已經存在的 Activity 實例。如果找到了目標 Activity,就會將目標 Activity 之上的 Activity 全部 finish 掉。

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

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

    上述這個差異也就導致了:如果待啟動 Activity 是某個 activity-alias 的 targetActivity,Android 6.0 和 Android 7.0+ 在處理 Intent 中 clear_top flag 時可能會有不同表現,前者 clear top 不生效,后者 clear top 生效。

    5.3 真相大白

    回到我們最初的問題:為什么我們的 App 在 Android 7.0 以下的版本上,發布頁加 clear top flag 跳轉到首頁 MainActivity 沒能清空任務棧?

    再來看下我們是怎么跳首頁的。從下圖可以看到跳首頁是指定的 class 是 AVEnv.APPLICATION_SERVICE.getPublishContainerActivityClass(),看了下具體的實現發現這個類就是 MainActivity,所以確實是直接跳到首頁的。

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

    在 Android 5.1 上啟動 App 時,啟動 Launch Activity 實際啟動的是 MainActivity,但是插入到任務棧中的 ActivityRecord 的 realActivity 指向的是 SplashActivity 。當走完發布流程,在發布頁點擊存草稿時,再次啟動 MainActivity,創建的這個 ActivityRecord 的 realActivity 指向 MainActivity,在任務棧中找不到 realActivity 等于 MainActivity 的,所以 clear top 不會生效。

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

    如果要避免上述這種差異導致的 clear_top flag 不生效的問題,將 clear_top 的 Intent 指向 activity-alias 這個 Activity 即可。所以修復(準確地說是適配)這個問題的話可以把存草稿后打開首頁的 intent 的 class 設置成 SplashActivity,這么改動之后在 Android 5.1 的測試機上運行發現問題確實修復了。

    當然,既然 AOSP 是從 Android 7.0+ 做出的邏輯改動,也就解釋了為什么反饋用戶都是 Android 7.0 以下的系統。

    5.4 在 Demo 上驗證

    在 Android 5.1 上啟動 Demo App,然后用 adb 打印一下任務??纯?。打印出來發現任務棧中 ActivityRecord#realActivity 指向 Splash

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

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

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

    將 Demo 上的啟動鏈路改成:Splash --> A --> B --> C --> Splash,可以發現在 Android 5.1 的測試機和模擬器上 B 和 C 都被干掉了,clear top 生效了。

    行文到最后,再提一個有意思的點,估計是 6.0 上才發現這個問題,所以在 Android 7.0 上修復了這個問題,因為在 ActivityRecord 構造方法里 realActivity 的賦值邏輯上源碼里 7.0 新增了這樣一段注釋:

    最后,回到本文的標題,Intent#FLAG_ACTIVITY_CLEAR_TOP 真的會 clear top 嗎,你知道了嗎?^_^

    六、Demo 工程

    待上傳。

    七、推薦幾個工具

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

    八、關于作者

  • Github
  • 知乎
  • 掘金
  • 個人網站
  • 微信公眾號:

    《新程序員》:云原生和全面數字化實踐50位技術專家共同創作,文字、視頻、音頻交互閱讀

    總結

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

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。