知乎的單Activity+多Fragment客戶端在使用的時候真的是如絲襪版順滑,給知乎團隊筆芯,可是Fragment在使用過程當中會遇到各類各樣的問題,平時使用都費勁,要寫這麼一個客戶端不得吐血?java
本篇文章介紹一個關於Fragment
的管理框架FragmentRigger。
這個框架的目標只有兩個:一、讓Fragment的使用更簡單。二、使用成本最低的Fragment框架。
本篇先對該框架產生的背景進行說明,接着介紹該框架解決的問題並給出部分解決方案,最後,介紹該框架的用法(水star三部曲)。git
疑問一: 你可能會問了,網上關於Fragment的框架不是一抓一大把,爲何還要重複造輪子呢?github
是的,關於Fragment
的框架在網上是比較多的,如比較出名的YoKeyword大神的Fragmentation,解決了各個場景下的Fragment問題,並添加了左滑退出等額外的支持,不可謂不強大,請收下個人膝蓋。數組
疑問二: 請正面回答疑問一,這個框架有什麼不同的地方嗎?難道是老農民嗎?重複造輪子閒的蛋疼?安全
網上大多數的Fragment
框架都是寫了一個Fragment
和Activity
父類,並添加了相應的方法支持,因此在使用那些框架的時候須要你的Activity
和Fragment
繼承他們框架提供的父類(不知怎麼的,筆者對繼承別人的父類總是有點排斥)。
是啊,Fragment
的不少操做都是生命週期相關的,因此不繼承父類按理說是沒法進行Fragment
的管理的,可是FragmentRigger
就是可讓你在集成的時候不須要繼承任何類就能夠對Fragment進行操做!!!(固然,你本身的父類仍是要繼承的= =)框架
疑問三: 啥?不繼承??那怎麼使用???會不會更復雜?ide
複雜??本框架的目的就是讓Fragment的使用更加簡單,好了,廢話不BB,仍是來一行代碼最省事。svg
//在Activity中add並show BFragment.
@Puppet(containerViewId = R.id.container)
public class AActivity extend AppcompatActivity{
//觸發顯示操做
Rigger.getRigger(this).startFragment(BFragment.newInstance());
}
複製代碼
沒騙你吧,上述的代碼有沒繼承,調用一行代碼,成本只有一行註解就可使用!!!源碼分析
疑問四: 代碼這麼少,也不繼承,靠不靠譜啊?動畫
重溫一下本框架的目的:讓Fragment的使用更加簡單,不繼承是由於確實有不少人排斥使用第三方的父類,筆者也不例外,就算知道里面沒什麼要緊的事,但仍是極度沒有安全感,框架的原理是:使用AOP把Activity/Fragment
的生命週期等方法定義爲切點,插入到代理類中,一切操做都經過代理類來進行!!!
上節扯的本身框架多牛逼多牛逼,但都是紙上談兵,還不如來點實際的,這節將列舉平時遇到的問題並給出其中一些問題的解決方案,這樣你總該放心了吧!!!我不僅是來騙star的!!!
由於在使用Fragment的時候常常遇到錯誤,並且有些場景在實現的時候無從下手,那麼在Fragment使用過程當中讓咱們頭痛的問題有哪些呢?下面咱們一一列舉一下。
難點一: 在使用過程當中遇到讓人抓狂的異常拋出!!
難點二: 使用Fragment自己遇到的錯誤
難點三: 其餘問題
上述只是在使用Fragment中遇到的部分問題,種種惡行,罄竹難書!!! 可是這些問題都在FragmentRigger中被解決了!!!
那麼這些問題是如何解決的呢?因爲篇章限制,下面列舉幾個特別常見的問題的解決方法。
已解決:Can not perform this action after onSaveInstanceState
咱們先來看看這個異常的拋出的出處,這是在FragmentManager
中被拋出的,源碼以下:
private void checkStateLoss() {
if (mStateSaved) {
throw new IllegalStateException(
"Can not perform this action after onSaveInstanceState");
}
}
複製代碼
而這個方法是在方法enqueueAction(Runnable,boolean)
中被調用的,調用代源碼以下:
public void enqueueAction(Runnable action, boolean allowStateLoss) {
if (!allowStateLoss) {
checkStateLoss();
}
}
複製代碼
這個方法會在提交事物的時候調用,而且參數也是在那時候傳遞的,因此,使用commitAllowingStateLoss
方法確實能夠避免該異常的拋出,可是此次提交可能丟失,因此這並非最好的解決方案。 使用該方法只能說是避免異常,並非解決異常!!!
因此要解決該異常,咱們須要知道mStateSaved
方法是何時被置爲true
的 ,經過源碼分析(請自行分析,此處對分析過程不進行闡述),發現mStateSaved
會在Activity#onStop
調用時被置爲true
。而onSaveInstanceState
是在onStop
以前被調用的,那麼這個錯誤的意思也是沒毛病的。
那麼咱們如何解決這個問題呢,Activity
生命週期中onSaveInstanceState
方法以前執行的是onPause
方法,因此咱們只須要判斷onPause
是否被執行,並在已經被執行的時候不進行事物提交便可!!! 貼心的是在Fragment
中提供了方法isResumed()
能夠判斷該狀態,咱們能夠手動在Activity
中實現該方法。
那麼最終解決方案就是:在Activity/Fragment非onResume的狀態下不要提交事物,保存下來,在onResum的狀況下從新提交,就能夠確保事物必定提交成功,而且不會丟失!!!
已解決:Fragment重疊顯示
Fragment重疊顯示的緣由就很明顯了,多個Fragment被add在同一個container中,而且都是show的狀態,因此會致使重疊!!! 這個的解決方案 在YoKeyword的文章《9行代碼讓你App內的Fragment對重疊說再見》中已經解決,就不在此進行重複了。
已解決:沒法監聽onBackPressed
這個問題相比是大多數人都有的需求,可是奈何Fragment
中並無該方法的支持,因此咱們只能手動去實現該功能。
解決方案:在Fragment中定義方法onBackPressed
,並在Activity中遍歷所持有的Fragment並對該方法進行調用。
一切看似很簡單,可是時候存在一系列新的問題,如:在Fragment入棧以後多級嵌套後的傳遞順序問題、在棧內該方法的攔截問題等。 實現起來成本仍是很大的。不過,在FragmenTRigger 中這個問題獲得了合理的解決。
已解決:在ViewPager中使用懶加載
ViewPager
爲咱們提供了預加載的機制,但這種機制在使用的時候有時候反而不是好事,若是咱們經過setOffscreenPageLimit
設置的條目少了會讓在切換的時候從新生成Fragment
實例,但要是添加的多了則會讓好多Fragment
同時被初始化,因此此時,使用懶加載能夠有效處理該場景,只有在顯示的時候進行數據加載等行爲,而且在正常狀況下只加載一次。
那麼咱們如何在ViewPager
中加入懶加載呢?經過源碼分析,ViewPager
是經過setUserVisibleHint(boolean)
來控制Fragment
是否顯示的,因此咱們能夠在Fragment
中重寫該方法,並根據傳入的boolean
值判斷Fragment
是否顯示的狀態,可是須要注意的是,咱們須要進行Fragment是否手機加載的判斷進行是否懶加載的調用,不然,ViewPager
每次切換都會調用setUserVisibleHint
。
解決方案:在Fragment中重寫setUserVisibleHint()方法,而且定義一個懶加載的方法如:onLazyLoad(),根據setUserVisibleHint()傳入的值判斷Fragment是否顯示,並調用懶加載方法。
樣例代碼以下:
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
if (!mHasInitView||!isVisibleToUser) return;
//make sure the method onLazyViewCreated will be called only once.
if (mHasInvokeLazyLoad) return;
onLazyLoad();
}
複製代碼
固然,上述只是僞代碼,不過進行懶加載的原理就是這樣。
上面列舉了部分Fragment
在使用過程當中遇到的問題給給出部分解決方案,看上去好像是這麼解決的啊,因此,我不是騙子啦~接下來正式對框架FragmentRigger進行介紹。
一個強大的Fragment框架,目標:讓Fragment的使用更簡單!!
這多是使用成本最低的Fragment框架了。
無需繼承!!!無需繼承!!!無需繼承!!! 重要的話說三遍!!
在使用FragmentRigger
的時候,使用成本只有一行註解!!!
原理是把Fragment
/Activity
生命週期相關方法定義爲切點,經過ASpectJ綁定並使用代理類進行操做。
onBackPressed
等常見的方法支持內存重啓
時的一系列異常棧管理 | 懶加載 | 同級顯示 |
---|---|---|
![]() |
![]() |
![]() |
支持Fragment同級\多層嵌套,並提供返回自動顯示棧頂成員等一系列場景支持 | 支持ViewPager 等場景下的懶加載機制,使用簡單,一行註解就能夠支持 |
經過show 方法顯示Fragment ,支持預加載,懶加載等場景 |
爲了保持篇章的簡潔和美觀性,其餘的場景具體請在項目中查看!!!
本框架在開始的時候就聲明強大的Api支持,那麼本節舉例幾個場景。
場景一: Fragment懶加載
在前面也對懶加載提出了相應的解方案,那麼在本框架中是怎樣使用的呢?請看下面代碼:
@LazyLoad
@Puppet
public class ContainerFragment extends Fragment{
public void onLazyLoadViewCreated(Bundle savedInstanceState) {
//do something in here
}
}
複製代碼
使用成本: 兩行註解,一個方法,不須要繼承父類!!!
場景二: 轉場動畫
Fragment爲咱們提供了轉場動畫機制,可是在使用的時候須要和事物提交一塊兒使用,而且在remove的時候不支持轉場動畫。
@Animator(enter=R.anim.enter,exit=R.anim.exit,popEnter=R.anim.popEnter,popExit=R.anim.popExit)
@Puppet
public class AnimatorFragment extends Fragment{
}
複製代碼
使用成本: 兩行註解,一個方法,不須要繼承父類!!!
那麼問題來了,如何在library
使用該註解呢,由於在library
中R中的資源id都是變量,沒法直接在註解中使用,本框架對此也進行了相應的解決方案。
@Puppet
public class AnimatorFragment extends Fragment{
public int[] getPuppetAnimations(){
return new int[]{
R.anim.enter, R.anim.exit, 0, 0
};
}
}
複製代碼
不須要支持某場景的轉場動畫就置爲0,可是返回參數必須爲長度爲4的int數組。無需繼承,直接添加該方法便可!!!
場景三: 棧管理
本框架徹底摒棄了原生的棧,內部本身維護了棧進行管理!!!那麼如何打開一個Fragment進行入棧操做呢?請看下面代碼:
@Puppet(containerViewId = R.id.container)
public class AActivity extend AppcompatActivity{
//觸發顯示操做
Rigger.getRigger(this).startFragment(BFragment.newInstance());
}
複製代碼
使用成本: 一行註解,一行代碼,不須要繼承父類!!!
本框架甚至提供了棧的樹狀圖的打印,能夠實時查看內部棧的成員!!出棧的時候默認會顯示棧頂的成員,無需再進行額外的顯示操做,還有onBackPress在棧成員中的調用並支持任意層級的攔截!!!
場景四: onBackPressed及其攔截
本框架爲棧內的Fragment提供onBackPressed方法的支持!!並支持任意層級的攔截,傳遞順序由外至內!!!
@Puppet
public class StackFragment extends Fragment{
public void onRiggerBackPressed(){
//Rigger.getRigger(this).onBackPressed();
//不攔截不須要寫該方法,如有該方法則可在此方法中進行攔截,上行代碼爲調用默認的返回代碼。
}
}
複製代碼
使用成本: 一行註解,一個方法,不須要繼承父類!!!
若是須要調用默認的返回方法,使用Rigger.getRigger(this).onBackPressed()
便可。
上述場景支持該框架的一部分使用方式,具體使用請看Wiki
本項目遵循MIT開源協議. 瀏覽LICENSE查看更多信息.