前言
Activity/Fragment/View 系列文章:
Android Activity 與View 的互動思考
Android Activity 生命周期詳解及監(jiān)聽
Android onSaveInstanceState/onRestoreInstanceState 原來要這么理解
Android Fragment 要你何用?
Android Activity/View/Window/Dialog/Fragment 深層次關(guān)聯(lián)(白話解析)
關(guān)于Fragment 的分析網(wǎng)上已經(jīng)有許多優(yōu)秀文章流傳,有些人覺得它的生命周期比較復(fù)雜,不好控制,實屬雞肋。有些人認(rèn)為它封裝得比較好,屬于"輕量級的Activity",值得在工程里引入。
通過本篇文章,你將了解到:
1、為什么需要Fragment
2、添加Fragment的方式
3、Activity 與Fragment 生命周期的聯(lián)動
4、常見的控制Fragment 方法
1、為什么需要Fragment
Fragment 定義
Fragment 翻譯為中文:碎片、片段。
最早在Android 3.0時引入的,為了應(yīng)用能夠適配大屏幕的設(shè)備而提供的一種靈活的UI 組件。
Fragment 與View、Activity 關(guān)系
和View 對比

如上圖,App 需要適配手機與平板。
因為有公用的界面,因此盡可能地想復(fù)用公用部分,這個時候我們想到了View。將View 抽取出來作為一個公共UI組件,分別放在手機和平板對應(yīng)的布局文件里。若是View里包含了比較多的邏輯,以后就不好復(fù)用這個組件了。并且View 本身并沒有生命周期,想要跟隨Activity的生命周期,只能靠Activity 傳遞過來或是主動監(jiān)聽Activity 生命周期變化,比較麻煩。
剛好,F(xiàn)ragment 能夠滿足此種需求。
和View 相比,F(xiàn)ragment 有如下特點:
1、擁有生命周期。
2、將View(UI)與邏輯 封裝在Fragment里。
3、其它Activity 可以復(fù)用Fragment(UI + 邏輯)。
和Activity 對比
我們常說Activity "重",View "輕",這很容易理解,試想一下:啟動一個Activity 遠(yuǎn)比展示一個View 慢很多。
為什么呢?
因為Activity 是受到AMS 管控的,Activity 的生命周期都是由AMS 跨進(jìn)程通知到App進(jìn)程,這顯然耗費了不少時間。再加上Activity 啟動時初始化了許多東西,比如Window、DecorView等,因此從啟動Activity 到完全展示它需要一定的時間。
而View 則不同,僅僅只需要new 一個對象,并設(shè)置一些屬性,最后添加到上層的ViewGroup里進(jìn)行展示即可,都是在本進(jìn)程內(nèi)操作,速度很快。
和View 類似,當(dāng)向Activity 里添加Fragment 時,實際上主要做了兩件事:
1、加入到Fragment棧里,方便管理。
2、將Fragment 所持有的View 添加到ViewTree 某個節(jié)點里。
可以看出,以上兩步?jīng)]有涉及進(jìn)程間通信,也沒有初始化許多的組件,因此啟動一個Fragment 比啟動一個Activity 快得多。
三者關(guān)系
用圖說明三者的聯(lián)系:

上圖僅僅表示類比關(guān)系,F(xiàn)ragment并不是Activity 子類也不是View/ViewGroup 父類或子類。
2、添加Fragment的方式
靜態(tài)添加方式
了解了Fragment特點,看看如何使用它。與View 類似,View 可以放在xml里作為靜態(tài)加載,也可以通過代碼動態(tài)加載。
先說靜態(tài)加載。
編寫靜態(tài)布局文件:activity_static_fragment.xml
<?xml version="1.0" encoding="utf-8"?>
<com.example.androiddemo.fragment.MyFrameLayout 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">
<fragment
android:id="@+id/static_fragment"
android:name="com.example.androiddemo.fragment.MyFragment"
android:layout_width="match_parent"
android:layout_height="100dp"
tools:ignore="Instantiatable">
</fragment>
</com.example.androiddemo.fragment.MyFrameLayout>
其中android:name 指定Fragment的全限定類名稱。
MyFragment 是自定義的Fragment。
然后在Activity onCreate里加載此布局文件:
setContentView(R.layout.activity_static_fragment);
可以看出,靜態(tài)添加fragment 與靜態(tài)添加View 很相似,接著分析其添加的原理。
靜態(tài)添加原理
1、整體流程
上節(jié)有提到過,F(xiàn)ragment 會將布局文件管理起來并添加到ViewTree里。在聲明自定義Fragment時需要重寫onCreateView(xx)方法:
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState) {
TextView textView = new TextView(getContext());
if (TextUtils.isEmpty(desc))
desc = "靜態(tài)fragment";
textView.setText(desc);
textView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
return textView;
}
該方法可以理解為Fragment指定其承載的布局,此處構(gòu)造一個TextView 對象,并返回。
串起來靜態(tài)加載的流程為:
1、Activity 通過LayoutInflater 加載布局文件。
2、LayoutInflater 尋找"fragment"標(biāo)簽。
3、找到"fragment"標(biāo)簽,根據(jù)"android:name"指定的Fragment類的全限定名稱反射實例化Fragment。
4、拿到Fragment實例后,調(diào)用onCreateView(xx),將返回的View對象與Fragment進(jìn)行關(guān)聯(lián)。
5、View 對象被添加到"fragment"標(biāo)簽的父布局里。在該例里是MyFrameLayout。
6、至此,F(xiàn)ragment關(guān)聯(lián)的View 已經(jīng)被添加到ViewTree里。

注:Fragment并不是Activity/View 的子類,
2、View 添加到ViewTree
核心代碼:
#FragmentManagerImpl.java
void ensureInflatedFragmentView(Fragment f) {
if (f.mFromLayout && !f.mPerformedCreateView) {
//最終執(zhí)行到Fragment.onCreateView()
//返回的View 對象賦值給f.mView
f.performCreateView(f.performGetLayoutInflater(
f.mSavedFragmentState), null, f.mSavedFragmentState);
if (f.mView != null) {
...
//調(diào)用onViewCreated(xx)
f.onViewCreated(f.mView, f.mSavedFragmentState);
} else {
f.mInnerView = null;
}
}
}
Fragment 將關(guān)聯(lián)的View 存儲在f.mView里,LayoutInflater 加載時將f.mView add 到上層的ViewGroup里,最終f.mView 掛接到ViewTree里。
動態(tài)添加方式
private void addFragment(Fragment fragment) {
// list.add(fragment);
//獲取Fragment管理對象
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction transaction = fragmentManager.beginTransaction(); // 開啟一個事務(wù)
//添加fragment
transaction.add(R.id.container, fragment);
//提交動作
transaction.commit();
}
封裝一個addFragment(xx)方法,傳入構(gòu)造好的Fragment。
然后在Activity的onCreate(xx)里調(diào)用此方法
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_fragment);
//直接new 出來
addFragment(new MyFragment("fragment1"));
}
動態(tài)添加原理
1、獲取Fragment 控制器
先看getSupportFragmentManager():
#FragmentActivity.java
public FragmentManager getSupportFragmentManager() {
return mFragments.getSupportFragmentManager();
}
而mFragments 是FragmentActivity.java 的成員變量:
#FragmentActivity.java
final FragmentController mFragments = FragmentController.createController(new HostCallbacks());
最終返回的是FragmentManagerImpl 實例。
從這里可以看出,每個直接或間接地繼承自FragmentActivity.java 的類都會擁有FragmentController 成員變量,從該變量里獲取FragmentManagerImpl 實例就可以控制Fragment的一切活動。
2、Fragment 關(guān)聯(lián) View
接著看:
transaction.add(R.id.container, fragment);
第一個參數(shù)表示要將Fragment掛接到的ViewGroup,第二個參數(shù)表示待掛接的Fragment 對象。
而當(dāng)Fragment.onCreateView(xx)被調(diào)用時,返回的View 將會被add到ViewGroup里,也就是R.id.container代表的ViewGroup。
而R.id.container 是Activity 布局文件里某個布局的id。
至此:
Fragment 所關(guān)聯(lián)的View 被添加到ViewTree里。
3、View 添加到ViewTree
核心代碼:
#FragmentManagerImpl.java
void moveToState(Fragment f, int newState, int transit, int transitionStyle,
boolean keepActive) {
...
//最終執(zhí)行Fragment.onCreateView(xx)
//返回的View 賦值給f.mView,f表示Fragment 對象
f.performCreateView(f.performGetLayoutInflater(
f.mSavedFragmentState), container, f.mSavedFragmentState);
if (f.mView != null) {
f.mInnerView = f.mView;
f.mView.setSaveFromParentEnabled(false);
//container 是ViewGroup
//transaction.replace(R.id.container, fragment) 方法里的 R.id.container 實例化得來的
if (container != null) {
//將Fragment.onCreateView(xx)得到的View 添加到container里,也就是添加到了ViewTree里。
container.addView(f.mView);
}
...
} else {
f.mInnerView = null;
}
...
}
需要注意的是,在onCreateView(xx)里咱們是動態(tài)生成了View,若是通過LayoutInflater加載布局文件:
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
//最后參數(shù)為false
View view = inflater.inflate(R.layout.fragment_layout, container,false);
return view;
}
那么最后一個參數(shù)必須為false,意思是不將生成的View add到container里,因為在FragmentManagerImpl.moveToState(xx)里也會執(zhí)行container.addView(view),若是最后參數(shù)為true,那么就會報重復(fù)添加的錯誤。
3、Activity 與Fragment 生命周期的聯(lián)動
通過對靜態(tài)添加與動態(tài)添加的分析,我們已經(jīng)弄清楚了Fragment 關(guān)聯(lián)的View 如何添加到ViewTree里。接著再來分析靜態(tài)添加與動態(tài)添加時Fragment 生命周期的流轉(zhuǎn)。
Fragment 的生命周期的由來
Fragment 依賴于Activity,因此我們想當(dāng)然地認(rèn)為它的生命周期依賴于Activity,事實究竟如何呢?為尋求真相,從源碼入手。
以Activity.onResume()為例,探究與Fragment.onResume()關(guān)系。
當(dāng)調(diào)用Activity.performResume(xx)時,有如下源碼:
#Activity.java
final void performResume(boolean followedByPause, String reason) {
...
onPostResume();
...
}
#FragmentActivity.java
protected void onPostResume() {
super.onPostResume();
onResumeFragments();
}
#FragmentActivity.java
protected void onResumeFragments() {
mFragmentLifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_RESUME);
//mFragments 最終控制著Fragment 生命周期
mFragments.dispatchResume();
}
可以看出,因為自定義的Activity 直接/間接地繼承自FragmentActivity,而FragmentActivity 重寫了很多Activity 方法,因此每當(dāng)調(diào)用Activity.xx()方法時都會調(diào)用到FragmentActivity重寫的對應(yīng)方法,而重寫的方法里會通過mFragments(FragmentController 控制器)最終控制Fragment各個生命周期回調(diào)方法。
總而言之:
1、Fragment 生命周期依賴于Activity 生命周期。
2、Activity 生命周期變更回調(diào)的方法onCreate/onStart/onResume/onPause/onStop/onDestroy,F(xiàn)ragment都有,F(xiàn)ragment 比Activity 還多一些回調(diào)方法,比如onAttach/onCreateView 等。
靜態(tài)添加Fragment 生命周期
以圖示之:

由圖可知:
Activity 生命周期變動就會調(diào)用Fragment對應(yīng)方法,因此Fragment 也間接擁有了生命周期。
明顯地可以看出,F(xiàn)ragment 生命周期涉及到的方法比Activity 更多。
簡單解釋涉及的各個方法的用處:
1、onAttach
Fragment 第一次綁定Context。
當(dāng)使用Fragment.getContext()/Fragment.getActivity()返回的是綁定的FragmentActivity。
2、onCreate
類似Activity onCreate。
3、onCreateView
關(guān)聯(lián)Fragment 與UI,F(xiàn)ragment的展示效果即是通過該UI表現(xiàn)的。
4、onViewCreated
執(zhí)行到這一步,說明第三步創(chuàng)建的View 已經(jīng)被添加到ViewTree里。
5、onActivityCreated
表示Activity 與Fragment 完全綁定了。
onDestroyView、onDetach 等是反向操作,不再細(xì)說。
動態(tài)添加Fragment 生命周期
理論上來說不管靜態(tài)添加還是動態(tài)添加,生命周期都是一樣的,為什么要區(qū)分呢?
我們之前說的動態(tài)添加方式,有個方法重點關(guān)注:
transaction.commit();
該方法有個孿生兄弟方法:
transaction.commitNow()
顧名思義,transaction.commitNow() 表示立即添加,生命周期與靜態(tài)添加一致。
而transaction.commit() 是加入到隊列里,延遲執(zhí)行,此時生命周期如下:

因為延遲執(zhí)行,并沒有在Activity.onCreate(xx)時進(jìn)行Fragment.onAttach()等一些列操作,而是在Activity.onStart()之后。
除了這點區(qū)別,其它都一樣。
4、常見的控制Fragment 方法
主要是依賴FragmentTransaction 來控制Fragment。
1、FragmentTransaction.hide(xx)
隱藏Fragment,本質(zhì)上是將Fragment關(guān)聯(lián)的View進(jìn)行隱藏:View.setVisibility(GONE)
不會回調(diào)Fragment 生命周期中的方法。
2、FragmentTransaction.show(xx)
顯示Fragment,本質(zhì)上是將Fragment關(guān)聯(lián)的View進(jìn)行展示:View.setVisibility(VISIBLE)
不會回調(diào)Fragment 生命周期中的方法。
3、FragmentTransaction.detach(xx)
將Fragment 從Activity 中移除,實際上是將Fragment 關(guān)聯(lián)的View 從ViewTree中移除。Fragment還在棧里。
生命周期變化如下:

4、FragmentTransaction.remove(xx)
除了將Fragment 從Activity 中移除,還將Fragment從回退棧里移除。
生命周期變化如下:

5、FragmentTransaction.replace(xx)
效果同 remove + add。
當(dāng)前展示fragment1,通過FragmentTransaction.replace(fragment2),
生命周期變動如下:

6、 數(shù)據(jù)傳遞
Activity 向Fragment傳遞數(shù)據(jù),實際上就是傳遞Bundle。
Fragment.setArguments(Bundle)。
在Fragment里通過:
Fragment.getArguments()獲取。
當(dāng)然,引入Jetpack可通過ViewModel共享數(shù)據(jù)。
最后附上Demo 效果圖

本文基于Android 10.0
您若喜歡,請點贊、關(guān)注,您的鼓勵是我前進(jìn)的動力
持續(xù)更新中,和我一起步步為營系統(tǒng)、深入學(xué)習(xí)Android
1、Android各種Context的前世今生
2、Android DecorView 必知必會
3、Window/WindowManager 不可不知之事
4、View Measure/Layout/Draw 真明白了
5、Android事件分發(fā)全套服務(wù)
6、Android invalidate/postInvalidate/requestLayout 徹底厘清
7、Android Window 如何確定大小/onMeasure()多次執(zhí)行原因
8、Android事件驅(qū)動Handler-Message-Looper解析
9、Android 鍵盤一招搞定
10、Android 各種坐標(biāo)徹底明了
11、Android Activity/Window/View 的background
12、Android Activity創(chuàng)建到View的顯示過
13、Android IPC 系列
14、Android 存儲系列
15、Java 并發(fā)系列不再疑惑
16、Java 線程池系列
17、Android Jetpack 前置基礎(chǔ)系列