Android Fragment 要你何用?

前言

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 對比

image.png

如上圖,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)系:


image.png

上圖僅僅表示類比關(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里。

image.png

注: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 生命周期

以圖示之:


image.png

由圖可知:

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í)行,此時生命周期如下:


image.png

因為延遲執(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還在棧里。

生命周期變化如下:


image.png

4、FragmentTransaction.remove(xx)

除了將Fragment 從Activity 中移除,還將Fragment從回退棧里移除。

生命周期變化如下:


image.png

5、FragmentTransaction.replace(xx)

效果同 remove + add。

當(dāng)前展示fragment1,通過FragmentTransaction.replace(fragment2),
生命周期變動如下:


image.png

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

最后附上Demo 效果圖


fragment.gif

Fragment 添加/刪除/替換/隱藏 測試代碼

本文基于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ǔ)系列

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • ![Flask](data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAW...
    極客學(xué)院Wiki閱讀 7,762評論 0 3
  • 不知不覺易趣客已經(jīng)在路上走了快一年了,感覺也該讓更多朋友認(rèn)識知道易趣客,所以就謝了這篇簡介,已做創(chuàng)業(yè)記事。 易趣客...
    Physher閱讀 3,787評論 1 2
  • 雙胎妊娠有家族遺傳傾向,隨母系遺傳。有研究表明,如果孕婦本人是雙胎之一,她生雙胎的機率為1/58;若孕婦的父親或母...
    鄴水芙蓉hibiscus閱讀 3,865評論 0 2
  • 今天理好了行李,看到快要九點了,就很匆忙的洗頭洗澡,(心存一份念想,你總會打給我的??)然后把洗頭液當(dāng)成沐浴液了??,...
    bevil閱讀 2,897評論 1 1
  • 那年我們15,像陽光一樣溫暖的年紀(jì)。每天我都會騎自行車上學(xué),路過田野,工廠,醫(yī)院,村莊,有微風(fēng),有陽光,有綠...
    木偶說愛你閱讀 2,580評論 0 3

友情鏈接更多精彩內(nèi)容