開篇廢話
公司搬遷告一段落,吐槽一句,從此就需要過上車程一個(gè)半小時(shí)的上下班之旅了。。。。。。
上一篇我們了解了四大組件之一的Activity,應(yīng)該對于Activity有了一定的了解,這一篇文章,我們一起來回顧一下我們依然比較熟悉的Fragment。
首先,我們要知道,在Android3.0以前,是沒有Fragment這個(gè)的,但為了讓大屏幕設(shè)備的UI更合理,更靈活,所以在Android3.0的時(shí)候,就出現(xiàn)了Fragment。而我們實(shí)際開發(fā)中更喜歡使用Fragment來替代之前的Activity的切換,主要是因?yàn)?,F(xiàn)ragment進(jìn)行切換更加的節(jié)省內(nèi)存,同時(shí),UI切換的時(shí)候,給用戶的體驗(yàn)會更加的舒服。

那么, 接下來,我們一起來詳細(xì)的回顧一下我們實(shí)際開發(fā)中所遇到的Fragment的相關(guān)知識。
技術(shù)詳情
本次回顧Fragment,準(zhǔn)備按如下邏輯進(jìn)行一一講述:
1. Fragment為什么被稱為第“五”大組件?
2. Fragment的生命周期都有哪些?
3. Fragment是如何通信的?
4. Fragment管理器:FragmentManager是怎么管理Fragment的?
1.Fragment為什么被稱為第五大組件?
1.Fragment被稱為第五大組件的原因
眾所周知,在我們的Android系統(tǒng)當(dāng)中,有四大組件:Activity,Service,廣播,ContentProvider。
在我們實(shí)際開發(fā)當(dāng)中,F(xiàn)ragment使用頻率,作用都是非常突出的,所以說,將Fragment列為第五大組件也是可以的。
不過也有不少人把View列為第五大組件,但是View與Fragment有一個(gè)比較明顯的不同之處,就是View是沒有生命周期的,而Fragment是有生命周期的,有了生命周期,F(xiàn)ragment就能與Activity一樣進(jìn)行更靈活的處理。
不過,我們需要注意的是,F(xiàn)ragment并不是像Activity一樣,完全獨(dú)立的,雖然擁有自己的生命周期,但是,F(xiàn)ragment必須依附于Activity,同時(shí)還要加載到Activity當(dāng)中去,接下來講講Fragment加載到Activity中的方式有哪些。
2.Fragment加載到Activity的兩種方式
首先,第一種加載方式,靜態(tài)加載
把Fragment直接在作為一個(gè)xml標(biāo)簽加載到Activity的布局文件中去。下面講一下實(shí)際開發(fā)中,我們是怎么使用靜態(tài)加載的。
首先需要有一個(gè)Activity承載Fragment的布局,這個(gè)Activity的布局如下:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<fragment
android:id="@+id/fragment_title"
android:name="senduo.com.studydemo.fragment.TitleFragment"
android:layout_width="fill_parent"
android:layout_height="50dp" />
<fragment
android:layout_below="@id/fragment_title"
android:id="@+id/fragment_content"
android:name="senduo.com.studydemo.fragment.ContentFragment"
android:layout_width="fill_parent"
android:layout_height="fill_parent" />
</RelativeLayout>
這個(gè)Activity的java文件ActivityForFragment.java如下(簡單例子,不添加任何邏輯業(yè)務(wù)):
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
public class ActivityForFragment extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_for_fragment);
}
}
有了承載的Activity之后,就可以手動(dòng)創(chuàng)建一個(gè)Fragment繼承自Fragment,我這里用一個(gè)TitleFragment和ContentFragment來舉例說明,
TitleFragment.java文件為:
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import senduo.com.studydemo.R;
public class TitleFragment extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_title, container, false);
}
}
對應(yīng)的布局文件為fragment_title.xml:
<FrameLayout 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"
android:background="@android:color/black"
tools:context="senduo.com.studydemo.fragment.TitleFragment">
<!-- TODO: Update blank fragment layout -->
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:textColor="@android:color/white"
android:text="我是標(biāo)題Fragment" />
</FrameLayout>
ContentFragment.java文件為:
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import senduo.com.studydemo.R;
public class ContentFragment extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_content, container, false);
}
}
對應(yīng)的布局文件為fragment_content.xml:
<FrameLayout 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="senduo.com.studydemo.fragment.ContentFragment">
<!-- TODO: Update blank fragment layout -->
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:text="我是界面內(nèi)容Fragment" />
</FrameLayout>
然后看一下運(yùn)行之后的效果如下:

第二種加載方式,動(dòng)態(tài)加載
我們實(shí)際開發(fā)中,用的比較多還是使用動(dòng)態(tài)加載,通過FragmentManager和FragmentTransaction來進(jìn)行動(dòng)態(tài)創(chuàng)建Fragment。
下面詳細(xì)介紹一下Fragment的動(dòng)態(tài)創(chuàng)建過程
第一步:創(chuàng)建FragmentManager的對象,然后通過這個(gè)對象創(chuàng)建一個(gè)FragmentTransaction實(shí)例對象
FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction transaction = fragmentManager.beginTransaction();
第二步:調(diào)用FragmentTransaction中的add()方法進(jìn)行添加Fragment的對象
transaction.add(R.id.fragment_content,TitleFragment);
這里需要注意的是,第一個(gè)參數(shù)傳入的不是layout而是Activity中的一個(gè)控件的ID,表示Fragment在Activity具體顯示在哪里,同時(shí)也是在這個(gè)FragmentManager隊(duì)列中的Fragment唯一標(biāo)識符,在Fragment通信過程中需要用到
第三步:調(diào)用FragmentTransaction中的commit()方法,讓以上操作進(jìn)行生效
transaction.commit();
3.FragmentPagerAdapter 和 FragmentStatePagerAdapter的區(qū)別
我們實(shí)際開發(fā)中,使用Fragment,一般都是通過viewpager(左右滑動(dòng))控件與Fragment結(jié)合進(jìn)行使用,F(xiàn)ragment用來控制滑動(dòng)時(shí)顯示的具體界面,他們之間的結(jié)合使用,就會有FragmentPagerAdapter與FragmentStatePagerAdapter的區(qū)別。
我們可以有一個(gè)簡單的概念:當(dāng)Fragment頁面較多的時(shí)候,使用FragmentStatePagerAdapter,較少的時(shí)候使用FragmentPagerAdapter
而其中的原因,我們主要查看一下FragmentStatePagerAdapter的destroyItem()方法源碼:
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
Fragment fragment = (Fragment) object;
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
if (DEBUG) Log.v(TAG, "Removing item #" + position + ": f=" + object
+ " v=" + ((Fragment)object).getView());
while (mSavedState.size() <= position) {
mSavedState.add(null);
}
mSavedState.set(position, fragment.isAdded()
? mFragmentManager.saveFragmentInstanceState(fragment) : null);
mFragments.set(position, null);
mCurTransaction.remove(fragment);
}
可以看到,F(xiàn)ragmentStatePagerAdapter的destoryItem() 的方法中的最后一行, mCurTransaction.remove(fragment),是將這個(gè)Fragment實(shí)例直接從隊(duì)列中移除,也就是釋放了內(nèi)存
而FragmentPagerAdapter的destoryItem()方法源碼:
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
if (DEBUG) Log.v(TAG, "Detaching item #" + getItemId(position) + ": f=" + object
+ " v=" + ((Fragment)object).getView());
mCurTransaction.detach((Fragment)object);
}
最后一行是mCurTransaction.detach((Fragment)object),調(diào)用的是detach方法,這個(gè)方法只是將Activity的UI與Fragment的UI進(jìn)行分離,并沒有進(jìn)行回收內(nèi)存,因此從內(nèi)存占用角度來說,當(dāng)頁面較少的時(shí)候,比較適合使用FragmentPagerAdapter。
2.Fragment的生命周期都有哪些?
首先,我們來看一下Fragment獨(dú)立的一個(gè)生命周期:

總的來看,F(xiàn)ragment的生命周期與Activity的生命周期比較相似。之前我們也談到過,F(xiàn)ragment本身不能獨(dú)立存在的,必須依附在某一個(gè)Activity上,那么,關(guān)于Fragment的實(shí)際開發(fā)中需要了解的生命周期,我們需要結(jié)合一下Activity的生命周期來進(jìn)行認(rèn)識:

直接看圖倒是可以知道整個(gè)流程,但是,方法確實(shí)太多了,他們之間的區(qū)別和聯(lián)系其實(shí)還是沒有辦法能夠搞清楚,一個(gè)一個(gè)進(jìn)行講解恐怕也會比較枯燥,所以,如果真正想弄懂整個(gè)過程,還是需要我們平時(shí)的日積月累以及深入探究。這里我就大概描述一下這個(gè)過程,希望能夠幫助大家更好的進(jìn)行理解:
- 首先,我們能夠看到,當(dāng)Fragment進(jìn)行第一次創(chuàng)建的時(shí)候,會調(diào)用onAttach()方法,表明Fragment與Activity進(jìn)行關(guān)聯(lián)后進(jìn)行的回調(diào)
- onAttach()方法后,會調(diào)用Fragment中的onCreate()方法,初次創(chuàng)建Fragment的時(shí)候進(jìn)行回調(diào),區(qū)別于Activity的onCreate()方法,這個(gè)只是創(chuàng)建Fragment,但Activity并沒有創(chuàng)建完成
- 之后調(diào)用的Fragment的onCreateView()方法,表示系統(tǒng)首次繪制Fragment的UI,值得注意的是,從這個(gè)方法返回的View必須是Fragment布局的根視圖。
- 之后調(diào)用的是onViewCreated()方法,表示Fragment的UI界面已經(jīng)完全繪制好了,這個(gè)時(shí)候可以進(jìn)行初始化Fragment的控件資源
- 接下來,就是調(diào)用Activity的onCreate()方法,表示Activity創(chuàng)建完成的回調(diào)
- 之后,調(diào)用的是Fragment的onActivityCreated()方法,表示Activity被渲染,繪制成功以后進(jìn)行的回調(diào),值得注意的是,這個(gè)方法必須在Activity的onCreate()方法之后進(jìn)行調(diào)用
- 接著調(diào)用Activity的onStart()方法,表示Activity已經(jīng)可見了
- 之后調(diào)用Fragment的onStart()方法,表示Fragment也已經(jīng)可見了
- 接著調(diào)用Activity的onResume()方法,表示Activity已經(jīng)可以與用戶進(jìn)行交互了
- 之后調(diào)用Fragment的onResume()方法,表示Fragment也可以與用戶進(jìn)行交互了,可以執(zhí)行處理點(diǎn)擊,滑動(dòng)之類的行為了,已經(jīng)完成了Fragment從啟動(dòng)到展現(xiàn)的整個(gè)操作。
- 接下來就是回退之后的生命周期了,先走Fragment的onPause()方法,表示已經(jīng)不能與用戶進(jìn)行交互了
- 接著調(diào)用Activity的onPause的方法,表示Activity也不能與用戶進(jìn)行交互了
- 接著調(diào)用Fragment的onStop()方法,表示Fragment不可見了
- 接著調(diào)用Activity的onStop()方法,表示Activity不可見了
- 然后調(diào)用onDestoryView()方法,這個(gè)方法我們不是很熟悉,但是我們需要知道的是,與這個(gè)方法對應(yīng)的是onCreateView()方法,表示這個(gè)Fragment即將結(jié)束,然后會被保存。
- 接著會回調(diào)Fragment的onDestory()方法,與之前對應(yīng)的onCreate()方法,表示Fragment不會被使用。
- 接著調(diào)用Fragment生命周期的最后一個(gè)方法,onDetach()方法,表示整個(gè)Fragment已經(jīng)被銷毀
- 最后調(diào)用Activity的onDestory()方法,表示整個(gè)Activity被回收了。
以上就是一個(gè)Fragment從啟動(dòng)到銷毀的整個(gè)過程,接下來我們來回顧一下Fragment的數(shù)據(jù)通信。
3. Fragment是如何通信的?
關(guān)于Fragment的通信,我們需要有以下三個(gè)大的概念:
1. 在Fragment中調(diào)用Activity中的方法 ----> 使用getActivity()
2. Activity中調(diào)用Fragment中的方法 ----> 常用接口回調(diào),在Fragment定義一個(gè)接口,在Activity中進(jìn)行實(shí)現(xiàn)該接口
3. Fragment中調(diào)用Fragment中的方法 ----> 首先使用getActivity()獲取Activity的方法,然后通過findFragmentById獲取到另一個(gè)Fragment中的方法
這三種方式,我們需要進(jìn)行理解他們的使用方式,這樣,我們遇到需要實(shí)現(xiàn)某一個(gè)功能的時(shí)候,可以借鑒這些方式進(jìn)行實(shí)現(xiàn),提供最適合的功能實(shí)現(xiàn)方案。
4.Fragment管理器:FragmentManager是怎么管理Fragment的
在我們實(shí)際開發(fā)過程當(dāng)中,都需要將某一個(gè)Fragment進(jìn)行顯示,隱藏,替換,移除等相關(guān)的操作,我們都是通過FragmentManager的操作類FragmentTransaction對象進(jìn)行操作的。
主要有以下這些方法:
1. add:將新創(chuàng)建的一個(gè)Fragment實(shí)例加入到FragmentManager的隊(duì)列中去,切換的時(shí)候,F(xiàn)ragment的狀態(tài)信息或者成員變量都沒有發(fā)生改變
2. replace:將之前顯示的Fragment直接remove銷毀掉,然后再把當(dāng)前的Fragment add進(jìn)來,也就是需要移除之前的Fragment再重新創(chuàng)建一個(gè)新的Fragment實(shí)例
3. remove:移除,銷毀FragmentManager隊(duì)列中的Fragment
4. hide:將某一個(gè)Fragment實(shí)例進(jìn)行隱藏,但狀態(tài)信息以及成員變量都沒有進(jìn)行回收
5. show:將某一個(gè)Fragment實(shí)例進(jìn)行顯示
6. detach:并不是將某一個(gè)Fragment實(shí)例進(jìn)行銷毀,而是將Fragment的View進(jìn)行銷毀,下次再加載的時(shí)候,需要重新繪制View
7. attach: 與detach方法相對應(yīng),將之前銷毀View的Fragment的實(shí)例進(jìn)行重新繪制View,在實(shí)際開發(fā)中,這一對我們用的相對比較少,因?yàn)檎撍俣?,并沒有hide/show方式那么快,論占用內(nèi)存的多少,也沒有比add/remove優(yōu)化多少
干貨總結(jié)
通過上面四個(gè)方面的回顧,我們對于Fragment對認(rèn)識應(yīng)該有了一個(gè)整體的概念。
不過,千說萬說,不如自己動(dòng)手去實(shí)踐,最好的方式還是自己親自將Fragment相關(guān)的一些實(shí)現(xiàn)用代碼實(shí)現(xiàn)出來,遇到了坑,就會對某一個(gè)問題有了深刻理解。
雖然Google也推薦我們盡量使用少Activity + 多Fragment的方式進(jìn)行項(xiàng)目開發(fā),但是,F(xiàn)ragment依然有些坑,我們在用的時(shí)候還是需要注意的。
下面大概說幾個(gè)我們在實(shí)際開發(fā)中使用Fragment的時(shí)候需要注意的地方:
1. 在同一個(gè)Activity中,只能存在一個(gè)id(Fragment的唯一標(biāo)識)標(biāo)識的Fragment實(shí)例。因?yàn)橛锌赡茉诓季值臅r(shí)候會出現(xiàn)不允許創(chuàng)建的錯(cuò)
誤。
2. FragmentManager的作用范圍是整個(gè)Activity,因此某一個(gè)布局中的Fragment ID,不能重復(fù)進(jìn)行替換。因?yàn)镕ragmentManager只認(rèn)布局
中的ID,如果有多個(gè)實(shí)例都是用的這一個(gè)布局ID,也只會顯示一個(gè)。
3. Fragment的可見性并沒有像Activity那么方便操作,當(dāng)A Fragment被B Fragment覆蓋的時(shí)候,A此時(shí)是會回調(diào)Fragment的onStop()方
法,但當(dāng)B Fragmeng從棧中回退后,A Fragment無法感知自己正處于棧頂,需要我們開發(fā)人員自行監(jiān)聽一些狀態(tài)來判斷A Fragment是否對于
用戶可見
4. Fragment的事件傳遞也需要注意,處于頂部的Fragment需要注意判斷是否該消費(fèi)此點(diǎn)擊事件,否則如果存在多個(gè)Fragment層疊現(xiàn)象,可能
下層的Fragment會消費(fèi)此事件。
...
所以,我們在實(shí)際開發(fā)當(dāng)中,還是需要了解當(dāng)前功能使用Fragment是否OK,如果有問題,是否有相應(yīng)的解決方案。
好了,關(guān)于Fragment的一些知識點(diǎn)就先回顧到這里。
如果喜歡本篇文章的內(nèi)容或者講述形式,希望給一個(gè)喜歡和贊,如果有什么不對的地方,或者需要改進(jìn)的地方,也希望能夠留言說一下,謝謝了。