您好,登錄后才能下訂單哦!
今天小編就為大家帶來一篇介紹Fragment的常見面試題和答案的文章。小編覺得挺實用的,為此分享給大家做個參考。一起跟隨小編過來看看吧。
實現很簡單,創建一個的布局,然后在 Activity
里點擊時替換 Fragment
mFragmentManager = getSupportFragmentManager();
mFragmentManager.beginTransaction()
.replace(R.id.fl_content, fragment)
.commitAllowingStateLoss();
代碼很簡單,核心就三步:
- 創建
Fragment
- 獲取
FragmentManager
- 調用事務,添加、替換
先來看看 FragmentManager
。
####二丶 FragmentManager
public abstract class FragmentManager {...}
FragmentManager
是一個抽象類,定義了一些和 Fragment 相關的操作和內部類/接口。
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.內部類/接口:
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
中
以上就是Fragment常見面試題和答案的詳細內容了,看完之后是否有所收獲呢?如果想了解更多相關內容,歡迎關注億速云行業資訊!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。