使用 Router 實現(xiàn)的模塊化,如何優(yōu)雅的回到主頁面
一、前言
現(xiàn)在越來越多的 App 以 Router 路由的形式,來實現(xiàn)模塊化。一般而言,這種 Router 的方案,從外部直接調(diào)起的方式,是由一個 ProxyActivity 做一個代理,然后再由它去跳轉(zhuǎn)到項目內(nèi)的其他目標 TargetActivity 。這樣的實現(xiàn),理論上,是可以從外部調(diào)起 App 內(nèi)所有的 Activity 的。
但是這樣就面臨一個問題,如果從外部調(diào)起了一個子頁的 Activity,舉例是一個影片詳情頁,如果想在用戶回退的時候,進入到應用主頁,而非直接退出了。就需要特殊處理了。
本文就以如何優(yōu)化的回退到我們需要的 Activity 來做一個說明。
二、分析和拆解問題
對于前面舉例的情況來說,實際上我們只需要處理好以下問題即可。
- 如何區(qū)分當前 Activity 是從應用內(nèi)打開,還是從應用外直接打開,就是打開 Activity 的來源。
- 在區(qū)分出來 Activity 打開的來源之后,如何優(yōu)雅的退回到我們需要的 Activity。
- 要處理一些特殊情況。
對于這樣幾個問題,區(qū)分來源,最粗暴的方式,就是在 Intent 里增加一個類似 from 的字段,來標記是從那個頁面過來的,如果不是我們指定的話,在 finish() 或者 onBackPressed() 的時候,就打開 MainActivity 即可。
這是一個粗暴的方式,雖然它有用,可它不夠優(yōu)雅。
而在 Support v4 22.0.0 開始,針對這樣的情況,為我們增加了一個 NavUtils 的類,專門用于處理這種情況,接下來就來看看如何使用它。
三、NavUtils 如何使用
NavUtils 從名稱上就可以看出來,它是一個用于處理導航的輔助工具類。
先來看看它的方法,它主要的方法主要分三中:
- getParentActivityIntent():獲得一個用戶回退到父Activity 的Intent。
- shouldUpRecreateTask():是否需要重新構(gòu)建一個 Task。
- navigateUpTo():回退到 Intent 指定的父 Activity。
為了方便使用 getParentActivityIntent() 提供了很多的重載,但是實際上目的都是一樣的,就是拿到我們指定的當前 Activity 的上一級 Activity(父 Activity )。
既然是 Support v4 包下的輔助類,它其實在內(nèi)部也是做好了很多兼容的處理。它針對不同的 Android Level 做了不同的實現(xiàn),可以看到 NavUtilsImplBase 和 NavUtilsImplJB 都是為了處理兼容性的問題,他們都實現(xiàn)了 NavUtilsImpl 接口,而且在靜態(tài)代碼塊內(nèi)處理好了兼容性問題。
好了,源碼就先聊到這里,先來看看如何使用。
如果想要使用 NavUtils 還需要在 AndroidManifest.xml 中,為 Activity 指定一個父的 Activity。
這里有個 Demo ,有兩個 Activity,分別是 MainActivity 和 ChildActivity 。我們需要對 ChildActivity 設定父 Activity。
為 Activity 設定父 Activity,是需要區(qū)分版本的,在 4.1 之后,是可以使用 android:parentActivityName 直接指定即可。而對于 4.0 及一下的版本,如果想要使用,可以配置 meta-data 標簽,name 必須是 android.support.PARENT_ACTIVITY 而 value 就是用來指定父 Activity 的。
先來看看官方推薦的使用示例。
它的流程非常的簡單,首先使用 NavUtils.getParentActivityIntent() 方法,獲得它的父 Activity,然后使用 NavUtils.shouldUpRecreateTask() 方法,確定當前 Activity 是否需要一個 Task ,如果需要,使用 TaskStackBuilder 來操作,如果不需要就直接調(diào)用 NavUtils.navigateUpTo() 方法來繼續(xù)接下去的邏輯。
可以看到這一套邏輯,非常的簡單,如果按照文檔的描述,使用起來應該體驗挺不錯的,但是它有坑,后面講。
三、NavUtils 的源碼
先來看看 NavUtilsImplBase 這個 Api Level 16 以下的 Api 實現(xiàn),它因為沒有一些高版本的 Api,從源碼上能看出跟多細節(jié)。
簡單關(guān)注一下它的細節(jié),shouldUpRecreateTask() 方法,實際上是通過校驗 Action 是否等于ACTION_MAIN 來確定的,而 navigateUpTo() 只是為 upIntent 添加了FLAG_ACTIVITY_CLEAR_TOP 這個 flag ,然后啟動父 Activity 并且關(guān)閉自己。而 getParentActivityIntent() 的代碼,其實核心還是在 NavUtils.getParentActivityName(),最終可以看到,它和我們配置的一樣,是從 meta-data 中獲取的數(shù)據(jù)。
再來看看 NavTilsImplJB 這個 Api Level 16 上的實現(xiàn)。
可以看到,它其實很多邏輯都放在 NavUtilsJB 這個類中。
而它實際上很多方法都是直接調(diào)用的 Activity 中對應的方法,有興趣可以去看看 Activity 的源碼中的實現(xiàn)。
四、填坑和最終實現(xiàn)
到這里,基本上就已經(jīng)了解了 NavUtils 的實現(xiàn)原理了,看樣子用起來應該沒那么多問題,但是如果實際使用起來,你就會發(fā)現(xiàn)有坑了。
1、shouldUpRecreateTask() 永遠返回的是 false。
實際上,這并不是 shouldUpRecreateTask() 方法在實現(xiàn)上有什么 Bug,它實際上是給Notification 使用的,在 Notification 使用 PendingIntent 的時候,使用 TaskStackBuilder來構(gòu)建它,其構(gòu)造一個 Back Task ,在這里就可以使用 shouldUpRecreateTask() 方法來做判斷了。
但是大多數(shù)情況下,我們并不只是在 Notification 中使用它,并且有一些推送的消息,這個Notification 并非我們?nèi)?gòu)造的,而是由第三方 SDK 來構(gòu)建的,這就導致這種情況并不符合大多數(shù)場景。
下面是官方提供的一個 Demo。
有興趣可以移步到官方文檔查看:
https://developer.android.com/guide/topics/ui/notifiers/notifications.html#NotificationResponse
所以我們可以使用 Activity.isTaskRoot() 來做輔助判斷,它是 Activity 的方法,可以判斷當前 Activity 是否是在當前 Task 的根 Activity,這樣就說明再回退的話,實際上就會將當前 Task 完整的清空,表現(xiàn)就是退出去了。
2、navigateUpTo() 會重新啟動MainActivity
navigateUpTo() 方法從源碼上可以看出來,它實際上是強加了一個FLAG_ACTIVITY_CLEAR_TOP ,然后重新啟動它,這樣的話,在某些設備上,默認是有動畫處理的,因為這里是打開了一個新的頁面,而非 finish() 之后,自動回退到上一個頁面的操作。
那么解決方案也非常的簡單,在判斷當前 Activity 不需要使用 TaskStackBuilder 構(gòu)造一個 Task Stack ,就直接 finish() 掉當前的頁面,因為這樣的判斷說明當前 Activity 是在頁面內(nèi)正常打開的,所以直接 finish() 就可以退回到上一個頁面了。
最終改動之后的實現(xiàn)效果就變成了這樣,AndroidManifest.xml 中的配置不變。
【本文為51CTO專欄作者“張旸”的原創(chuàng)稿件,轉(zhuǎn)載請通過微信公眾號聯(lián)系作者獲取授權(quán)】