基於AOP設計的Fragment框架

知乎的單Activity+多Fragment客戶端在使用的時候真的是如絲襪版順滑,給知乎團隊筆芯,可是Fragment在使用過程當中會遇到各類各樣的問題,平時使用都費勁,要寫這麼一個客戶端不得吐血?java

本篇文章介紹一個關於Fragment的管理框架FragmentRigger
這個框架的目標只有兩個:一、讓Fragment的使用更簡單。二、使用成本最低的Fragment框架。
本篇先對該框架產生的背景進行說明,接着介紹該框架解決的問題並給出部分解決方案,最後,介紹該框架的用法(水star三部曲)。git

1、拋出誘餌

疑問一: 你可能會問了,網上關於Fragment的框架不是一抓一大把,爲何還要重複造輪子呢?github

是的,關於Fragment的框架在網上是比較多的,如比較出名的YoKeyword大神的Fragmentation,解決了各個場景下的Fragment問題,並添加了左滑退出等額外的支持,不可謂不強大,請收下個人膝蓋。數組

疑問二: 請正面回答疑問一,這個框架有什麼不同的地方嗎?難道是老農民嗎?重複造輪子閒的蛋疼?安全

網上大多數的Fragment框架都是寫了一個FragmentActivity父類,並添加了相應的方法支持,因此在使用那些框架的時候須要你的ActivityFragment繼承他們框架提供的父類(不知怎麼的,筆者對繼承別人的父類總是有點排斥)。
是啊,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的生命週期等方法定義爲切點,插入到代理類中,一切操做都經過代理類來進行!!!

2、贏得信任

上節扯的本身框架多牛逼多牛逼,但都是紙上談兵,還不如來點實際的,這節將列舉平時遇到的問題並給出其中一些問題的解決方案,這樣你總該放心了吧!!!我不僅是來騙star的!!!

一、常見難點

由於在使用Fragment的時候常常遇到錯誤,並且有些場景在實現的時候無從下手,那麼在Fragment使用過程當中讓咱們頭痛的問題有哪些呢?下面咱們一一列舉一下。

難點一: 在使用過程當中遇到讓人抓狂的異常拋出!!

  • Can not perform this action after onSaveInstanceState
  • Can not perform this action inside of
  • Activity has been destroyed
  • FragmentManager is already executing transactions

難點二: 使用Fragment自己遇到的錯誤

  • getActivity()返回null
  • remove一個Fragment以後轉場動畫不執行問題
  • Fragment棧的各類問題
  • Fragment多層嵌套的問題
  • Fragment重疊顯示
  • 屏幕翻轉時(內存重啓)後Fragment碰見的問題
  • 在一個ContainerView中添加兩個Fragment,第一個Fragment還能夠被點擊問題
  • 提交事物後沒法當即執行致使的各類問題

難點三: 其餘問題

  • 沒法監聽onBackPressed
  • 在ViewPager中使用懶加載
  • 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();
}
複製代碼

固然,上述只是僞代碼,不過進行懶加載的原理就是這樣。

3、上勾

上面列舉了部分Fragment在使用過程當中遇到的問題給給出部分解決方案,看上去好像是這麼解決的啊,因此,我不是騙子啦~接下來正式對框架FragmentRigger進行介紹。

一個強大的Fragment框架,目標:讓Fragment的使用更簡單!!

這多是使用成本最低的Fragment框架了。
無需繼承!!!無需繼承!!!無需繼承!!! 重要的話說三遍!!
在使用FragmentRigger的時候,使用成本只有一行註解!!!
原理是把Fragment/Activity生命週期相關方法定義爲切點,經過ASpectJ綁定並使用代理類進行操做。

一、Wiki

二、特性

  • 超強大Api支持
  • 足夠多的英文註釋
  • 嚴格的異常拋出
  • 解決Fragment中常見的異常及Bug
  • 事務提交永不丟失
  • 擴展原生方法,添加onBackPressed等常見的方法支持
  • 當前棧成員樹狀圖打印
  • Fragment懶加載
  • Fragment轉場動畫
  • Fragment間共享元素轉場動畫(TODO)
  • Kotlin支持(TODO)

三、解決的問題

  • Fragment界面重疊
  • Fragment多級嵌套
  • Fragment棧的管理問題
  • Fragment事務提交失敗
  • Activity在非onResume狀態下提交事務
  • Fragment事務提交不能當即執行致使兩次提交事件衝突
  • 內存重啓時的一系列異常
  • 屏幕翻轉時的數據保存及恢復
  • Can not perform this action after onSaveInstanceState
  • 在ViewPager中的懶加載及其餘場景下的懶加載
  • 不一樣場景下轉場動畫不執行問題

四、Demo演示

棧管理 懶加載 同級顯示
支持Fragment同級\多層嵌套,並提供返回自動顯示棧頂成員等一系列場景支持 支持ViewPager等場景下的懶加載機制,使用簡單,一行註解就能夠支持 經過show方法顯示Fragment,支持預加載,懶加載等場景

爲了保持篇章的簡潔和美觀性,其餘的場景具體請在項目中查看!!!

五、超強大Api支持演示

本框架在開始的時候就聲明強大的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查看更多信息.

相關文章
相關標籤/搜索