經(jīng)過反復(fù)的學(xué)習(xí)對(duì)比,個(gè)人覺得帶著問題學(xué)習(xí)新知是最有效的學(xué)習(xí)方式,因此文本就以提問的方式來講述Fragment框架實(shí)現(xiàn)方式。
Fragment包含在Activity中,F(xiàn)ragment只能存在于Activity的上下文(context)內(nèi),沒有Activity就無法使用Fragment,因此Fragment只能在Activity的上下文(context)創(chuàng)建。Fragment可以作為Activity的一部分,F(xiàn)ragment和Activity非常相似,F(xiàn)ragment擁有一個(gè)與她相關(guān)的視圖層次結(jié)構(gòu),擁有一個(gè)與活動(dòng)非常相似的生命周期。
Activity的使用局限:不能將多個(gè)Activity活動(dòng)界面放在屏幕上一并顯示。因此創(chuàng)建了Fragment來彌補(bǔ)Activity的局限。Fragment可以像Activity一樣響應(yīng)Back鍵等類似Activity的功能。
談到這兒,就不得不說一下Fragment的結(jié)構(gòu)。Fragment的結(jié)構(gòu)包括:視圖層次結(jié)構(gòu)、初始化參數(shù)的包
首先來看一下Fragment和Activity的繼承關(guān)系,如圖1-1,1-2所示:
圖1-1 Fragment類繼承關(guān)系 圖1-2 Activity類繼承關(guān)系
從圖中很容易看出Fragment是直接從Object繼承的,而Activity是Context的子類。因此我們可以得出結(jié)論:Fragment不是Activity的擴(kuò)展。但是與Activity一樣,在我們使用Fragment的時(shí)候我們總會(huì)擴(kuò)展Fragment(或者是她的子類),并可以通過子類更改她的行為。
Fragment可以擁有一個(gè)與用戶交互的視圖層次結(jié)構(gòu),該視圖層次結(jié)構(gòu)和Activity的視圖層次結(jié)構(gòu)一樣,也可以通過XML布局規(guī)范創(chuàng)建(擴(kuò)充)或者代碼創(chuàng)建。(備注:如果視圖層次結(jié)構(gòu)需要向用戶顯示,則必須將Fragment的視圖層次結(jié)構(gòu)附加到Activity的試圖層次結(jié)構(gòu)中)
前面介紹了那么多,只是為了拋磚引玉,接下來進(jìn)入正題,為什么Fragment必須包含一個(gè)默認(rèn)的構(gòu)造函數(shù)(在Java類中,如果沒有構(gòu)造函數(shù),在編譯的時(shí)候會(huì)自動(dòng)創(chuàng)建一個(gè)不帶參數(shù)的默認(rèn)構(gòu)造函數(shù)。因此如果Java類中沒有其他的構(gòu)造函數(shù),可以將默認(rèn)函數(shù)省略,編譯器會(huì)自動(dòng)創(chuàng)建;如果Java類中有其他構(gòu)造函數(shù)時(shí),在編譯時(shí)系統(tǒng)不會(huì)創(chuàng)建默認(rèn)的構(gòu)造函數(shù),因此在有非默認(rèn)構(gòu)造函數(shù)時(shí),又需要在編譯時(shí)出現(xiàn)默認(rèn)構(gòu)造函數(shù),就必須在Java類中顯示的寫出默認(rèn)構(gòu)造函數(shù))初始化參數(shù)的包——類似于活動(dòng),碎片可由系統(tǒng)自動(dòng)保存并在以后還原。當(dāng)系統(tǒng)還原Fragment時(shí),她調(diào)用默認(rèn)的構(gòu)造函數(shù),然后將參數(shù)包還原到新建的Fragment。該Fragment執(zhí)行的后續(xù)回調(diào)能夠訪問這些參數(shù),可以將碎片還原到上一個(gè)狀態(tài)。因此在使用Fragment時(shí),一定要確保以下兩點(diǎn):
小結(jié):Fragment的子類必須具有默認(rèn)的構(gòu)造函數(shù)和一個(gè)參數(shù)包,因?yàn)镕ragment在重新創(chuàng)建的時(shí)候會(huì)調(diào)用默認(rèn)的構(gòu)造函數(shù),而且會(huì)在重新創(chuàng)建時(shí)將狀態(tài)保存到一個(gè)包(Bundle)對(duì)象(備注:注意區(qū)分對(duì)象包和前面所說的參數(shù)包)中,這個(gè)包(Bundle)對(duì)象會(huì)被回送到該Fragment的onCreate()回調(diào)。這個(gè)保存的包(Bundle)也會(huì)傳遞到onInflate()\onCreateView()\onActivityCreated()。(備注:這不是作為初始化參數(shù)而附加的包??赡茉谶@個(gè)包中存儲(chǔ)Fragment的當(dāng)前狀態(tài),而不是應(yīng)該用于初始化她的值)
在使用Fragment之前,一定要了解Fragment的生命周期。Fragment的生命周期相比Activity的生命周期要更為復(fù)雜,理解何時(shí)處理Fragment至關(guān)重要。由于Fragment是依賴于Activity的,接下來看一下兩者的生命周期有什么異同,F(xiàn)ragment與Activity生命周期如圖1-3、1-4所示:
圖1-3 Activity運(yùn)行時(shí)Fragment生命周期 圖1-4 Activity生命周期
通過對(duì)Fragment和Activity對(duì)比,將會(huì)發(fā)現(xiàn)許多不同之處,主要原因是因?yàn)锳ctivity和Fragment之間需要交互。 相信大家對(duì)Activity的生命周期已經(jīng)非常熟悉,在此就不做過多介紹,直接切入正題介紹Fragment的生命周期。在Fragment開始階段,F(xiàn)ragment會(huì)以對(duì)象的形式存在于內(nèi)存中。創(chuàng)建Fragment實(shí)例有如下兩種情況:
使用代碼創(chuàng)建Fragment的實(shí)例:
API文檔:onInflate(Activity activity, AttributeSet attrs, Bundle savedInstanceState)--Called when a fragment is being created as part of a view layout inflation, typically from setting the content view of an activity.
Called when a fragment is being created as part of a view layout inflation, typically from setting the content view of an activity. This may be called immediately after the fragment is created from a tag in a layout file. Note this is before the fragment's onAttach(Activity)
has been called; all you should do here is parse the attributes and save them away.
如果Fragment是由<fragment>標(biāo)記定義的(通常是在活動(dòng)調(diào)用setContentView()來設(shè)置自己的主要布局),F(xiàn)ragment將調(diào)用自己的onInflate()回調(diào)。這一過程中傳入一個(gè)AttributeSet(包含來自<fragment>標(biāo)記的特性)和一個(gè)保存的包。如果重新創(chuàng)建碎片,并且之前在onSaveInstanceState()中保存了某種狀態(tài),此包(Bundle)將包含保存的狀態(tài)值。onInflate()預(yù)計(jì)開發(fā)人員會(huì)讀取特性值并保存她們供以后使用。在Fragment的onInflate()這一生命階段,對(duì)用戶界面執(zhí)行任何操作都尚早,因?yàn)镕ragment甚至還沒有與其Activity關(guān)聯(lián)。
(備注:onInflate()文檔與實(shí)際使用不符,文檔表明onInflate()始終在onAttach()之前調(diào)用。實(shí)際上,在Activity重新啟動(dòng)后,onInflate()可能在onCreateView()之后調(diào)用。這對(duì)于將值設(shè)置到包(Bundle)中并調(diào)用setArguments()而言太遲了,Android官方文檔中Fragment生命周期的圖示中也沒有將onInflate()包含在生命周期,看來Android的大牛們也很難預(yù)測(cè)onInflate()會(huì)在何時(shí)回調(diào))
好了,討論了那么糾結(jié)的問題,相比大家都被我繞暈了吧,但是追求技術(shù)的道路中是不能有半點(diǎn)怠慢的,透徹分析問題才是我們追求技術(shù)的使命。如果大家都頭有點(diǎn)暈的話,建議大家先看部喜劇片吧,清醒一下頭腦!如果還能堅(jiān)持的,就跟隨我的思路繼續(xù)探尋。onAttach()回調(diào),回頭一看,MyGod,該方法終于出現(xiàn)在Fragment生命周期的圖解中了,總算是引領(lǐng)大家步入正軌了。
API文檔:public void onAttach (Activity activity) --Called when a fragment is first attached to its activity. onCreate(Bundle)
will be called after this.
onAttach()回調(diào)將在Fragment與其Activity關(guān)聯(lián)之后調(diào)用。需要使用Activity的引用或者使用Activity作為其他操作的上下文,將在此回調(diào)方法中實(shí)現(xiàn)。
注意:Fragment類有一個(gè)getActivity()方法,返回與Fragment關(guān)聯(lián)的Activity。在Fragment的整個(gè)生命周期中,初始化參數(shù)包(Bundle)可以從碎片的getArguments()方法獲得。
切忌:將Fragment附加到Activity以后,就無法再次調(diào)用setArguments()——除了在最開始,無法向初始化參數(shù)添加內(nèi)容。
接下來回調(diào)的方法就是onCreate(),大家可以不要與Activity的onCreate()回調(diào)方法混淆了。此回調(diào)獲取傳入的參數(shù)包(備注:如果在創(chuàng)建時(shí)設(shè)置了參數(shù)包(Bundle)的話就可以獲得),不應(yīng)該將需要依賴于Activity視圖層次結(jié)構(gòu)的存在性的代碼放在此回調(diào)方法中,盡管Fragment現(xiàn)在可能已經(jīng)與其Activity關(guān)聯(lián),但是我們還沒有獲得Activity的onCreate()已完成的通知,所以不能將依賴于Activity視圖層次結(jié)構(gòu)存在性的代碼放入此回調(diào)方法中。
(備注:在onCreate()回調(diào)方法中,我們應(yīng)該盡量避免耗時(shí)操作(避免阻塞UI線程),在實(shí)際項(xiàng)目中觸發(fā)后臺(tái)線程進(jìn)行準(zhǔn)備非常有用,阻塞調(diào)用應(yīng)該位于后臺(tái)線程中。)
示例代碼:
接下來的回調(diào)方法是onCreateView(),在該回調(diào)方法中應(yīng)該返回該Fragment的一個(gè)視圖層次結(jié)構(gòu)。
API文檔:public View onCreateView (LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)--Called to have the fragment instantiate its user interface view. This is optional, and non-graphical fragments can return null (which is the default implementation). This will be called betweenonCreate(Bundle)
andonActivityCreated(Bundle)
.
其中的Bundle為狀態(tài)包(備注:必須和前面所說的參數(shù)包(Bundle)區(qū)分開來)注意:不要將視圖層次結(jié)構(gòu)附加到傳入的ViewGroup父元素中,該關(guān)聯(lián)會(huì)自動(dòng)完成。如果在此回調(diào)中將碎片的視圖層次結(jié)構(gòu)附加到父元素,很可能會(huì)出現(xiàn)異常。實(shí)例代碼如下所示:
終于到了與用戶交互的時(shí)刻了,onActivityCreated()回調(diào)會(huì)在Activity完成其onCreate()回調(diào)之后調(diào)用。在調(diào)用onActivityCreated()之前,Activity的視圖層次結(jié)構(gòu)已經(jīng)準(zhǔn)備好了,這是在用戶看到用戶界面之前你可對(duì)用戶界面執(zhí)行的最后調(diào)整的地方。(備注:如果Activity和她的Fragment是從保存的狀態(tài)重新創(chuàng)建的,此回調(diào)尤其重要,也可以在這里確保此Activity的其他所有Fragment已經(jīng)附加到該活動(dòng)中了)
接下來的onStart()\onResume()\onPause()\onStop()回調(diào)方法將和Activity的回調(diào)方法進(jìn)行綁定,也就是說與Activity中對(duì)應(yīng)的生命周期相同,因此不做過多介紹。
該回調(diào)方法在視圖層次結(jié)構(gòu)與Fragment分離之后調(diào)用。
為什么會(huì)在這兒花一定的篇幅詳細(xì)說明setRetainInstance()方法呢?因?yàn)榇朔椒梢杂行У靥岣呦到y(tǒng)的運(yùn)行效率,對(duì)流暢性要求較高的應(yīng)用可以適當(dāng)采用此方法進(jìn)行設(shè)置。
Fragment有一個(gè)非常強(qiáng)大的功能——就是可以在Activity重新創(chuàng)建時(shí)可以不完全銷毀Fragment,以便Fragment可以恢復(fù)。在onCreate()方法中調(diào)用setRetainInstance(true/false)方法是最佳位置。當(dāng)Fragment恢復(fù)時(shí)的生命周期如圖1-6所示,注意圖中的紅色箭頭。當(dāng)在onCreate()方法中調(diào)用了setRetainInstance(true)后,F(xiàn)ragment恢復(fù)時(shí)會(huì)跳過onCreate()和onDestroy()方法,因此不能在onCreate()中放置一些初始化邏輯,切忌!
既然Fragment必須存在Activity的上下文(context)內(nèi),那么怎樣管理Fragment是我們接下來需要關(guān)心的話題。FragmentManager組件負(fù)責(zé)管理屬于一個(gè)活動(dòng)的碎片(包括后退棧上的碎片和空閑的碎片)??梢栽贏ctivity或附加的Fragment上使用getFragmentManager()方法來獲取碎片管理器。代碼如下所示:
聯(lián)系客服