Fragment
是Android
的視圖生命週期控制器(帶生命週期的自定義View
),是Activity
上的View
層級中的一部分,通常能夠把它看作一個輕量級的Activity
.與傳統的Activity
相比,它只佔用更少的資源,而且提供更大的編碼靈活性
、在超低版本上的兼容性
等.java
使用Fragment
,即便是在肥腸差勁的平臺(例如API 19如下連ART
都沒有的的老系統)上也能獲得較好的運行效果,而且能將過渡動畫兼容到更低的版本(經過FragmentTransition
指定)。android
早期的Fragment
出現過不少問題,好比沒有onBackPressed()
,沒有啓動模式,重複建立,辣雞的回退棧,迷之生命週期等等,致使不少開源做者本身獨立開發了用於Fragment
管理的框架,其中比較出名的有YoKeyword大佬的Fragmentation.git
不過事物老是曲折發展的,通過Google
多年的調教,如今的Fragment
的功能已經很完善了,在不少場合,足以在不少場合替代Activity
的存在,上面的一些問題也獲得了比較妥善的解決,若是看完這篇文章,相信你會找到答案。github
巨佬JakeWharton
曾經建議:一個App
只須要一個Activity
.api
這說的就是單Activity
多Fragment
模式.使用這種模式有許多好處:bash
Activity
屬於系統組件,受AMS
管理而且自身是一個God Object
(上帝對象,Activity
的功能太過強大以致於耦合了View
層和Model
層),它的開銷是很大的,單Activity
模式能夠爲咱們節省不少資源,還能夠避免資源不足時,被前臺Activity
覆蓋的Activity
被殺掉致使頁面數據丟失的狀況(由於只有一個Activity
,除非JAVA
堆內存到達系統要殺掉一個程序的臨界點,不然系統最不傾向於殺死前臺正在運行的Activity
);知乎
就使用了單Activity
多Fragment
這種模式),由於咱們都知道Activity
的是沒法在多個頁面中複用的,而此時Fragment
就有了它的用武之地,它做爲輕量級的Activity
,基本能夠代理Activity
的工做,而且他是可複用Fragment
能夠爲程序帶來更大的靈活性,咱們都知道在Activity
之間傳遞對象,對象須要序列化,這是由於Activity
做爲系統組件,是受AMS
管理的,而AMS
屬於系統進程,不在當前程序運行的進程中,啓動Activity
時須要暫時離開當前進程去到AMS
的進程中,而AMS
則會將你準備好的數據(也就是Intent
之類的)用來啓動Activity
,這也是Fragment
和Activity
之間的區別之一,Activity
屬於系統組件,能夠在別的進程運行(組件化/多進程方案),而Fragment
只是框架提供給咱們的的一個組件,它必須依附於Activity
生存,而且只能在當前進程使用,但這同時也意味這它能夠得到更大的靈活性,咱們能夠給Fragment
傳遞對象而無需序列化,甚至能夠給Fragment
傳遞View
之類的對象,這都是Activity
不容易作到的.首先要提一點,若是你要學習Fragment
那麼你至少得是掌握了Activity
的,若是你還不瞭解Activity
,筆者建議你先去看一些Activity
相關的文章,再來進階Fragment
.從下面的文章開始,默認讀者已經瞭解了Activity
的生命週期等相關知識。微信
Fragment
有兩種方式生成,一是硬編碼到xml
文件中,二是在Java
代碼中new
,而後經過FragmentManager#beginTransaction
開啓FragmentTransaction
提交來添加Fragment
(下文會介紹).兩種方式存在着必定區別.硬編碼到xml
的Fragment
沒法被FragmentTransition#remove
移除,與Activity
同生共死,因此你要是這麼用了,就不用試了,移除不了的,可是在代碼中new
出來的是能夠被移除的.app
直接硬編碼到xml
中:框架
<fragment
android:id="@+id/map_view"
android:name="org.kexie.android.dng.navi.widget.AMapCompatFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
複製代碼
添加Fragment
的第二種方式就是使用FragmentManager#beginTransaction
(代碼以下)動態添加,你須要先new
一個Fragment
,而後經過下面Fragment#requireFragmentManager
獲取FragmentManager
來使用beginTransaction
添加Fragment
,注意add
方法的第一個參數,你須要給它指定一個id
,也就是Fragment
容器的id
,一般容器是一個沒有子View
的FrameLayout
,它決定了這個Fragment
要在什麼位置顯示.ide
//在xml中編寫放置Fragment位置的容器
<FrameLayout
android:id="@+id/fragment_container"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
//在java代碼中動態添加Fragment
requireFragmentManager()
.beginTransaction()
.add(R.id.fragment_container, fragment)
.runOnCommit(()->{/*TODO*/})
.addToBackStack(null)
.commit();
複製代碼
在Fragment
中,咱們可使用getId()
能夠返回自身的id
,一般用這個方法返回它所在的容器的id
,供其餘Fragment
添加進也添加到當前容器時使用(例如使用Fragment
返回棧的場景)。
/**
* Return the identifier this fragment is known by. This is either
* the android:id value supplied in a layout or the container view ID
* supplied when adding the fragment.
*/
final public int getId() {
return mFragmentId;
}
複製代碼
須要注意的是FragmentTransaction
並非當即執行的,而是在當前代碼執行完畢後,回到事件循環(也就是大家知道的Looper
)時,纔會執行,不過他會保證在下一幀渲染以前獲得執行,若要在FragmentTransaction
執行時搞事情,你須要使用runOnCommit
,在上面的代碼中我使用了Java8
的lambda
表達式簡寫了Runnable
.
若是你還想使用Fragment
回退棧記得調用addToBackStack
,最後別忘了commit
,這樣纔會生效,此時commit
函數返回的是BackStackEntry
的id
固然FragmentTransaction
不止能夠執行add
操做,一樣也能夠執行remove
,show
,hide
等操做.
這裏插入一個簡短的題外話做爲上面知識的補充。如何在Android Studio
中啓用Java8
?在你模塊的build.gradle
中
android{
//省略.....
//加上下面的腳本代碼,而後sync你的項目
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
複製代碼
onBackPressed
在哪?我知道第一次使用Fragment
的人確定都超想問這個問題.衆所周知Fragment
自己是沒有onBackPressed
的.不是Google
不設計,而是真的無法管理啊!!!,若是一個界面上有三四個地方都有Fragment
存在,一按回退鍵,誰知道要交給哪一個Fragment
處理呢?因此Fragment
自己是沒有onBackPressed
的.可是,實際上給Fragment
添加相似onBackPressed
的功能的辦法是存在的,只是Google
把它設計成交給開發者自行管理了.
這個功能是徹底基於Google
的appcompat
包實現的,可是如果咱們想要使用這個功能,可能須要較高版本的appcompat
包,或者你把項目遷移到AndroidX
(遷移方式下面會介紹).
咱們可使用FragmentActivity
(AppCompatActivity
繼承了FragmentActivity
)的addOnBackPressedCallback
方法爲你的Fragment
提供攔截OnBackPressed
的功能了.(非AndroidX
的其餘版本可能也有實現了這個功能)
public void addOnBackPressedCallback(@NonNull LifecycleOwner owner,
@NonNull OnBackPressedCallback onBackPressedCallback)
複製代碼
OnBackPressedCallback#handleOnBackPressed
須要返回一個boolean
值。若是你在這個回調裏攔截了onBackPressed
應該返回true
,說明你本身已經處理了本次返回鍵按下的操做,這樣你的Fragment
就不會被彈出返回棧了。
值得注意的是,這個函數的第一個參數,一個LifecycleOwner
,Activity
和Fragment
都是LifecycleOwner
,用於提供組件的生命週期,這個參數能夠幫咱們自動管理OnBackPressedCallback
回調,你無需手動將他從Activity
中移除,在LifecycleOwner
的ON_DESTROY
事件來到的時候,他會被自動移除列表,你無需擔憂內存泄漏,框架會幫你完成這些事情。
/**
* Interface for handling {@link ComponentActivity#onBackPressed()} callbacks without
* strongly coupling that implementation to a subclass of {@link ComponentActivity}.
*
* @see ComponentActivity#addOnBackPressedCallback(LifecycleOwner, OnBackPressedCallback)
* @see ComponentActivity#removeOnBackPressedCallback(OnBackPressedCallback)
*/
public interface OnBackPressedCallback {
/**
* Callback for handling the {@link ComponentActivity#onBackPressed()} event.
*
* @return True if you handled the {@link ComponentActivity#onBackPressed()} event. No
* further {@link OnBackPressedCallback} instances will be called if you return true.
*/
boolean handleOnBackPressed();
}
複製代碼
咱們能夠看到Activity
內管理的OnBackPressedCallback
的執行循序與添加時間有關.最後被添加進去的能最早獲得執行.
public void addOnBackPressedCallback(@NonNull LifecycleOwner owner,
@NonNull OnBackPressedCallback onBackPressedCallback) {
Lifecycle lifecycle = owner.getLifecycle();
if (lifecycle.getCurrentState() == Lifecycle.State.DESTROYED) {
// Already destroyed, nothing to do
return;
}
// Add new callbacks to the front of the list so that
// the most recently added callbacks get priority
mOnBackPressedCallbacks.add(0, new LifecycleAwareOnBackPressedCallback(
lifecycle, onBackPressedCallback));
}
複製代碼
能夠看到它是添加到mOnBackPressedCallbacks
這個List
的最前面的.
startFragmentForResult
方法在哪?對不起和OnBackPressed
同樣,Google
沒有直接爲咱們實現這個方法,但這並不表明Fragment
沒有這個功能,你固然能夠直接用定義getter
的方式來獲取Fragment
上內容,但這並非最佳實踐,爲了規範編碼咱們最好仍是使用公共的API
Fragment#setTargetFragment
能夠給當前Fragment
設置一個目標Fragment
和一個請求碼
public void setTargetFragment(@Nullable Fragment fragment, int requestCode)
複製代碼
噹噹前Fragment
完成相應的任務後,咱們能夠這樣將返回值送回給咱們的目標Fragment
經過Intent
getTargetFragment().onActivityResult(getTargetRequestCode(),
Activity.RESULT_OK,new Intent());
複製代碼
不過要注意,目標Fragment
和被請求的Fragment
必須在同一個FragmentManager
的管理下,不然就會報錯
好了若是你如今使用的appcompat
包沒有上面的騷操做.那麼下面我將帶你遷移到AndroidX
.
這裏可能有人會問AndroidX
是什麼?
簡單來說AndroidX
就是一個與平臺解綁的appcompat
(低版本兼容高版本功能)庫,也就是說在build.gradle
中不須要再與compileSdkVersion
寫成同樣,例如以前這樣的寫法:
compile 'com.android.support:appcompat-v7:24.+'
複製代碼
(注:使用24.+則代表使用 24. 開頭的版本的最新版本,若直接使用+號則代表直接使用該庫的最新版本。
如今能夠寫成:
implementation 'androidx.appcompat:appcompat:1.1.0-alpha02'
複製代碼
(注:新的依賴方式implementation
與compile
功能相同,可是implementation
沒法在該模塊內引用依賴的依賴,但compile
能夠,這麼作的好處是能夠加快編譯速度。新的依賴方式api
與compile
徹底相同,只是換了名字而已)
在Android Studo3.0
以上中的Refactor->Migrate to AndroidX
的選點擊以後便可將項目遷移到AndroidX
,在確認的時會提示你將項目備份以避免遷移失敗時丟失原有項目,一般狀況下不會遷移失敗,只是遷移的過程會花費不少的時間,若是項目很大,遷移時間會很長,這時即便Android Studio
的CPU
利用率爲0
也不要關閉, 可是若是發生遷移失敗,這時候就須要手動遷移了。
一些使用gradle
依賴的一些第三方庫中的某些類可能繼承了android.support.v4
包下的Fragment
,但遷移到AndroidX
後appcompat
的Fragment
變成了androidx.fragment.app
包下,原有的代碼下會畫紅線,Android Studio
也會警告你出現錯誤,可是不用擔憂,依然能夠正常編譯,Android Studio
在編譯的時候會自動完成基類的替換,但前提是你要確保你項目裏的gradle.properties
進行了以下設置。
android.useAndroidX=true
android.enableJetifier=true
複製代碼
爲了消除這些難看的紅線,你能夠直接將新的Fragment
使用這種方式強制轉換成原有的Fragment
。
TextureSupportMapFragment mapFragment = TextureSupportMapFragment
.class.cast(getChildFragmentManager()
.findFragmentById(R.id.map_view));
複製代碼
同理,也能夠將舊的Fragment
強制類型轉換成新的Fragment
.
Fragment f = Fragment.class.cast(mapFragment);
複製代碼
(注:上面的TextureSupportMapFragment
是一個典型案例,他是高德地圖SDK
中的Fragment
,它自己已經繼承了v4包下的Fragment
,能夠用過上面的轉換來使他兼容AndroidX
)
最後補充一個小Tips
:當咱們在使用Fragment#getActivity()
時返回的是一個可空值,若是沒有判空檢查在Android Studio
中將會出現一個噁心的黃色警告,你可使用requireActivity()
來代替它,一樣的方法還有requireFragmentManager()
等.
這多是最讓人懊惱的部分之一了。它彰顯了Fragment
中最讓人恐懼的一部分,它的生命週期.
Fragment
擁有Activity
全部的生命週期回調函數而且因爲自身特色還擴展了一些回調函數,若是不熟悉Fragment
,很容易憑直覺形成誤會.例如,一個Fragment
並不會由於在Fragment
回退棧上有其餘Fragment
把它蓋住,又或者是你使用FragmentTransition
將它hide
而致使他onPause
,onPause
只跟此Fragment
依附的Activity
有關,這在Fragment
的源碼中寫得清清楚楚.
/**
* Called when the Fragment is no longer resumed. This is generally
* tied to {@link Activity#onPause() Activity.onPause} of the containing
* Activity's lifecycle. */ @CallSuper public void onPause() { mCalled = true; } 複製代碼
那當咱們想在Fragment
不顯示時作一些事情要怎麼辦呢?咱們有onHiddenChanged
回調,當Fragment
的顯示狀態經過FragmentTransition
改變時(hide
和show
),就會回調這個函數,參數hidden
將告訴你這個Fragment
如今是被隱藏仍是顯示着.
/**
* Called when the hidden state (as returned by {@link #isHidden()} of
* the fragment has changed. Fragments start out not hidden; this will
* be called whenever the fragment changes state from that.
* @param hidden True if the fragment is now hidden, false otherwise.
*/
public void onHiddenChanged(boolean hidden) {
}
複製代碼
原本筆者想要用ProcessOn,本身畫一張Fragment
生命週期的流程圖.,最後......真香,由於這圖實在是太複雜了,真要畫它時間上有點過不去,因此我只好拿來主義.
下圖展現了各回調發生的時間順序(出處在這):
捋一下,以爲上面有圖有點煩的話的話那就看下面總結的文字吧,經常使用的回調有這些:
onInflate(Context,AttributeSet,Bundle)
只有硬編碼在xml
中的Fragment
(即便用fragment
標籤)纔會回調此方法,這與自定義View
十分相似,在實例化xml
佈局時該方法會被調用,先於onAttach
.
onAttach(Context)
執行該方法時,Fragment
與Activity
已經完成綁定,當一個Fragment
被添加到FragmentManager
時,若是不是在xml
中直接定義fragment
標籤,那麼該方法老是最早被回調.該方法傳入一個Context
對象,實際上就是該Fragment
依附的Activity
.重寫該方法時記得要調用父類的super.onAttach
,父類的onAttach
調用返回後,此時調用getActivity
將不會返回null
,可是Activity#onCreate
可能還有沒有執行完畢(若是是在xml
中定義,這種狀況就會發生,由於此時這個回調的這個發生的時間也就是你在Activity#onCreate
裏setContentView
的時間,直到Fragment#onViewCreated
返回以後,Activity#onCreate
纔會繼續執行)。
onCreate(Bundle)
用來初始化Fragment
。它老是在onAttach
執行完畢後回調,可經過參數savedInstanceState
獲取以前保存的值,記得必定要調用父類的super.onCreate
。
onCreateView(LayoutInflater,ViewGroup,Bundle)
須要返回一個View
用來初始化Fragment
的佈局,它老是在onCreate
執行完畢後回調。默認返回null
,值得注意的是,若返回null
Fragment#onViewCreated
將會被跳過,且若是是在xml
中定義fragment
標籤並用name
指定某個Fragment
,則這個方法不容許返回null
,不然就會報錯。當使用ViewPager
+Fragment
時此方法可能會被屢次調用(與Fragment#onDestroyView
成對調用)。
onActivityCreated(Bundle)
執行該方法時,與Fragment
綁定的Activity
的onCreate
方法已經執行完成並返回,若在此方法以前與Activity
交互交互沒有任何保證,引用了未初始化的資源就會應發空指針異常。
onStart()
執行該方法時,Fragment
所在的Activity
由不可見變爲可見狀態
onResume()
執行該方法時,Fragment
所在的Activity
處於活動狀態,用戶可與之交互.
onPause()
執行該方法時,Fragment
所在的Activity
處於暫停狀態,但依然可見,用戶不能與之交互,好比Dialog
蓋住了Activity
onStop()
執行該方法時,Fragment
所在的Activity
徹底不可見
onSaveInstanceState(Bundle)
保存當前Fragment
的狀態。該方法會自動保存Fragment
的狀態,好比EditText
鍵入的文本,即便Fragment
被回收又從新建立,同樣能恢復EditText
以前鍵入的文本,說實話我不太喜歡這個方法,保存到Bundle
裏的設計實在是太蠢了,不過好在如今已經有了代替它的方案,Google
的Android Jetpack MVVM
框架,以後我也會專門出一篇文章來介紹。
onDestroyView()
銷燬與Fragment
有關的視圖,但未與Activity
解除綁定,通常在這個回調裏解除Fragment
對視圖的引用。一般在ViewPager
+Fragment
的方式下會使用並重寫此方法,而且與Fragment#onCreateView
同樣多是屢次的。
onDestroy()
銷燬Fragment
。一般按Back
鍵退出或者Fragment
被移除FragmentManager
時調用此方法,此時應該清理Fragment
中所管理的全部數據,它會在onDetach
以前回調。
onDetach()
解除與Activity
的綁定。在onDestroy
方法以後調用。Fragment
生命週期的最末期,若在super.onDetach
返回後getActivity()
,你將會獲得一個null
。
看了那麼多有關Fragment
的介紹,若是你還對Fragment
嗤之以鼻,又想減少業務的邏輯的粒度,那麼我只能給你Fragment
的替代方案了。
一位square
公司(對就是那個誕生了Retrofit
和okhttp
的公司)的工程師開發的Fragment
替代方案《View框架flow》,以及相關博文,國內有優秀的簡書做者翻譯了這篇文章《(譯)我爲何不主張使用Fragment》,原做者在這篇文章中痛斥了Fragment
的各類缺點,我想你可能會喜歡這個.
好了關於從Activity
遷移到Fragment
的介紹差很少就到這了,我也是想到什麼就寫什麼,因此文章的結構可能會有些亂(逃......),之後若是還有其餘知識點我會慢慢補充上來.
若是你喜歡個人文章記得給我點個贊,拜託了,這對我真的很重要.