Fragment中的那些坑——Android進階

lotus_ruan發表於2021-09-09

Fragment是Android在3.0(Homeycomb)版本時加入的用以更靈活的構建多螢幕介面的可UI元件。關於Fragment以基本使用方法可以參考官方的  和  ,以及 。 但是Fragment使用起來卻遠沒有教程中說的那樣簡單,也遠比Activity要複雜一些,這裡總結了孤在使用Fragment時所遇到的坑。

圖片描述

巢狀Fragment時Duplicated id或者Tag之癢

這是一個小坑,但是初學者很容易遇到,特別是在Fragment之中套有Fragment時,且又是佈局中新增子Fragment時更容易遇到。

現象:

Fragment中套有另一個Fragment,當第二次進入父Fragment時或者由Fragment建立的介面時會拋異常,大致意思是子Fragment的Id或Tag重複了。如果你在layout中給子fragment加了id或者tag,那麼一定會遇到此異常。

原因:

在新增Fragment時都可以為Fragment指定一個Id或者Tag用以標識這個Fragment。因為每個Activity所附帶的Fragment都是放在一個物件池中,在Activity的生命週期裡,Fragment仍然在池中,即使是把某一個Fragment從Activity中detach掉(也即用FragmentManager pop掉),這個池是由FragmentManager來管理的。當你再次要以某個id或者Tag新增Fragment時,FragmentManager會在池中檢索,如果發現已經存在Fragment物件帶有此Id或者Tag時,就會拋此異常並報怨Id重複。這麼做的目的就是減少物件的建立,儘可以的複用物件。

如何破解:

  1. . 在佈局中寫fragment時,不要新增id或者tag;

  2. . 如果非要新增id或者tag,就在程式碼中新增fragment,如使用Id或者Tag時,先到FragmentManager中查詢物件是否存在,不存在時再建立,也即:

      Fragment target = getFragmentManager().findFragmentByTag("tag");  if (target == null) {    targe = new SomeFragment();
      }
      FragmentTransaction ft = getFragmentManager().beginTransaction();
      ft.add(R.id.content, target, "tag");
      ft.commit();

replace之痛

現象:

當有二個相同的整體頁面層疊時,想把最後一個佈局中的某個用Fragment來replace,會發現,它把前面的replace,後面的沒效果。

原因:

佈局的Id在一個窗體(Activity)中是唯一的,Fragment的replace也是使用此唯一的Id來把相應佈局替換成Fragment的。當相同的頁面層疊時,同一個Id的佈局出現了二次,但Id是一樣的。所以FragmentTransaction在replace時僅替換了一個。而不會像期待的那樣,替換最後一個頁面。

如何破解:

如果相同的頁面非要層疊,要麼不使用Fragment,要麼為佈局設定不同的Id。這種情況多出現在佈局的複用上面,比如某二個頁面長的像,所以複用了同一整體佈局。但實際的邏輯上不是相同的頁面,完全可以為佈局設定不同的Id。

可見性之疼

現象:

當有多個Fragment層疊在一起時,每個Fragment如何能感知其對使用者的可見性。比如應用有三個頁面,A,B和C,比如A是整體類別列表,B是每個類別的詳情,C又是類別的某種更詳細的資訊,當C顯示出來時,A和B怎麼能知道它其實對於使用者已經不可見了,所以就可以不重新整理,不載入資料等等。當C被使用者BACK後,B又如何感覺它變成可見了?

原因:

Fragment的生命週期與Activity是一樣的,新增到Activity會把OnCreate類似的回撥走一遍,然後,Activity onResume/onPause/onstart/onStop時,其所持有的Fragment也走相應的onResume/onPause/onstart/onPause。但是Fragment與Activity非常不同的是,Activity當有另一個Activity顯示時,當前的Activity會走onPause/onStop,而Fragment則完全沒有感知。最多隻能從FragmentManager那裡知道BackStackState改變了,但是是Fragment增加了,還是減少了,並不能知道。

如何破解:

這個一個非常令人蛋疼的問題,簡單的頁面還好,但是涉及到資料載入或者要針對某些事件(網路)重新整理時就有問題了,對使用者不可見的頁面沒必要重新整理。可行的解法就是:

  1. . 監聽FragmentManager的BackStackState的改變

  2. . 定義頁面路徑深度然後與BackStack深度比較,以感知是否對使用者可見 如前面A是一級,其path為1,B是2,C是3。當前Stack深度為3時,C是可見的,A與B不可見,以此類推。

空白區域的點選之膿

現象:

一個Fragment,層疊在另外一個Fragment或者Activity之上,此Fragment中有一些空白區域,也即Widget之外的空白區域,當點選這些空白區域的時候發現這個Fragment下面的Fragment或者Activity中的View收到了事件並且響應了點選事件。

原因:

Fragment的本質就是一個View佈局的管理器,當Fragment attach到Activity時,其實就是把Fragment#onCreateView()返回的View,替換掉(如果是用replace)FragmentTransaction#replace中指定的View,或者新增到(如果是add)FragmentTransaction#add()中指定的ViewGroup裡面。

當我們以層疊方式顯示多個Fragment時,通常的做法就是弄一個FrameLayout,然後每次把Fragment add到此佈局。因此,這時Activity的頁面佈局樹實際上就是一個FrameLayout裡面包含幾個View。

所以,當點選上面Fragment的空白區域時,如果事件沒被吃掉,就會向下傳遞。

如何破解:

在Fragment的根佈局加上一個clickable=true,這會讓根佈局把點選事件吃掉,以防止事件會繼續傳遞下去,造成上面的情況。

Activity重新建立之殤

現象:

這個沒有一般性的錯誤,只會有與專案相關的具體的錯誤異常,或者頁面顯示不正確。以及為什麼教程中都有這麼一句:

@OverrideonCreate(Bundle savedInstance) {   if (savedIntance == null) {      // create fragment and add it to Activity.   }}

原因:

Activity除了正常啟動走到onCreate,還有另外的入口,比如系統配置資訊發生變化時,或者Activity在棧比較深的地方,系統會把Activity殺掉,然後再 重新建立 它,問題就是在這個重新建立。重新建立與新建一個Activity不同,它是要儘可能的恢復先前所在的狀態,因為這對使用者來說是透明的,也就是說不能讓使用者感知到,否則體驗會相當差。唯一與常規建立的區別就在於傳給onCreate的引數savedInstanceState是不是null.

如何破解:

為了能在Activity重建時恢復狀態,需要:

  1. . 對於Activity

    要在onSaveInstanceState()時,把一些變數儲存,然後在onCreate時恢復

  2. . 對於Fragment

    告訴系統,你想恢復狀態Fragment#setRetainInstance(true)。然後,也在onSavedInstance()中儲存狀態,在onCreate時恢復。 這就夠了,系統會在重新建立Activity時把其所持有的Fragment也建立出來。所以為什麼每個Fragment子類都需要定義一個預設的Constructor。更多的可以參考  。

FragmentTransaction的非同步操作之殤

FragmentTransaction是非同步的,commit()僅是相當於把操作加入到FragmentManager的佇列,然後FragmentManager會在某一個時刻來執行,並不是立即執行。所以,真正開始執行commit()時,如果Activity的生命週期發生了變化,比如走到了onPause,或者走到了onStop,或者onDestroy都走完了,那麼就會報出IllegalStateException。

原文連結:http://www.apkbus.com/blog-24118-59411.html

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/1762/viewspace-2816596/,如需轉載,請註明出處,否則將追究法律責任。

相關文章