版權宣告:
本賬號釋出文章均來自公眾號,承香墨影(cxmyDev),版權歸承香墨影所有。
每週會統一更新到掘金,如果喜歡,可關注公眾號獲取最新文章。
未經允許,不得轉載。
一、前言
現在越來越多的 App 以 Router 路由的形式,來實現模組化。一般而言,這種 Router 的方案,從外部直接調起的方式,是由一個 ProxyActivity 做一個代理,然後再由它去跳轉到專案內的其他目標 TargetActivity 。這樣的實現,理論上,是可以從外部調起 App 內所有的 Activity 的。
但是這樣就面臨一個問題,如果從外部調起了一個子頁的 Activity,舉例是一個影片詳情頁,如果想在使用者回退的時候,進入到應用主頁,而非直接退出了。就需要特殊處理了。
本文就以如何優化的回退到我們需要的 Activity 來做一個說明。
二、分析和拆解問題
對於前面舉例的情況來說,實際上我們只需要處理好以下問題即可。
- 如何區分當前 Activity 是從應用內開啟,還是從應用外直接開啟,就是開啟 Activity 的來源。
- 在區分出來 Activity 開啟的來源之後,如何優雅的退回到我們需要的 Activity。
- 要處理一些特殊情況。
對於這樣兩個問題,區分來源,最粗暴的方式,就是在 Intent 裡增加一個類似 from 的欄位,來標記是從那個頁面過來的,如果不是我們指定的話,在 finish()
或者 onBackPressed()
的時候,就開啟 MainActivity 即可。
這是一個粗暴的方式,雖然它有用,可它不夠優雅。
而在 Support v4 22.0.0 開始,針對這樣的情況,為我們增加了一個 NavUtils 的類,專門用於處理這種情況,接下來就來看看如何使用它。
三、NavUtils 如何使用
NavUtils 從名稱上就可以看出來,它是一個用於處理導航的輔助工具類。
先來看看它的方法,它主要的方法主要分三中:
- getParentActivityIntent():獲得一個使用者回退到父Activity 的Intent。
- shouldUpRecreateTask():是否需要重新構建一個 Task。
- navigateUpTo():回退到 Intent 指定的父 Activity。
為了方便使用 getParentActivityIntent()
提供了很多的過載,但是實際上目的都是一樣的,就是拿到我們指定的當前 Activity 的上一級 Activity(父 Activity )。
既然是 Support v4 包下的輔助類,它其實在內部也是做好了很多相容的處理。它針對不同的 Android Level 做了不同的實現,可以看到 NavUtilsImplBase 和 NavUtilsImplJB 都是為了處理相容性的問題,他們都實現了 NavUtilsImpl 介面,而且在靜態程式碼塊內處理好了相容性問題。
好了,原始碼就先聊到這裡,先來看看如何使用。
如果想要使用 NavUtils 還需要在 AndroidManifest.xml 中,為 Activity 指定一個父的 Activity。
這裡有個 Demo ,有兩個 Activity,分別是 MainActivity 和 ChildActivity 。我們需要對 ChildActivity 設定父 Activity。
為 Activity 設定父 Activity,是需要區分版本的,在 4.1 之後,是可以直接使用 android:parentActivityName
直接指定即可。而對於 4.0 及一下的版本,如果想要使用,可以配置 meta-data
標籤,name 必須是 android.support.PARENT_ACTIVITY
而 value 就是用來指定父 Activity 的。
先來看看官方推薦的使用示例。
它的流程非常的簡單,首先使用 NavUtils.getParentActivityIntent()
方法,獲得它的父 Activity,然後使用 NavUtils.shouldUpRecreateTask()
方法,確定當前 Activity 是否需要一個 Task ,如果需要,使用 TaskStackBuilder 來操作,如果不需要就直接呼叫 NavUtils.navigateUpTo()
方法來繼續接下去的邏輯。
可以看到這一套邏輯,非常的簡單,如果按照文件的描述,使用起來應該體驗挺不錯的,但是它有坑,後面講。
三、NavUtils 的原始碼
先來看看 NavUtilsImplBase 這個 Api Level 16 以下的 Api 實現,它因為沒有一些高版本的 Api,從原始碼上能看出跟多細節。
簡單關注一下它的細節,shouldUpRecreateTask()
方法,實際上是通過校驗 Action 是否等於 ACTION_MAIN 來確定的,而 navigateUpTo()
只是為 upIntent 新增了 FLAG_ACTIVITY_CLEAR_TOP 這個 flag ,然後啟動父 Activity 並且關閉自己。而 getParentActivityIntent()
的程式碼,其實核心還是在 NavUtils.getParentActivityName()
,最終可以看到,它和我們配置的一樣,是從 meta-data 中獲取的資料。
再來看看 NavTilsImplJB 這個 Api Level 16 上的實現。
可以看到,它其實很多邏輯都放在 NavUtilsJB 這個類中。
而它實際上很多方法都是直接呼叫的 Activity 中對應的方法,有興趣可以去看看 Activity 的原始碼中的實現。
四、填坑和最終實現
到這裡,基本上就已經瞭解了 NavUtils 的實現原理了,看樣子用起來應該沒那麼多問題,但是如果實際使用起來,你就會發現有坑了。
1、shouldUpRecreateTask() 永遠返回的是 false。
實際上,這並不是 shouldUpRecreateTask()
方法在實現上有什麼 Bug,它實際上是給 Notification 使用的,在 Notification 使用 PendingIntent 的時候,使用 TaskStackBuilder 來構建它,其構造一個 Back Task ,在這裡就可以使用 shouldUpRecreateTask()
方法來做判斷了。
但是大多數情況下,我們並不只是在 Notification 中使用它,並且有一些推送的訊息,這個 Notification 並非我們去構造的,而是由第三方 SDK 來構建的,這就導致這種情況並不符合大多數場景。
下面是官方提供的一個 Demo。
有興趣可以移步到官方文件檢視:
developer.android.com/guide/topic…
所以我們可以使用 Activity.isTaskRoot()
來做輔助判斷,它是 Activity 的方法,可以判斷當前 Activity 是否是在當前 Task 的根 Activity,這樣就說明再回退的話,實際上就會將當前 Task 完整的清空,表現就是退出去了。
2、navigateUpTo() 會重新啟動MainActivity
navigateUpTo() 方法從原始碼上可以看出來,它實際上是強加了一個 FLAG_ACTIVITY_CLEAR_TOP ,然後重新啟動它,這樣的話,在某些裝置上,預設是有動畫處理的,因為這裡是開啟了一個新的頁面,而非 finish() 之後,自動回退到上一個頁面的操作。
那麼解決方案也非常的簡單,在判斷當前 Activity 不需要使用 TaskStackBuilder 構造一個 Task Stack ,就直接 finish()
掉當前的頁面,因為這樣的判斷說明當前 Activity 是在頁面內正常開啟的,所以直接 finish()
就可以退回到上一個頁面了。
最終改動之後的實現效果就變成了這樣,AndroidManifest.xml 中的配置不變。