1. 程式人生 > >Android4.0-Fragment框架實現方式剖析(一)

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所示:

                        

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

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

  1. 建立Fragment例項;
  2. 系統從儲存狀態重新建立Fragment的情況下,將初始化引數新增到碎片物件中。當系統從儲存的狀態還原Fragment時,會呼叫預設的建構函式,然後附件初始化引數包;

使用程式碼建立Fragment的例項:

public static MyFragment newInstance(int index){
    MyFragment mf = new MyFragment();
    Bundle args = new Bundle();
    args.putInt("index",index);
    mf.setArguments(args);
    return mf;
}
1.onInflate()回撥

(Activityactivity,AttributeSetattrs,BundlesavedInstanceState)--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 atag in a layout file. Note this isbeforethe fragment'shas 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 voidonAttach(Activityactivity) --Called when a fragment is first attached to its activity.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執行緒),在實際專案中觸發後臺執行緒進行準備非常有用,阻塞呼叫應該位於後臺執行緒中。)

示例程式碼:

/**
     * During creation, if arguments have been supplied to the fragment
     * then parse those out.
     */
    @Override public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        Bundle args = getArguments();
        if (args != null) {
            mLabel = args.getCharSequence("label", mLabel);
        }
    }
4.onCreateView()回撥

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

API文件:publicViewonCreateView(LayoutInflaterinflater,ViewGroupcontainer,BundlesavedInstanceState)--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 betweenand.

 其中的Bundle為狀態包(備註:必須和前面所說的引數包(Bundle)區分開來)注意:不要將檢視層次結構附加到傳入的ViewGroup父元素中,該關聯會自動完成。如果在此回撥中將碎片的檢視層次結構附加到父元素,很可能會出現異常。例項程式碼如下所示:

 /**
     * Create the view for this fragment, using the arguments given to it.
     */
    @Override public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        View v = inflater.inflate(R.layout.hello_world, container, false);// 不能將Fragment的檢視附加到此回撥的容器元素,因此attachToRoot引數必須為false 
        View tv = v.findViewById(R.id.text);
        ((TextView)tv).setText(mLabel != null ? mLabel : "(no label)");
        tv.setBackgroundDrawable(getResources().getDrawable(android.R.drawable.gallery_thumb));
        return v;
    }
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所示:                                             圖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()方法來獲取碎片管理器。程式碼如下所示:

FragmentManager fragmentManager = getFragmentManager()
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();