Android4.0-Fragment框架實現方式剖析

經過反覆的學習對比,個人覺得帶着問題學習新知是最有效的學習方式,因此文本就以提問的方式來講述Fragment框架實現方式。

1、什麼是Fragment?

Fragment包含在Activity中,Fragment只能存在於Activity的上下文(context)內,沒有Activity就無法使用Fragment,因此Fragment只能在Activity的上下文(context)創建。Fragment可以作爲Activity的一部分,Fragment和Activity非常相似,Fragment擁有一個與她相關的視圖層次結構,擁有一個與活動非常相似的生命週期。

2、爲什麼要使用Fragment?

Activity的使用侷限:不能將多個Activity活動界面放在屏幕上一併顯示。因此創建了Fragment來彌補Activity的侷限。Fragment可以像Activity一樣響應Back鍵等類似Activity的功能。

3、實現Fragment的時候,爲什麼要有一個默認的構造函數?

談到這兒,就不得不說一下Fragment的結構。Fragment的結構包括:視圖層次結構、初始化參數的包

首先來看一下Fragment和Activity的繼承關係,如圖1-1,1-2所示:


圖1-1 Fragment類繼承關係                                                                                                             圖1-2 Activity類繼承關係

從圖中很容易看出Fragment是直接從Object繼承的,而Activity是Context的子類。因此我們可以得出結論:Fragment不是Activity的擴展。但是與Activity一樣,在我們使用Fragment的時候我們總會擴展Fragment(或者是她的子類),並可以通過子類更改她的行爲。

Fragment可以擁有一個與用戶交互的視圖層次結構,該視圖層次結構和Activity的視圖層次結構一樣,也可以通過XML佈局規範創建(擴充)或者代碼創建。(備註:如果視圖層次結構需要向用戶顯示,則必須將Fragment的視圖層次結構附加到Activity的試圖層次結構中)

前面介紹了那麼多,只是爲了拋磚引玉,接下來進入正題,爲什麼Fragment必須包含一個默認的構造函數(在Java類中,如果沒有構造函數,在編譯的時候會自動創建一個不帶參數的默認構造函數。因此如果Java類中沒有其他的構造函數,可以將默認函數省略,編譯器會自動創建;如果Java類中有其他構造函數時,在編譯時系統不會創建默認的構造函數,因此在有非默認構造函數時,又需要在編譯時出現默認構造函數,就必須在Java類中顯示的寫出默認構造函數)初始化參數的包——類似於活動,碎片可由系統自動保存並在以後還原。當系統還原Fragment時,她調用默認的構造函數,然後將參數包還原到新建的Fragment。該Fragment執行的後續回調能夠訪問這些參數,可以將碎片還原到上一個狀態。因此在使用Fragment時,一定要確保以下兩點:

  1. 確保Fragment類存在默認的構造函數;

  2. 在Fragment創建後立即添加一個參數包(Bundle),使Fragment重建時可以正確設置Fragment,也使Android系統可以在必要時正確還原Fragment。

小結:Fragment的子類必須具有默認的構造函數和一個參數包,因爲Fragment在重新創建的時候會調用默認的構造函數,而且會在重新創建時將狀態保存到一個包(Bundle)對象(備註:注意區分對象包和前面所說的參數包)中,這個包(Bundle)對象會被回送到該Fragment的onCreate()回調。這個保存的包(Bundle)也會傳遞到onInflate()\onCreateView()\onActivityCreated()。(備註:這不是作爲初始化參數而附加的包。可能在這個包中存儲Fragment的當前狀態,而不是應該用於初始化她的值)

4、Fragment的生命週期是怎樣與Activity的生命週期整合的?

在使用Fragment之前,一定要了解Fragment的生命週期。Fragment的生命週期相比Activity的生命週期要更爲複雜,理解何時處理Fragment至關重要。由於Fragment是依賴於Activity的,接下來看一下兩者的生命週期有什麼異同,Fragment與Activity生命週期如圖1-3、1-4所示:

235131313.png235147211.png

圖1-3 Activity運行時Fragment生命週期                                                                                   圖1-4 Activity生命週期

通過對Fragment和Activity對比,將會發現許多不同之處,主要原因是因爲Activity和Fragment之間需要交互。 相信大家對Activity的生命週期已經非常熟悉,在此就不做過多介紹,直接切入正題介紹Fragment的生命週期。在Fragment開始階段,Fragment會以對象的形式存在於內存中。創建Fragment實例有如下兩種情況:

  1. 創建Fragment實例;

  2. 系統從保存狀態重新創建Fragment的情況下,將初始化參數添加到碎片對象中。當系統從保存的狀態還原Fragment時,會調用默認的構造函數,然後附件初始化參數包;

使用代碼創建Fragment的實例:

  1. publicstatic MyFragment newInstance(int index){  

  2.    MyFragment mf = new MyFragment();  

  3.    Bundle args = new Bundle();  

  4.    args.putInt("index",index);  

  5.    mf.setArguments(args);  

  6. return mf;  

  7. }  

1.onInflate()回調

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>標記定義的(通常是在活動調用setContentView()來設置自己的主要佈局),Fragment將調用自己的onInflate()回調。這一過程中傳入一個AttributeSet(包含來自<fragment>標記的特性)和一個保存的包。如果重新創建碎片,並且之前在onSaveInstanceState()中保存了某種狀態,此包(Bundle)將包含保存的狀態值。onInflate()預計開發人員會讀取特性值並保存她們供以後使用。在Fragment的onInflate()這一生命階段,對用戶界面執行任何操作都尚早,因爲Fragment甚至還沒有與其Activity關聯。

(備註:onInflate()文檔與實際使用不符,文檔表明onInflate()始終在onAttach()之前調用。實際上,在Activity重新啓動後,onInflate()可能在onCreateView()之後調用。這對於將值設置到包(Bundle)中並調用setArguments()而言太遲了,Android官方文檔中Fragment生命週期的圖示中也沒有將onInflate()包含在生命週期,看來Android的大牛們也很難預測onInflate()會在何時回調)

2.onAttach()回調

好了,討論了那麼糾結的問題,相比大家都被我繞暈了吧,但是追求技術的道路中是不能有半點怠慢的,透徹分析問題纔是我們追求技術的使命。如果大家都頭有點暈的話,建議大家先看部喜劇片吧,清醒一下頭腦!如果還能堅持的,就跟隨我的思路繼續探尋。onAttach()回調,回頭一看,MyGod,該方法終於出現在Fragment生命週期的圖解中了,總算是引領大家步入正軌了。

API文檔:public void onAttach(Activity activity) --Called when a fragment is first attached to its activity. onCreate(Bundle)will be called after this.

onAttach()回調將在Fragment與其Activity關聯之後調用。需要使用Activity的引用或者使用Activity作爲其他操作的上下文,將在此回調方法中實現。

注意:Fragment類有一個getActivity()方法,返回與Fragment關聯的Activity。在Fragment的整個生命週期中,初始化參數包(Bundle)可以從碎片的getArguments()方法獲得。

切忌:將Fragment附加到Activity以後,就無法再次調用setArguments()——除了在最開始,無法向初始化參數添加內容。

3.onCreate()回調

接下來回調的方法就是onCreate(),大家可以不要與Activity的onCreate()回調方法混淆了。此回調獲取傳入的參數包(備註:如果在創建時設置了參數包(Bundle)的話就可以獲得),不應該將需要依賴於Activity視圖層次結構的存在性的代碼放在此回調方法中,儘管Fragment現在可能已經與其Activity關聯,但是我們還沒有獲得Activity的onCreate()已完成的通知,所以不能將依賴於Activity視圖層次結構存在性的代碼放入此回調方法中。

(備註:在onCreate()回調方法中,我們應該儘量避免耗時操作(避免阻塞UI線程),在實際項目中觸發後臺線程進行準備非常有用,阻塞調用應該位於後臺線程中。)

示例代碼:

  1. /**

  2.     * During creation, if arguments have been supplied to the fragment

  3.     * then parse those out.

  4.     */

  5. @Overridepublicvoid onCreate(Bundle savedInstanceState) {  

  6. super.onCreate(savedInstanceState);  

  7.        Bundle args = getArguments();  

  8. if (args != null) {  

  9.            mLabel = args.getCharSequence("label", mLabel);  

  10.        }  

  11.    }  

4.onCreateView()回調

接下來的回調方法是onCreateView(),在該回調方法中應該返回該Fragment的一個視圖層次結構。

API文檔:public ViewonCreateView(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爲狀態包(備註:必須和前面所說的參數包(Bundle)區分開來)注意:不要將視圖層次結構附加到傳入的ViewGroup父元素中,該關聯會自動完成。如果在此回調中將碎片的視圖層次結構附加到父元素,很可能會出現異常。實例代碼如下所示:


  1. /**

  2.    * Create the view for this fragment, using the arguments given to it.

  3.    */

  4. @Overridepublic View onCreateView(LayoutInflater inflater, ViewGroup container,  

  5.           Bundle savedInstanceState) {  

  6.       View v = inflater.inflate(R.layout.hello_world, container, false);// 不能將Fragment的視圖附加到此回調的容器元素,因此attachToRoot參數必須爲false

  7.       View tv = v.findViewById(R.id.text);  

  8.       ((TextView)tv).setText(mLabel != null ? mLabel : "(no label)");  

  9.       tv.setBackgroundDrawable(getResources().getDrawable(android.R.drawable.gallery_thumb));  

  10. return v;  

  11.   }  

5.onActivityCreated()回調

終於到了與用戶交互的時刻了,onActivityCreated()回調會在Activity完成其onCreate()回調之後調用。在調用onActivityCreated()之前,Activity的視圖層次結構已經準備好了,這是在用戶看到用戶界面之前你可對用戶界面執行的最後調整的地方。(備註:如果Activity和她的Fragment是從保存的狀態重新創建的,此回調尤其重要,也可以在這裏確保此Activity的其他所有Fragment已經附加到該活動中了)

6. Fragment與Activity相同生命週期調用

接下來的onStart()\onResume()\onPause()\onStop()回調方法將和Activity的回調方法進行綁定,也就是說與Activity中對應的生命週期相同,因此不做過多介紹。

7.onDestroyView()回調

該回調方法在視圖層次結構與Fragment分離之後調用。

8.onDestroy()回調
不再使用Fragment時調用。(備註:Fragment仍然附加到Activity並任然可以找到,但是不能執行其他操作)
9.onDetach()回調
Fragme生命週期最後回調函數,調用後,Fragment不再與Activity綁定,釋放資源
通過以上說明,縱觀Fragment生命週期和Activity生命週期整合後如圖1-5所示:
235220825.png235234235.png
                圖1-5 Activity和Fragment生命週期整合                                                                                        圖1-6 Fragment生命週期
10.巧妙使用setRetainInstance()

爲什麼會在這兒花一定的篇幅詳細說明setRetainInstance()方法呢?因爲此方法可以有效地提高系統的運行效率,對流暢性要求較高的應用可以適當採用此方法進行設置。

Fragment有一個非常強大的功能——就是可以在Activity重新創建時可以不完全銷燬Fragment,以便Fragment可以恢復。在onCreate()方法中調用setRetainInstance(true/false)方法是最佳位置。當Fragment恢復時的生命週期如圖1-6所示,注意圖中的紅色箭頭。當在onCreate()方法中調用了setRetainInstance(true)後,Fragment恢復時會跳過onCreate()和onDestroy()方法,因此不能在onCreate()中放置一些初始化邏輯,切忌!

5、怎樣管理Fragment?

既然Fragment必須存在Activity的上下文(context)內,那麼怎樣管理Fragment是我們接下來需要關心的話題。FragmentManager組件負責管理屬於一個活動的碎片(包括後退棧上的碎片和空閒的碎片)。可以在Activity或附加的Fragment上使用getFragmentManager()方法來獲取碎片管理器。代碼如下所示:

  1. FragmentManager fragmentManager = getFragmentManager()  

  2. FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();  



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章