fragment源碼分析
❶ 從源碼角度分析,為什麼會發生 Fragment 重疊
1、發生了頁面重啟(旋轉屏幕、內存不足等情況被強殺重啟)。2、使用add()方式載入Fragment;
為什麼會發生Fragment重疊?
從源碼角度分析,為什麼發生頁面重啟後會導致重疊?(在以add方式載入Fragment的時候)
我們知道Activity中有個onSaveInstanceState()方法,該方法在app進入後台、屏幕旋轉前、跳轉下一個
Activity等情況下會被調用,此時系統幫我們保存一個Bundle類型的數據,我們可以根據自己的需求,手動保存一些例如播放進度等數據,而後如果
發生了頁面重啟,我們可以在onRestoreInstanceState()或onCreate()里get該數據,從而恢復播放進度等狀態。
而產生Fragment重疊的原因就與這個保存狀態的機制有關,大致原因就是系統在頁面重啟前,幫我們保存了Fragment的狀態,但是在重啟後恢復時,視圖的可見狀態沒幫我們保存,而Fragment默認的是show狀態,所以產生了Fragment重疊現象。
分析: 我們先看FragmentActivity的相關源碼:
public class FragmentActivity extends ... {
final FragmentController mFragments = FragmentController.createController(new HostCallbacks());
protected void onCreate(@Nullable Bundle savedInstanceState) {
...省略
if (savedInstanceState != null) {
Parcelable p = savedInstanceState.getParcelable(FRAGMENTS_TAG);
mFragments.restoreAllState(p, nc != null ? nc.fragments : null);
}
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
Parcelable p = mFragments.saveAllState();
...省略
}
}
從上面源碼可以看出,FragmentActivity確實是幫我們保存了Fragment的狀態,並且在頁面重啟後會幫我們恢復!
其中的mFragments是FragmentController,它是一個Controller,內部通過FragmentHostCallback間接控制FragmentManagerImpl。相關代碼如下:
public class FragmentController {
private final FragmentHostCallback<?> mHost;
public Parcelable saveAllState() {
return mHost.mFragmentManager.saveAllState();
}
public void restoreAllState(Parcelable state, List<Fragment> nonConfigList) {
mHost.mFragmentManager.restoreAllState(state, nonConfigList);
}
}
public abstract class FragmentHostCallback<E> extends FragmentContainer {
final FragmentManagerImpl mFragmentManager = new FragmentManagerImpl();
}
通過上面代碼可以看出FragmentController通過FragmentHostCallback里的FragmentManagerImpl對象來控制恢復工作。
我們接著看FragmentManagerImpl到底做了什麼:
final class FragmentManagerImpl extends FragmentManager {
Parcelable saveAllState() {
...省略 詳細保存過程
FragmentManagerState fms = new FragmentManagerState();
fms.mActive = active;
fms.mAdded = added;
fms.mBackStack = backStack;
return fms;
}
void restoreAllState(Parcelable state, List<Fragment> nonConfig) {
// 恢復核心代碼
FragmentManagerState fms = (FragmentManagerState)state;
FragmentState fs = fms.mActive[i];
if (fs != null) {
Fragment f = fs.instantiate(mHost, mParent);
}
}
}
我們通過saveAllState()看到了關鍵的保存代碼,原來是是通過FragmentManagerState來保存Fragment的狀態、所處Fragment棧下標、回退棧狀態等。
而在restoreAllState()恢復時,通過FragmentManagerState里的FragmentState的instantiate()方法恢復了Fragment(見下面的分析就明白啦)
我們看下FragmentManagerState:
final class FragmentManagerState implements Parcelable {
FragmentState[] mActive; // Fragment狀態
int[] mAdded; // 所處Fragment棧下標
BackStackState[] mBackStack; // 回退棧狀態
...
}
我們只看FragmentState,它也實現了Parcelable,保存了Fragment的類名、下標、id、Tag、ContainerId以及Arguments等數據:
final class FragmentState implements Parcelable {
final String mClassName;
final int mIndex;
final boolean mFromLayout;
final int mFragmentId;
final int mContainerId;
final String mTag;
final boolean mRetainInstance;
final boolean mDetached;
final Bundle mArguments;
...
// 在FragmentManagerImpl的restoreAllState()里被調用
public Fragment instantiate(FragmentHostCallback host, Fragment parent) {
...省略
mInstance = Fragment.instantiate(context, mClassName, mArguments);
}
}
至此,我們就明白了系統幫我們保存的Fragment其實最終是以FragmentState形式存在的。
此時我們再思考下為什麼在頁面重啟後會發生Fragment的重疊? 其實答案已經很明顯了,根據上面的源碼分析,我們會發現FragmentState里沒有Hidden狀態的欄位!
而Hidden狀態對應Fragment中的mHidden,該值默認false...
public class Fragment ... {
boolean mHidden;
}
我想你應該明白了,在以add方式載入Fragment的場景下,系統在恢復Fragment時,mHidden=false,即show狀態,這
樣在頁面重啟後,Activity內的Fragment都是以show狀態顯示的,而如果你不進行處理,那麼就會發生Fragment重疊現象!
為什麼使用add()載入Fragment會導致重疊?
我們知道載入Fragment有2種方式:replace()和add()。使用replace載入Fragment是不會發生重疊現象的,只有通過add方式才有可能發生重疊現象。
我們一般使用add時,會和show(),hide()配合使用,add配合hide是使Fragment的視圖改變為GONE狀態;而
replace是銷毀Fragment
的視圖。頁面重啟時,add的Fragment會全部走生命周期,創建視圖;而replace的非棧頂Fragment不會走生命周期,只有Back時,
才會逐一走棧頂Fragment生命周期,創建視圖。
結合上面的源碼分析,在使用replace載入Fragment時,頁面重啟後,Fragment視圖都還沒創建,所以mHidden沒有意義,不
會發生重疊現象;而在使用add載入時,視圖是存在的並且疊加在一起,頁面重啟後
mHidden=false,所有的Fragment都會是show狀態顯示出來(即VISIBLE),從而造成了Fragment重疊!
❷ Android組件之Fragment(一)---基礎知識與運用
Fragment是Android3.0後引入的一個新的API,他出現的初衷是為了適應大屏幕的平板電腦, 當然現在他仍然是平板APP UI設計的寵兒,而且我們普通手機開發也會加入這個Fragment, 我們可以把他看成一個小型的Activity,又稱Activity片段!想想,如果一個很大的界面,我們 就一個布局,寫起界面來會有多麻煩,而且如果組件多的話是管理起來也很麻煩!而使用Fragment 我們可以把屏幕劃分成幾塊,然後進行分組,進行一個模塊化的管理!從而可以更加方便的在 運行過程中動態地更新Activity的用戶界面!另外Fragment並不能單獨使用,他需要嵌套在Activity 中使用,盡管他擁有自己的生命周期,但是還是會受到宿主Activity的生命周期的影響,比如Activity 被destory銷毀了,他也會跟著銷毀!
引用官方的一張圖片,其實已經說明問題了,就是為了更好的適配大屏,在大屏的時候,不需要去在一個activity內部通過復雜的布局和界面去實現,只需要去在一個activity內部,通過多個fragment來做界面布局實現即可,而且針對於多個fragment來說, 每個fragment有單獨的生命周期,
Demo樣例,我們在一個界面中,有上下兩個fragment,如圖所示:
Step 2: Fragment創建,視圖載入,數據賦值
BlankFragment .java
Step 3: Activity在onCreate( )方法中調用setContentView()之後調用FragmentTransaction 進行事務提交
FragmentTestActivity.java
在xml中聲明兩個fragment,指定為具體的fragment
Step 1:定義Fragment的布局,就是fragment顯示內容的
Step 2:自定義一個Fragment類,需要繼承Fragment或者他的子類,重寫onCreateView()方法 在該方法中調用:inflater.inflate()方法載入Fragment的布局文件,接著返回載入的view對象
BlankFragment.java
Step 3:在需要載入Fragment的Activity對應的布局文件中添加fragment的標簽, 記住,name屬性是全限定類名哦,就是要包含Fragment的包名,如:
Step 4: Activity在onCreate( )方法中調用setContentView()載入布局文件即可!
針對在一個Activity中的某個Layout中切換Fragment,,無非兩種方法:
我們自己看一下方法注釋
源碼方法注釋裡面說的很明白,這個方法會移除所有的fragment,然後添加當前的fragment。
這時分為兩種情況,一種是fragment已有並且在前台展示,一種是未有或者在後台,針對於前者,此時replace,生命周期不會發生變化,針對後者,生命周期會重新走
分為兩種情況,一種fragment已存在,一種未存在,針對於前者,生命周期無變化,但是會回調onHiddenChanged方法,針對於後者,生命周期會創建一次。
1.Fragment是Google官方引入的一個為了適配大屏、多頁面的一個組件。您可以理解為它就是一個類而已,只不過裡麵包含了View,並且與activity的生命周期進行了關聯。
2.動態載入與靜態載入相對來說,建議使用動態載入,靜態載入固定在了xml文件中,永遠不變。
3.replace的fragment如果不在前台,會執行所有生命周期,反之不會執行任何生命周期方法;hide+show生命周期並不會發生變化,但是會回調onHiddenChanged方法,在實際開發中,建議add之後,使用hide+show來操作fragment,一方面減少資源的重復載入和創建,另外一方面提升用戶體驗感。
4.fragment的生命周期大體上和activity一致,但是前期和後期多了一些東西,因為fragment內部有view,那麼這個view需要進行創建、然後添加到activity內部,所以相應的在onCreate與onStart之間就多了幾個方法-onCreateView、onViewCreated。相同的道理,fragment的view與activity解綁,也相應的在onStop與onDestory之間多個方法-onDestroyView。onAttach與onDetach可以理解為視圖與activity產生關聯和接觸關聯,是最開始和最後的步驟。
Demo地址
❸ 淺析Fragment為什麼需要空的構造方法
因為Activity橫豎屏切換的時候,會重新構造Fragment,默認調用的就是Fragment的無參構造方法。
如果通過 setArguments來傳遞參數的話,Activity在構造Fragment時會通過反射無參構造實例化一個新的Fragment,並且給mArgments初始化為原先的值。
如果參數是通過構造方法傳遞的,在重新構造Fragment實例之後,數據就丟失了
這有一篇:Fragment源碼分析http://blog.csdn.net/shensky711/article/details/53171248,希望可以幫到你