對(duì)于Fragment的一些理解

前言
Fragment想必大家不陌生吧,在日常開發(fā)中,對(duì)于Fragment的使用也很頻繁,現(xiàn)在主流的APP中,基本的架構(gòu)也都是一個(gè)主頁,然后每個(gè)Tab項(xiàng)用Fragment做布局,不同選項(xiàng)做切換,使用起來也方便。但是否對(duì)它有足夠的認(rèn)識(shí)嗎,谷歌推薦用Fragment來代替Activity,但又沒有明確說為什么要用Fragment來代替Activity,這里就引發(fā)爭議了,那到底是要不要用,是否使用Fragment完全替換Activity真的比常規(guī)開發(fā)模式更好嗎?如果要用的話,那需要了解為何要使用Fragment,F(xiàn)ragment是什么,它的生命周期如何,如何使用,通信又是怎樣,有什么缺點(diǎn)嗎?帶著這些問題,我們一一去解讀。

目錄
Fragment為何要用
Fragment是什么
Fragment生命周期
Fragment怎么用
Fragment通信
Fragment是否很完美
小結(jié)
參考地址

Fragment為何要用

Fragment是Android 3.0 (Honeycomb)被引入的。主要目的是為了給大屏幕(如平板電腦)上更加動(dòng)態(tài)和靈活的UI設(shè)計(jì)提供支持。由于平板電腦的屏幕比手機(jī)的屏幕大很多,因此可用于組合和交換的UI組件的空間更大,利用Fragment實(shí)現(xiàn)此類設(shè)計(jì)的時(shí),就無需管理對(duì)視圖層次結(jié)構(gòu)的復(fù)雜更改。
通過將 Activity 布局分成片段,您可以在運(yùn)行時(shí)修改 Activity 的外觀,并在由 Activity 管理的返回棧中保留這些更改。如果僅僅只有Activity布局,那是不夠的,不僅在手機(jī)上有一套布局,同時(shí)在平板上還需要設(shè)計(jì)一套布局,那樣維護(hù)起來也麻煩,代碼上也有一定的冗余,對(duì)于APP包的大小也有一定的壓力。Fragment的優(yōu)勢是布局在不同設(shè)備上的適配。
比如:


v2-17c0d6588c95b45225e210b926705470_hd.jpg

從圖中我們可以看到,在平板中,一個(gè)Activity A包含了兩個(gè)Fragment,分別是Fragment A和Fragment B,但在手機(jī)中呢,就需要兩個(gè)Activity,分別是Activity A包含F(xiàn)ragment A和Activity B包含F(xiàn)ragment B。同時(shí)每個(gè)Fragment都具有自己的一套生命周期回調(diào)方法,并各自處理自己的用戶輸入事件。 因此,在平板中使用一個(gè)Activity 就可以了,左側(cè)是列表,右邊是內(nèi)容詳情。

除此之外,使用Fragment還有這么幾個(gè)方面優(yōu)勢:

代碼復(fù)用。特別適用于模塊化的開發(fā),因?yàn)橐粋€(gè)Fragment可以被多個(gè)Activity嵌套,有個(gè)共同的業(yè)務(wù)模塊就可以復(fù)用了,是模塊化UI的良好組件。
Activity用來管理Fragment。Fragment的生命周期是寄托到Activity中,F(xiàn)ragment可以被Attach添加和Detach釋放。
可控性。Fragment可以像普通對(duì)象那樣自由的創(chuàng)建和控制,傳遞參數(shù)更加容易和方便,也不用處理系統(tǒng)相關(guān)的事情,顯示方式、替換、不管是整體還是部分,都可以做到相應(yīng)的更改。
Fragments是view controllers,它們包含可測試的,解耦的業(yè)務(wù)邏輯塊,由于Fragments是構(gòu)建在views之上的,而views很容易實(shí)現(xiàn)動(dòng)畫效果,因此Fragments在屏幕切換時(shí)具有更好的控制。

Fragment是什么

說了半天的Fragment,也看到這么多次Fragment這個(gè)名詞出現(xiàn),那么Fragment到底是什么東東呢?定義又是如何?
Fragment也可以叫為“片段”,但我覺得“片段”中文叫法有點(diǎn)生硬,還是保持叫Fragment比較好,它可以表示Activity中的行為或用戶界面部分。我們可以在一個(gè)Activity中用多個(gè)Fragment組合來構(gòu)建多窗格的UI,以及在多個(gè)Activity中重復(fù)使用某個(gè)Fragment。它有自己的生命周期,能接受自己的輸入,并且可以在 Activity 運(yùn)行時(shí)添加或刪除Fragment(有點(diǎn)像在不同 Activity 中重復(fù)使用的“子 Activity”)。
簡單來說,F(xiàn)ragment其實(shí)可以理解為一個(gè)具有自己生命周期的控件,只不過這個(gè)控件又有點(diǎn)特殊,它有自己的處理輸入事件的能力,有自己的生命周期,又必須依賴于Activity,能互相通信和托管。

Fragment生命周期

v2-cd6a61dcbb7c37f7c8661654a1061784_r.jpg

這張圖是Fragment生命周期和Activity生命周期對(duì)比圖,可以看到兩者還是有很多相似的地方,比如都有onCreate(),onStart(),onPause(),onDestroy()等等,因?yàn)镕ragment是被托管到Activity中的,所以多了兩個(gè)onAttach()和onDetach()。這里講講與Activity生命周期不一樣的方法。
onAttach()
Fragment和Activity建立關(guān)聯(lián)的時(shí)候調(diào)用,被附加到Activity中去。
onCreate()
系統(tǒng)會(huì)在創(chuàng)建Fragment時(shí)調(diào)用此方法。可以初始化一段資源文件等等。
onCreateView()
系統(tǒng)會(huì)在Fragment首次繪制其用戶界面時(shí)調(diào)用此方法。 要想為Fragment繪制 UI,從該方法中返回的 View 必須是Fragment布局的根視圖。如果Fragment未提供 UI,您可以返回 null。
onViewCreated()
在Fragment被繪制后,調(diào)用此方法,可以初始化控件資源。
onActivityCreated()
當(dāng)onCreate(),onCreateView(),onViewCreated()方法執(zhí)行完后調(diào)用,也就是Activity被渲染繪制出來后。
onPause()
系統(tǒng)將此方法作為用戶離開Fragment的第一個(gè)信號(hào)(但并不總是意味著此Fragment會(huì)被銷毀)進(jìn)行調(diào)用。 通??梢栽诖朔椒▋?nèi)確認(rèn)在當(dāng)前用戶會(huì)話結(jié)束后仍然有效的任何更改(因?yàn)橛脩艨赡懿粫?huì)返回)。
onDestroyView()
Fragment中的布局被移除時(shí)調(diào)用。
onDetach()
Fragment和Activity解除關(guān)聯(lián)的時(shí)候調(diào)用。

但需要注一點(diǎn)是:除了onCreateView,其他的所有方法如果你重寫了,必須調(diào)用父類對(duì)于該方法的實(shí)現(xiàn)。
還有一般在啟動(dòng)Fragment的時(shí)候,它的生命周期就會(huì)執(zhí)行這幾個(gè)方法。


v2-b70578b43ca487bd51e754765652a67c_r.jpg

Fragment怎么用

public class MainActivity extends Activity {  
  
    @Override  
    protected void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.activity_main);  
        Display display = getWindowManager().getDefaultDisplay();  
        if (display.getWidth() > display.getHeight()) {  
            Fragment1 fragment1 = new Fragment1();  
            getFragmentManager().beginTransaction().replace(R.id.main_layout, fragment1).commit();  
        } else {  
            Fragment2 fragment2 = new Fragment2();  
            getFragmentManager().beginTransaction().replace(R.id.main_layout, fragment2).commit();  
        }  
    }  
  
} 

看到了沒,首先,我們要獲取屏幕的寬度和高度,然后進(jìn)行判斷,如果屏幕寬度大于高度就添加fragment1,如果高度大于寬度就添加fragment2。動(dòng)態(tài)添加Fragment主要分為4步:
1.獲取到FragmentManager,在Activity中可以直接通過getFragmentManager得到。
2.開啟一個(gè)事務(wù),通過調(diào)用beginTransaction方法開啟。
3.向容器內(nèi)加入Fragment,一般使用replace方法實(shí)現(xiàn),需要傳入容器的id和Fragment的實(shí)例。
4.提交事務(wù),調(diào)用commit方法提交。

要想管理 Activity 中的片段,需要使用 FragmentManager。要想獲取它,需要 Activity 調(diào)用 getFragmentManager()。
使用 FragmentManager 執(zhí)行的操作包括:

通過 findFragmentById()(對(duì)于在 Activity 布局中提供 UI 的片段)或 findFragmentByTag()(對(duì)于提供或不提供 UI 的片段)獲取 Activity 中存在的片段
通過 popBackStack()將片段從返回棧中彈出
通過 addOnBackStackChangedListener() 注冊一個(gè)偵聽返回棧變化的偵聽器
也可以使用 FragmentManager 打開一個(gè) FragmentTransaction,通過它來執(zhí)行某些事務(wù),如添加和刪除片段。
Fragment通信
盡管 Fragment 是作為獨(dú)立于 Activity的對(duì)象實(shí)現(xiàn),并且可在多個(gè) Activity 內(nèi)使用,但Fragment 的給定實(shí)例會(huì)直接綁定到包含它的 Activity。具體地說,F(xiàn)ragment 可以通過 getActivity() 訪問 Activity實(shí)例,并輕松地執(zhí)行在 Activity 布局中查找視圖等任務(wù)。

Fragment是否很完美
因?yàn)镕ragment是由FragmentManager來管理,每一個(gè)Activity有一個(gè)FragmentManager,管理著一個(gè)Fragment的棧,Activity是系統(tǒng)級(jí)別的,由系統(tǒng)來管理ActivityManager,棧也是系統(tǒng)范圍的。而Fragment則是每個(gè)Activity范圍內(nèi)的,所以在使用Fragment的時(shí)候也有幾點(diǎn)要注意。

同一個(gè)Activity中,只能有一個(gè)ID或TAG標(biāo)識(shí)的Fragment實(shí)例。
這很容易理解,同一個(gè)范圍內(nèi),有標(biāo)識(shí)的實(shí)例肯定是要唯一才行(否則還要標(biāo)識(shí)干嘛)這個(gè)在布局中經(jīng)常犯錯(cuò),在布局中寫Fragment最好不要加ID或者TAG,否則很容易出現(xiàn)不允許創(chuàng)建的錯(cuò)誤。我的原則是如果放在布局中,就不要加ID和TAG,如果需要ID和TAG就全用代碼控制。創(chuàng)建新實(shí)例前先到FragmentManager中查找一番,這也正是有標(biāo)識(shí)的意義所在。
一個(gè)Activity中有一個(gè)Fragment池,實(shí)例不一定會(huì)被銷毀,可能會(huì)保存在池中。
這個(gè)跟第一點(diǎn)差不多。就好比系統(tǒng)會(huì)緩存Activity的實(shí)例一樣,F(xiàn)ragmentManager也會(huì)緩存Fragment實(shí)例,以方便和加速再次顯示。
FragmentManager的作用范圍是整個(gè)Activity,所以,某一個(gè)布局ID,不能重復(fù)被Fragment替換。
通常顯示Fragment有二種方式,一種是層疊到某個(gè)布局上,或者把某個(gè)布局上面的Fragment替換掉,但是這個(gè)布局不能出現(xiàn)二次,比如布局A中有ID為id的區(qū)域,要顯示為Fragment,此布局A,只能在一個(gè)Activity中顯示一個(gè),否則第二個(gè)id區(qū)域不能被Fragment成功替換。因?yàn)殡m有二個(gè)ID布局的實(shí)例,但I(xiàn)D是相同的,對(duì)FragmentManager來說是一樣的,它會(huì)認(rèn)為只有一個(gè),因?yàn)樗吹氖遣季值腎D,而不是布局的實(shí)例。
Fragment的生命周期反應(yīng)Activity的生命周期。
Fragment在顯示和退出時(shí)會(huì)走一遍完整的生命周期。此外,正在顯示時(shí),就跟Activity的一樣,Activity被onPause,里面的Fragment就onPause,以此類推,由此帶來的問題就是,比如你在onStart()里面做了一些事情,那么,當(dāng)宿主Activity被擋住,又出現(xiàn)時(shí)(比如接了個(gè)電話),F(xiàn)ragment的onStart也會(huì)被高到,所以你要想到,這些生命周期不單單在顯示和退出時(shí)會(huì)走到。
Fragment的可見性。
這個(gè)問題出現(xiàn)在有Fragment棧的時(shí)候,也就是說每個(gè)Fragment不知道自己是否真的對(duì)用戶可見。比如現(xiàn)在是Fragment A,又在其上面顯示了Fragment B,當(dāng)B顯示后,A并不知道自己上面還有一個(gè),也不知道自己對(duì)用戶不可見了,同樣再有一個(gè)C,B也不知。C退出后,B依然不知自己已在棧頂,對(duì)用戶可見,B退后,A也不知。也就是說Fragment顯示或者退出,棧里的其他Fragment無法感知。這點(diǎn)就不如Activity,a被b蓋住后,a會(huì)走到onStop(),同樣c顯示后,b也能通過onStop()感知。Fragment可以從FragmentManager監(jiān)聽BackStackState的變化,但它只告訴你Stack變了,不告訴你是多了,還是少,還有你處的位置。有一個(gè)解決方案就是,記錄頁面的Path深度,再跟Fragment所在的Stack深度來比較,如果一致,那么這個(gè)Fragment就在棧頂。因?yàn)槊總€(gè)頁面的Path深度是固定的,而Stack深度是不變化的,所以這個(gè)能準(zhǔn)確的判斷Fragment是否對(duì)用戶可見,當(dāng)然,這個(gè)僅針對(duì)整個(gè)頁面有效,對(duì)于布局中的一個(gè)區(qū)域是無效的。
Fragment的事件傳遞。
對(duì)于層疊的Fragment,其實(shí)就相當(dāng)于在一個(gè)FrameLayout里面加上一堆的View,所以,如果處于頂層的Fragment沒處理點(diǎn)擊事件,那么事件就會(huì)向下層傳遞,直到事件被處理。比如有二個(gè)Fragment A和B,B在A上面,B只有TextView且沒處理事件,那么點(diǎn)擊B時(shí),會(huì)發(fā)現(xiàn)A里的View處理了事件。這個(gè)對(duì)于Activity也不會(huì)發(fā)生,因?yàn)槭录荒芸绱绑w傳播,上面的Activity沒處理事件,也不會(huì)傳給下面的Activity,即使它可見。解決之法,就是讓上面的Fragment的根布局吃掉事件,為每個(gè)根ViewGroup添加onClick=“true”。
與第三方Activity交互。與第三方交互,仍要采用Android的標(biāo)準(zhǔn)startActivityForResult()和onActivityResult()這二個(gè)方法來進(jìn)行。但對(duì)于Fragment有些事情需要注意,F(xiàn)ragment也有這二個(gè)方法,但是為了能正確的讓Fragment收到onActivityResult(),需要:
宿主Activity要實(shí)現(xiàn)一個(gè)空的onActivityResult(),里面調(diào)用super.onActivityResult()
調(diào)用Fragment#startActivityForResult()而不是用Activity的 當(dāng)然,也可以直接使用Activity的startActivityForResult(),那樣的話,就只能在宿主Activity里處理返回的結(jié)果了。

小結(jié)
在用法的代碼部分參考郭神的博客,感覺郭神在代碼講解部分通俗易懂,看起來也方便??傊?,在使用Fragment也有一些注意事項(xiàng),不是那么完美的,雖然谷歌推薦我們用Fragment來代替Activity來使用,我們也確實(shí)這做了,現(xiàn)在基本主流的APP也都是少量Activity+很多Fragment,但也需要避免有些坑慎入。

參考地址

https://developer.android.com/guide/components/fragments.html

http://blog.csdn.net/guolin_blog/article/details/8881711

http://toughcoder.net/blog/2014/10/22/effective-android-ui-architecture

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容