原文出處:http://www.ccbu.cc/index.php/android/fragment-lifecycle.html
什麼是Fragment
Fragment實在是Android 3.0(API 11)中引入的,譯作“碎片”。Fragment作爲應用用戶接口或行爲的一部分而放置在Activity中。Fragment不能獨立存在,只能依賴與Activity存在。Fragment擁有自己的生命週期,其生命週期受宿主Activity的控制,狀態會隨着Activity的狀態的改變而發生改變。
因Android各個版本的Fragment有所差異,同時爲了兼容低版本,support-v4庫中提供了一套兼容Fragment API,最低兼容Android 1.6。
過去support-v4庫是一個jar包,24.2.0版本開始,將support-v4庫模塊化爲多個jar包,包含:support-fragment, support-ui, support-media-compat等,這麼做是爲了減少APK包大小,你需要用哪個模塊就引入哪個模塊。
Fragment的特點
Fragment是爲了解決碎片態的用戶界面而產生的,在處理UI方面有者自己獨特的優勢。Fragment擁有自己的生命週期,可以處理用戶輸入事件。一個Activity中可以擁有多個Fragment,一個Fragment可以被多個Activity重用。在Activity中可以動態的添加,刪除,和管理Fragment。
基於上面所述的優勢,可以看出,Fragment擁有以下以下特點:
- 獨立性 Fragment擁有獨立的生命週期,可以獨立處理所有的用戶交互事件
- 模塊化 將某一功能封裝到一個Fragment中供Activity使用
- 複用性 一個Fragment可以被多個Activity複用
- 靈活性 Fragment不但可以按照特定功能進行單獨封裝,還可以在Activity中進行動態的添加,刪除和管理。
Fragment生命週期
上面多次提到Fragment擁有自己的生命週期,那他的生命週期是怎麼樣的呢,先看一下經典的Fragment生命週期圖。
其中,各個生命週期函數的說明如下:
- onAttach():Fragment和Activity相關聯時調用。可以通過該方法獲取 Activity引用,還可以通過getArguments()獲取參數。
- onCreate():系統在Fragment被創建時調用。
- onCreateView():創建Fragment的佈局,如果片段未提供 UI,您可以返回 null。
- onActivityCreated():當Activity完成onCreate()時調用。
- onStart():當Fragment可見時調用。
- onResume():當Fragment可見且可交互時調用。
- onPause():當Fragment不可交互但可見時調用。
- onStop():當Fragment不可見時調用。
- onDestroyView():當Fragment的UI從視圖結構中移除時調用。
- onDestroy():銷燬Fragment時調用。
- onDetach():當Fragment和Activity解除關聯時調用。
Fragment的是依於Activity而存在的,從他的生命週期圖可以看出,Fragment的生命週期與Activity的生命週期是十分相似的。下圖展示了fragment的生命週期與Activity生命週期函數的對應關係。
Fragment的使用
下面通過Fragment的使用實例瞭解在實際應用中如何來使用Fragment構建UI,同時通過這些使用實例,進一步的理解其生命週期。
1. 創建一個新的Fragment
創建一個自己的Fragment,只需要繼承系統的Fragment或support-v4庫中的Fragment類,或者是繼承Fragment的子類,並實現需要的方法即可。其中一個默認的不帶參數的構造函數,onCreateView()函數是必須的。onCreate,onResume,onPause()等方法也是十分常用的。
public class BlankFragment extends Fragment {
public BlankFragment() {
// Required empty public constructor
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_blank, container, false);
}
}
Fragment作爲Activity用戶界面的一部分,支持View操作是其最基本的功能;其中onCreateView用來加載Fragment的佈局,返回值即爲加載進來的View。一般onCreateView通過加載XML佈局文件來加載自己的佈局,onCreateView提供了加載佈局的LayoutInflater,專門供使用者來加載佈局。
Fragment還有幾個常用的子類,可以幫助我們來更方便的實現想要的特定功能的Fragment。包括以下幾個。
- DialogFragment 顯示浮動對話框。使用此類創建對話框可有效地替代使用 Activity 類中的對話框幫助程序方法,因爲您可以將片段對話框納入由 Activity 管理的片段返回棧,從而使用戶能夠返回清除的片段。
- ListFragment 顯示由適配器(如 SimpleCursorAdapter)管理的一系列項目,類似於 ListActivity。它提供了幾種管理列表視圖的方法,如用於處理點擊事件的 onListItemClick() 回調。
- PreferenceFragment 以列表形式顯示 Preference 對象的層次結構,類似於 PreferenceActivity。這在爲您的應用創建“設置” Activity 時很有用處。
2.靜態加載Fragment
靜態加載Fragment即是將Fragment像其他普通View一樣,直接寫在layout的xml文件中。View加載時會自動進行加載。
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<fragment
android:name="cc.ccbu.canvassimple.BlankFragment"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</RelativeLayout>
在佈局文件中使用fragment標籤進行Fragment的添加,並指明android:name
屬性制定對應的Fragment類。當系統創建此 Activity 佈局時,會實例化在佈局中指定的每個片段,併爲每個片段調用 onCreateView()
方法,以檢索每個片段的佈局。系統會直接插入片段返回的 View
來替代 <fragment>
元素。
每個片段都需要一個唯一的標識符,重啓 Activity 時,系統可以使用該標識符來恢復片段(您也可以使用該標識符來捕獲片段以執行某些事務,如將其移除)。 可以通過三種方式爲片段提供 ID:
1.爲 android:id 屬性提供唯一 ID。
2.爲 android:tag 屬性提供唯一字符串。
3.如果您未給以上兩個屬性提供值,系統會使用容器視圖的 ID。
3.動態加載fragment
靜態方式加載Fragment的方式還是顯得不夠靈活,所以在 Activity 中我們還可以可以根據需要來執行Fragment的添加、移除、替換以及其他操作。 提交給 Activity 的每組更改都稱爲事務,我們可以通過使用 FragmentTransaction
的 API 來執行一項事務。每次可以將每個事務保存到由 Activity 管理的返回棧內(addToBackStack),從事使能夠回退片到之前保持的狀態。
Fragment newFragment = new BlankFragment();
FragmentTransaction transaction = getFragmentManager().beginTransaction();
transaction.replace(R.id.fragment_container, newFragment);
transaction.addToBackStack(null);
transaction.commit();
上例中,newFragment
會替換在 R.id.fragment_container
ID 所標識的佈局容器中的內容;通過調用 addToBackStack() 可將事務保存到返回棧,當用戶按返回鍵時,會執行撤銷事務,如果返回棧有之前保存的事務項,則以出棧的形式回退到上一個事務項狀態。
如果向FragmentTransaction添加了多個更改(如add()
或 remove()
),並且調用了 addToBackStack()
,則在調用commit()
前應用的所有更改都將作爲單一事務添加到返回棧,並且返回按鈕會將它們一併撤消。
對於每個片段事務,您都可以通過在提交前調用 setTransition()
來應用過渡動畫。
4.添加菜單項
Fragment可以通過實現onCreateOptionsMenu()項Activity的OptionsMenu中添加菜單項。不過需要注意的是,必須在 onCreate() 期間調用 setHasOptionsMenu(),Fragment的onCreateOptionsMenu方法纔會被調用。在Fragment中添加的所有菜單項都會被追加到現有菜單項之後。當菜單項被選中時,Fragment會收到對應的 onOptionsItemSelected() 回調。
儘管您Fragment會收到其添加的每個菜單項對應的菜單項回調,但當用戶選中菜單項時,Activity 會首先收到相應的回調。 如果 Activity 對菜單項回調的實現不會處理該菜單項,則系統會將事件傳遞到Fragment的回調。
Fragment通信
1.給Fragment設置參數
如果在創建Fragment時要傳入參數,必須要通過setArguments(Bundle bundle)方式添加,而不建議通過爲Fragment添加帶參數的構造函數,因爲通過setArguments()方式添加,在由於內存緊張導致Fragment被系統殺掉並恢復(re-instantiate)時能保留這些數據。使用setArguments,在創建Fragment的時候傳遞參數,然後在fragment的onCreate方法處獲取參數,但是需要注意的是setArguments()方法必須在fragment創建以後,add之前調用。
public static TestFragment newInstance(String str){
Bundle bundle = new Bundle();
bundle.putString("info", str);
TestFragment fragment = new TestFragment();
fragment.setArguments(bundle);
return fragment;
}
2.獲取實例
Fragment可以通過getActivity方法來獲取Activity的實例,從而執行Activity相關的UI操作。但使用getActivity前需要注意Fragment的生命週期與Activity生命週期的對應關係,在部分生命週期函數內調用getActivity是不會返回有效的實例,獲取到的是null值。
同樣的,Activity中也可以通過調用 findFragmentById() 或 findFragmentByTag()方法,從 FragmentManager 獲取Fragment 引用,進而來訪問Fragment相應的方法。
3.使用回調方法
有的時候,我們可以通過使用回調方法來實現Activity與Fragment的通信操作。
public class BlankFragment extends Fragment {
...
private OnItemClickListener mItemClickListener = null;
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
try {
mItemClickListener = (OnItemClickListener)activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString() + " must implement OnItemClickListener");
}
}
public interface OnItemClickListener {
void onItemClick(int itemId);
}
}
public class MainActivity extends Activity implements BlankFragment.OnItemClickListener {
...
@Override
public void onItemClick(int itemId) {
Log.d(TAG, "onItemClick : " + itemId);
}
}
上面的例子中,BlankFragment定義了OnItemClickListener接口,宿主Activity必須要實現該接口,在BlankFragment的onAttach方法中,通過強制類型轉換,將Activity參數轉爲OnItemClickListener接口,如果宿主Activity沒有實現該接口,會拋出ClassCastException異常。
Fragment懶加載
懶加載主要用於ViewPager加載Fragment頁面的情況,且ViewPager的每個子頁面都是一個Fragment。默認情況下,ViewPager會執行預加載來提前加載一些頁面來使得UI左右滑動效果更加流暢。ViewPager可以通過setOffscreenPageLimit(int limit)
設置預加載頁面數量,但有一個最小限制,保證知識加載兩個或三個頁面。在一些場景下,當ViewPager中的頁面不可見時,我們不希望他來加載數據時,那麼此時我們就需要通過懶加載方式來加載數據。懶加載的方式庫提供應用的初始化速度,同時也可以避免不必要的資源加載。
那Fragment的懶加載該如何實現呢,這裏首先來認識一個方法setUserVisibleHint(boolean isVisibleToUser)
。該方法中的isVisibleToUser
參數用來表示當前Fragment是否對用戶可見。當Fragment對用戶可見或不可見時,該方法都會被調用。所以我們可以依據該方法,在Fragment對用戶可見時在去加載數據。但需要注意的一點是,setUserVisibleHint(boolean isVisibleToUser)
方法會多次回調,而且可能會在onCreateView()
方法執行完畢之前回調。所以,在加載數據前,必須滿足兩個條件:
setUserVisibleHint(boolean isVisibleToUser)
參數爲true。onCreateView()
方法已經執行- 數據還未被加載
我們可以把這些操作封裝到一個基類裏。
public abstract class LazyLoadFragment extends Fragment {
private boolean isUserVisible = false;
private boolean isViewCreated = false;
private boolean isDataLoaded = false;
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
isUserVisible = isVisibleToUser;
onLazyLoad();
}
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
isViewCreated = true;
onLazyLoad();
}
public void onLazyLoad() {
if (!isDataLoaded && isUserVisible && isViewCreated) {
loadData();
}
}
public abstract boolean loadData();
}
在子類中只用實現loadData()方法來加載數據即可。