不知道大家都沒有本身特別的學習的方法,我是有吧全部的整理成筆記的習慣
好比今天講解的關於Fragment
的我會作成筆記git
因爲文章有些地方代碼過於太長了繁瑣,因此部分省略掉了,敲了一下午眼睛和手脖子都酸了,至於省略的部分,對這些筆記,面試內容感興趣的能夠看筆記研究,歡迎留言
github
相關內容後續GitHub更新,想衝擊金三銀四的小夥伴能夠找找看看,歡迎star
(順手留下GitHub連接,須要獲取相關面試等內容的能夠本身去找)
https://github.com/xiangjiana/Android-MS面試
實現很簡單,建立一個的佈局,而後在 Activity
裏點擊時替換 Fragment
異步
mFragmentManager = getSupportFragmentManager(); mFragmentManager.beginTransaction() .replace(R.id.fl_content, fragment) .commitAllowingStateLoss();
代碼很簡單,核心就三步:ide
- 建立
Fragment
- 獲取
FragmentManager
- 調用事務,添加、替換
咱們一步步來了解這背後的故事。
Fragment 你們應該比較熟悉,放到最後。
先來看看 FragmentManager
。
####二丶 FragmentManager
佈局
public abstract class FragmentManager {...}
FragmentManager
是一個抽象類,定義了一些和 Fragment 相關的操做和內部類/接口。post
FragmentManager
中定義的方法以下:學習
//開啓一系列對 Fragments 的操做 public abstract FragmentTransaction beginTransaction(); //FragmentTransaction.commit() 是異步執行的,若是你想當即執行,能夠調用這個方法 public abstract boolean executePendingTransactions(); //根據 ID 找到從 XML 解析出來的或者事務中添加的 Fragment //首先會找添加到 FragmentManager 中的,找不到就去回退棧裏找 public abstract Fragment findFragmentById(@IdRes int id); //跟上面的相似,不一樣的是使用 tag 進行查找 public abstract Fragment findFragmentByTag(String tag); //彈出回退棧中棧頂的 Fragment,異步執行的 public abstract void popBackStack(); //當即彈出回退棧中棧頂的,直接執行哦 public abstract boolean popBackStackImmediate(); ......
能夠看到,定義的方法有不少是異步執行的,後面看看它到底是如何實現的異步。動畫
2.2.內部類/接口:this
BackStackEntry
:Fragment
後退棧中的一個元素onBackStackChangedListener
:後退棧變更監聽器FragmentLifecycleCallbacks
: FragmentManager
中的 Fragment
生命週期監聽
//後退棧中的一個元素 public interface BackStackEntry { //棧中該元素的惟一標識 public int getId(); //獲取 FragmentTransaction#addToBackStack(String) 設置的名稱 public String getName(); @StringRes public int getBreadCrumbTitleRes(); @StringRes public int getBreadCrumbShortTitleRes(); public CharSequence getBreadCrumbTitle(); public CharSequence getBreadCrumbShortTitle(); }
能夠看到 BackStackEntry
的接口比較簡單,關鍵信息就是 ID 和 Name。
//在 Fragment 回退棧中有變化時回調 public interface OnBackStackChangedListener { public void onBackStackChanged(); } //FragmentManager 中的 Fragment 生命週期監聽 public abstract static class FragmentLifecycleCallbacks { public void onFragmentPreAttached(FragmentManager fm, Fragment f, Context context) {} public void onFragmentAttached(FragmentManager fm, Fragment f, Context context) {} public void onFragmentCreated(FragmentManager fm, Fragment f, Bundle savedInstanceState) {} public void onFragmentActivityCreated(FragmentManager fm, Fragment f, Bundle savedInstanceState) {} public void onFragmentViewCreated(FragmentManager fm, Fragment f, View v, Bundle savedInstanceState) {} public void onFragmentStarted(FragmentManager fm, Fragment f) {} public void onFragmentResumed(FragmentManager fm, Fragment f) {} public void onFragmentPaused(FragmentManager fm, Fragment f) {} public void onFragmentStopped(FragmentManager fm, Fragment f) {} public void onFragmentSaveInstanceState(FragmentManager fm, Fragment f, Bundle outState) {} public void onFragmentViewDestroyed(FragmentManager fm, Fragment f) {} public void onFragmentDestroyed(FragmentManager fm, Fragment f) {} public void onFragmentDetached(FragmentManager fm, Fragment f) {} } }
熟悉 Fragment 生命週期的同窗必定以爲很面熟,這個接口就是爲咱們提供一個 FragmentManager
所 有 Fragment
生命週期變化的回調。
小結:
能夠看到, FragmentManager
是一個抽象類,它定義了對一個 Activity/Fragment
中 添加進來的Fragment
列表、Fragment
回退棧的操做、管理。
FragmentManagerImpl
FragmentManager
定義的任務是由 FragmentManagerImpl
實現的。
主要成員:
final class FragmentManagerImpl extends FragmentManager implements LayoutInflaterFactory { ArrayList<OpGenerator> mPendingActions; Runnable[] mTmpActions; boolean mExecutingActions; ArrayList<Fragment> mActive; ArrayList<Fragment> mAdded; ArrayList<Integer> mAvailIndices; ArrayList<BackStackRecord> mBackStack; ArrayList<Fragment> mCreatedMenus; // Must be accessed while locked. ArrayList<BackStackRecord> mBackStackIndices; ArrayList<Integer> mAvailBackStackIndices; ArrayList<OnBackStackChangedListener> mBackStackChangeListeners; private CopyOnWriteArrayList<Pair<FragmentLifecycleCallbacks, Boolean>> mLifecycleCallbacks; //... }
能夠看到, FragmentManagerImpl
中定義了 添加的、活躍的。以及回退棧的列表,這和FragmentManager
的要求一致
接着還有當前的狀態,當前 Fragment
的起始 mParent
,以及 FragmentManager
的 mHost
和mContainer
。
FragmentContainer
就是一個接口,定義了關於佈局的兩個方法:
public abstract class FragmentContainer { @Nullable public abstract View onFindViewById(@IdRes int id); public abstract boolean onHasView(); }
而 FragmentHostCallback
就複雜一點了,它提供了 Fragment
須要的信息,也定義了 Fragment
宿主應該作的操做:
public abstract class FragmentHostCallback<E> extends FragmentContainer { private final Activity mActivity; final Context mContext; private final Handler mHandler; final int mWindowAnimations; final FragmentManagerImpl mFragmentManager = new FragmentManagerImpl(); //... }
咱們知道,通常來講 Fragment
的宿主就兩種:
Activity
Fragment
好比 FragmentActivity
的內部類 HostCallbacks
就實現了這個抽象類:
class HostCallbacks extends FragmentHostCallback<FragmentActivity> { public HostCallbacks() { super(FragmentActivity.this /*fragmentActivity*/); } //... @Override public LayoutInflater onGetLayoutInflater() { return FragmentActivity.this.getLayoutInflater().cloneInContext(FragmentActivity.t his); } @Override public FragmentActivity onGetHost() { return FragmentActivity.this; } ...... }
咱們再看看他對 FragmentManager
定義的關鍵方法是如何實現的。
@Override public FragmentTransaction beginTransaction() { return new BackStackRecord(this); }
beginTransaction()
返回一個新的 BackStackRecord
,咱們後面介紹。前面提到了, popBackStack()
是一個異步操做,它是如何實現異步的呢?
@Override public void popBackStack() { enqueueAction(new PopBackStackState(null, -1, 0), false); } public void enqueueAction(OpGenerator action, boolean allowStateLoss) { if (!allowStateLoss) { checkStateLoss(); } synchronized (this) { if (mDestroyed || mHost == null) { throw new IllegalStateException("Activity has been destroyed"); } if (mPendingActions == null) { mPendingActions = new ArrayList<>(); } mPendingActions.add(action); scheduleCommit(); } } private void scheduleCommit() { synchronized (this) { boolean postponeReady = mPostponedTransactions != null && !mPostponedTransactions.isEmpty(); boolean pendingReady = mPendingActions != null && mPendingActions.size() == 1; if (postponeReady || pendingReady) { mHost.getHandler().removeCallbacks(mExecCommit); mHost.getHandler().post(mExecCommit); } } }
能夠看到,調用到最後,是調用宿主中的 Handler
來發送任務的,so easy 嘛。其餘的異步執行也是相似,就不贅述了。
後退棧相關方法:
ArrayList<BackStackRecord> mBackStack; @Override public int getBackStackEntryCount() { return mBackStack != null ? mBackStack.size() : 0; } @Override public BackStackEntry getBackStackEntryAt(int index) { return mBackStack.get(index); }
能夠看到,開始事務和後退棧,返回/操做的都是 BackStackRecord
,咱們來了解了解它是何方神聖。
BackStackRecord
繼承了 FragmentTransaction
:
final class BackStackRecord extends FragmentTransaction implements FragmentManager.BackStackEntry, FragmentManagerImpl.OpGenerator {...}
先來看看 FragmentTransaction
。
FragmentTransaction
FragmentTransaction
定義了一系列對 Fragment 的操做方法:
//它會調用 add(int, Fragment, String),其中第一個參數傳的是 0 public abstract FragmentTransaction add(Fragment fragment, String tag); //它會調用 add(int, Fragment, String),其中第三個參數是 null public abstract FragmentTransaction add(@IdRes int containerViewId, Fragment fragment); //添加一個 Fragment 給 Activity 的最終實現 //第一個參數表示 Fragment 要放置的佈局 id //第二個參數表示要添加的 Fragment,【注意】一個 Fragment 只能添加一次 //第三個參數選填,能夠給 Fragment 設置一個 tag,後續可使用這個 tag 查詢它 public abstract FragmentTransaction add(@IdRes int containerViewId, Fragment fragment, @Nullable String tag); //調用 replace(int, Fragment, String),第三個參數傳的是 null public abstract FragmentTransaction replace(@IdRes int containerViewId, Fragment fragment); //替換宿主中一個已經存在的 fragment //這一個方法等價於先調用 remove(), 再調用 add() public abstract FragmentTransaction replace(@IdRes int containerViewId, Fragment fragment, @Nullable String tag); //移除一個已經存在的 fragment //若是以前添加到宿主上,那它的佈局也會被移除 public abstract FragmentTransaction remove(Fragment fragment); //隱藏一個已存的 fragment //其實就是將添加到宿主上的佈局隱藏 public abstract FragmentTransaction hide(Fragment fragment); //顯示前面隱藏的 fragment,這隻適用於以前添加到宿主上的 fragment public abstract FragmentTransaction show(Fragment fragment); //將指定的 fragment 將佈局上解除 //當調用這個方法時,fragment 的佈局已經銷燬了 public abstract FragmentTransaction detach(Fragment fragment); //當前面解除一個 fragment 的佈局綁定後,調用這個方法能夠從新綁定 //這將致使該 fragment 的佈局重建,而後添加、展現到界面上 public abstract FragmentTransaction attach(Fragment fragment);
對 fragment
的操做基本就這幾步,咱們知道,要完成對 fragment
的操做,最後還須要提交一下:
mFragmentManager.beginTransaction() .replace(R.id.fl_child, getChildFragment()) // .commit() .commitAllowingStateLoss();
事務最終的提交方法有四種:
commit()
commitAllowingStateLoss()
commitNow()
commitNowAllowingStateLoss()
它們之間的特色及區別以下:
public abstract int commit();
commit()
在主線程中異步執行,其實也是 Handler
拋出任務,等待主線程調度執行。
注意:commit()
須要在宿主 Activity
保存狀態以前調用,不然會報錯。
這是由於若是 Activity
出現異常須要恢復狀態,在保存狀態以後的 commit()
將會丟失,這和調用的初衷不符,因此會報錯。
public abstract int commitAllowingStateLoss();
commitAllowingStateLoss()
也是異步執行,但它的不一樣之處在於,容許在 Activity
保存狀態以後調用,也就是說它遇到狀態丟失不會報錯。
所以咱們通常在界面狀態出錯是能夠接受的狀況下使用它。
public abstract void commitNow();
commitNow()
是同步執行的,當即提交任務。
前面提到 FragmentManager.executePendingTransactions()
也能夠實現當即提交事務。但咱們通常建議使用 commitNow()
, 由於另外那位是一會兒執行全部待執行的任務,可能會把當前全部的事務都一會兒執行了,這有可能有反作用。
此外,這個方法提交的事務可能不會被添加到
FragmentManger
的後退棧,由於你這樣直接提交,有可能影響其餘異步執行任務在棧中的順序。
和 commit()
同樣, commitNow()
也必須在 Activity
保存狀態前調用,不然會拋異常。
public abstract void commitNowAllowingStateLoss();
同步執行的 commitAllowingStateLoss()
。
OK,瞭解了 FragmentTransaction
定義的操做,去看看咱們真正關心的、 beginTransaction()
中返回的 BackStackRecord
:
@Override public FragmentTransaction beginTransaction() { return new BackStackRecord(this); }
BackStackRecord
BackStackRecord
既是對 Fragment
進行操做的事務的真正實現,也是 FragmentManager
中的回退棧的實現:
final class BackStackRecord extends FragmentTransaction implements FragmentManager.BackStackEntry, FragmentManagerImpl.OpGenerator {...}
它的關鍵成員:
final FragmentManagerImpl mManager; //Op 可選的狀態值 static final int OP_NULL = 0; static final int OP_ADD = 1; static final int OP_REPLACE = 2; static final int OP_REMOVE = 3; static final int OP_HIDE = 4; static final int OP_SHOW = 5; static final int OP_DETACH = 6; static final int OP_ATTACH = 7; ArrayList<Op> mOps = new ArrayList<>(); static final class Op { int cmd; //狀態 Fragment fragment; int enterAnim; int exitAnim; int popEnterAnim; int popExitAnim; } int mIndex = -1; //棧中最後一個元素的索引 }
能夠看到 Op 就是添加了狀態和動畫信息的 Fragment
, mOps
就是棧中全部的 Fragment
。事務定義的方法它是如何實現的呢
先看添加一個 Fragment
到佈局 add()
的實現:
@Override public FragmentTransaction add(int containerViewId, Fragment fragment) { doAddOp(containerViewId, fragment, null, OP_ADD); return this; ...... }
能夠看到添加一個 Fragment
到佈局很簡單,概況一下就是:
修改 fragmentManager
和 ID,構形成 Op,設置狀態信息,而後添加到列表裏。
添加完了看看替換 replace
的實現:
@Override public FragmentTransaction replace(int containerViewId, Fragment fragment) { return replace(containerViewId, fragment, null); } @Override public FragmentTransaction replace(int containerViewId, Fragment fragment, String tag) { if (containerViewId == 0) { throw new IllegalArgumentException("Must use non-zero containerViewId"); } doAddOp(containerViewId, fragment, tag, OP_REPLACE); return this; }
太可怕了,也是調用上面剛提到的 doAddOp()
,不一樣之處在於第四個參數爲 OP_REPLACE
,看來以前小看了這個狀態值!
再看其餘方法的實現就很簡單了,無非就是構造一個 Op,設置對應的狀態值。
@Override public FragmentTransaction remove(Fragment fragment) { Op op = new Op(); op.cmd = OP_REMOVE; op.fragment = fragment; addOp(op); return this; } @Override public FragmentTransaction hide(Fragment fragment) { Op op = new Op(); op.cmd = OP_HIDE; op.fragment = fragment; addOp(op); return this; } @Override public FragmentTransaction show(Fragment fragment) { Op op = new Op(); op.cmd = OP_SHOW; op.fragment = fragment; addOp(op); return this; }
那這些狀態值的不一樣是何時起做用的呢?
別忘了咱們操做 Fragment
還有最後一步,提交。
看看這兩個是怎麼實現的:
@Override public int commit() { return commitInternal(false); } @Override public int commitAllowingStateLoss() { return commitInternal(true); } int commitInternal(boolean allowStateLoss) { if (mCommitted) throw new IllegalStateException("commit already called"); //... } }
前面已經介紹過了, FragmentManager.enqueueAction()
最終是使用 Handler
實現的異步執行。
如今的問題是執行的任務是啥?
答案就是 Handler
發送的任務 mExecCommit
:
代碼多了一點省略掉了,但咱們終於找到了最終的實現:Handler
異步發到主線,調度執行後,聚合、修改 Ops的狀態,而後遍歷、修改 Fragment
棧中的 View 的狀態。
前面主要是對 Fragment
的包裝類 Ops 進行一些狀態修改,真正根據 Ops 狀態進行操做在這個部分:
/** * Executes the operations contained within this transaction. The Fragment states will only * be modified if optimizations are not allowed. */ void executeOps() { final int numOps = mOps.size(); for (int opNum = 0; opNum < numOps; opNum++) { final Op op = mOps.get(opNum); final Fragment f = op.fragment; f.setNextTransition(mTransition, mTransitionStyle); switch (op.cmd) { case OP_ADD: f.setNextAnim(op.enterAnim); mManager.addFragment(f, false); break; case OP_REMOVE: f.setNextAnim(op.exitAnim); mManager.removeFragment(f); break; case OP_HIDE: f.setNextAnim(op.exitAnim); mManager.hideFragment(f); break; case OP_SHOW: f.setNextAnim(op.enterAnim); mManager.showFragment(f); break; case OP_DETACH: f.setNextAnim(op.exitAnim); mManager.detachFragment(f); break; case OP_ATTACH: f.setNextAnim(op.enterAnim); mManager.attachFragment(f); break; default: throw new IllegalArgumentException("Unknown cmd: " + op.cmd); } if (!mAllowOptimization && op.cmd != OP_ADD) { mManager.moveFragmentToExpectedState(f); } } if (!mAllowOptimization) { // Added fragments are added at the end to comply with prior behavior.mManager.moveToState(mManager.mCurState, true); } }
FragmentManager
對這些方法的實現也很簡單,修改 Fragment
的狀態值,好比remove(Fragment) :
public void removeFragment(Fragment fragment) { if (DEBUG) Log.v(TAG, "remove: " + fragment + " nesting=" + fragment.mBackStackNesting); final boolean inactive = !fragment.isInBackStack(); if (!fragment.mDetached || inactive) { if (mAdded != null) { mAdded.remove(fragment); } if (fragment.mHasMenu && fragment.mMenuVisible) { mNeedMenuInvalidate = true; } fragment.mAdded = false; //設置屬性值 fragment.mRemoving = true; } }
代碼很長,先省略掉......但作的事情很簡單:
- 根據狀態調用對應的生命週期方法
- 若是是新建立的,就把佈局添加到
ViewGroup
中
OK,看完這篇文章,相信對開頭提出的問題你已經有了答案,這裏再總結一下。Fragment、FragmentManager、FragmentTransaction
關係
Fragment
view, containerView, fragmentManager,<br/>childFragmentManager
等信息FragmentManager
Activity/Fragment
中 添加進來的 Fragment
列表、Fragment 回退棧的操做、管理方法FragmentImpl
中FragmentTransaction
Fragment
添加、替換、隱藏等操做,還有四種提交方法BackStackRecord
中Fragment
如何實現佈局的添加替換
經過得到當前 Activity/Fragment
的FragmentManager/ChildFragmentManager
,進而拿到事務的實
現類 BackStackRecord
,它將目標 Fragment
構形成 Ops(包裝Fragment
和狀態信息),而後提交給FragmentManager
處理。
若是是異步提交,就經過 Handler
發送 Runnable
任務,FragmentManager
拿到任務後,先處理 Ops
狀態,而後調用 moveToState()
方法根據狀態調用 Fragment
對應的生命週期方法,從而達到Fragment 的添加、佈局的替換隱藏等。
下面這張圖從下往上看就是一個 Fragment
建立經歷的方法:
Fragment
的原理也比較簡單,Fragment
內部有一個 childFragmentManager
,經過它管理子 Fragment
。
在添加子 Fragment
時,把子 Fragment
的佈局 add 到父 Fragment
便可
因爲不少代碼太長了,敲了一下午,眼睛和手都酸了,對這樣感興趣的能夠拿這份筆記本身研究,有不懂的歡迎留言
知識彙總的PDF相關內容後續GitHub更新,想衝擊金三銀四的小夥伴能夠找找看看,歡迎star
(順手留下GitHub連接,須要獲取相關面試等內容的能夠本身去找)
https://github.com/xiangjiana/Android-MS