Fragment 知識梳理(1) Fragment 原始碼解析

澤毛發表於2017-12-21

一、概述

官方是從3.0開始引入Fragment的,在文件中有對於其使用的詳細介紹,可以看出來,在它剛出現時大家對於它是十分推崇的。然而隨著使用Fragment開發的專案越來越多,在一些複雜場景下逐漸暴露出了它的一些問題,對於是否繼續使用Fragment大家也有不同的看法。目前在專案中也有大量之前留下來的使用 Fragment的程式碼, monkey有時會跑出一些莫名奇妙的問題,在這裡我們對於使用Fragment的好壞先不做評價,而是看看它內部整個的實現過程,學習它的思想,在以後出現問題的時候也方便排查,同時大家可以根據自己的需求來決定是否使用Fragment

二、Fragment事務的執行過程

在操作Fragment時,第一件是就是通過ActivitygetFragmentManger()方法得到一個FragmentManager物件:

Fragment manager = getFragmentManager();
複製程式碼

我們看一下Activity.java中的這個方法:

final FragmentController mFragments = FragmentController.createController(new HostCallbacks())

public FragmentManager getFragmentManager() {
    return mFragments.getFragmentManager();
}

class HostCallbacks extends FragmentHostCallback<Activity> {
    public HostCallbacks() {
        super(Activity.this);
    }
}
複製程式碼

可以看到getFragmentManager()呼叫的是FragmentController的介面,那麼我們再看看這個類對應的方法:

private final FragmentHostCallback<?> mHost;

public static final FragmentController createController(FragmentHostCallback<?> callbacks) {
   return new FragmentController(callbacks)
}

private FragmentController(FragmentHostCallback<?> callbacks) {
    mHost = callbacks;
}

public FragmentManager getFragmentManager() {
   return mHost.getFragmentManagerImpl();
}
複製程式碼

原來FragmentController是把一個callback給包裝了起來,真正完成任務的是FragmentHostCallback

final FragmentManagerImpl mFragmentManager = new FragmentManagerImpl();

FragmentManagerImpl getFragmentManagerImpl() {
    return mFragmentManagerImpl;
}
複製程式碼

那麼結論就是我們實際得到的是Activity#mFragments#mHost#mFragmentManagerImpl這個變數,得到這個例項之後,我們通過它的beginTransition方法得到一個事務:

FragmentTransation transation = manager.beginTransation();
複製程式碼

我們看一下這個FragmentTransation究竟是個什麼東西,FragmentManagerImplFragmentManager的一個內部類,它實現了該介面:

final class FragmentManagerImpl extends FragmentManager implements LayoutInflater.Factory2 {

public FragmentTransaction beginTransation() {
    return new BackStackRecord(this);
}

}
複製程式碼

原來這個FragmentTransation也是一個介面,它的實現是BackStackRecord,並且在每次 beginTransaction時都是返回一個新的事務物件,包括之後進行的後退操作都是通過這個事務物件來管理的,這個物件中儲存了建立它的FragmentManagerImpl例項。 BackStackRecord其實是BackStackState的一個內部類,我們平時就是通過它來進行add、remove、replace、attach、detach、hide、show等操作的,進入原始碼看一下這些操作背後都幹了些什麼:

final class BackStackRecord extends FragmentTransation implements FragmentManager.BackStackEntry, Runnable {

public FragmentTransation add(Fragment fragment, String tag) {
    doAddOp(0, fragment, tag, OP_ADD);
    return this;
}

public FragmentTransation add(int containerViewId, Fragment fragment) {
    doAddOp(containerViewId, fragment, null, OP_ADD);
    return this;
}

public FragmentTransation add(int containerViewId, Fragment fragment, String tag) {
    doAddOp(containerViewId, fragment, tag, OP_ADD);
}

public FragmentTransation replace(int containerViewId, Fragment fragment) {
    return replace(containerViewId, fragment, null);
}

public FragmentTransation replace(int containerViewId, Fragment fragment, String tag) {
    if (containerViewId == 0) {
        //replace操作必須要指定containerViewId.
   }
   doAddOp(containerViewId, fragment, tag, OP_REPLACE);
}

public FragmentTransaction remove(Fragment fragment) {
   Op op = new Op();
   op.cmd = OP_REMOVE;
   op.fragment = fragment;
   addOp(op);
}

public FragmentTransaction hide(Fragment fragment) {
   Op op = new Op();
   op.cmd = OP_HIDE;
   op.fragment = fragment;
   addOp(op);
}

public FragmentTransaction show(Fragment fragment) {
   Op op = new Op();
   op.cmd = OP_SHOW;
   op.fragment = fragment;
   addOp(op);
}

public FragmentTransaction attach(Fragment fragment) {
   Op op = new Op();
   op.cmd = OP_ATTACH;
   op.fragment = fragment;
   addOp(op);
}

public FragmentTransaction detach(Fragment fragment) {
   Op op = new Op();
   op.cmd = OP_DETACH;
   op.fragment = fragment;
   addOp(op);
}

//新建一個操作,並給操作賦值。
private void doAddOp(int containerViewId, Fragment fragment, String tag, int opcmd) {
    fragment.mFragmentManager = mManager;
   if (tag != null) {
       if (fragment.mTag != null && !tag.equals(fragment.mTag)) {
             //如果這個 fragment 之前已經有了tag,那麼是不允許改變它的。
      }
      fragment.mTag = tag;
   }
   if (containerViewId != 0) {
        if (fragment.mFragmentId != 0 && fragment.mFragmentId != containerViewId) {
            //如果這個fragment已經有了mFragmentId,那麼不允許改變它。
       }
       fragment.mContainerId = fragment.mFragmentId = containerViewId;
   }
   Op op = new Op();
   op.cmd = opcmd;
   op.fragment = fragment;
   addOp(op);
}
//把操作新增到連結串列之中。
void addOp() {
    if (mHead == null) {
        mHead = mTail = op;
    } else {
       op.prev = mTail;
       mTail.next = op;
       mTail = op;
    }
    mNumOp++;
}
}
複製程式碼

看完上面這段程式碼,我們可以得到下面這些資訊:

  • 我們呼叫這些操作之後僅僅是把這個操作新增到了BackStackRecord當中的一個連結串列。
  • 在進行attach、detach、hide、show、remove這五個操作時是不需要傳入containerViewId的,因為在執行這些操作之前這個Fragment必然已經經過了add操作,它的containerId是確定的。
  • 在執行add操作時,需要保證FragmentmTagmContainerViewId在不為空時(也就是這個 Fragment例項之前已經執行過add操作),它們和新傳入的tagcontainerViewId必須是相同的,這是因為在FragmentManagermActive列表中儲存了所有被新增進去的Fragment,而其提供的 findFragmentById/Tag正是通過這兩個欄位作為判斷的標準,因此不允許同一個例項在列表當中重複出現兩次。
  • replace操作和add類似,它增加了一個額外條件,就是containerViewId不為空,因為replace需要知道它是對哪個container進行操作,後面我們會看到replace其實是一個先removeadd的過程,因此`add 的那些判斷條件同樣適用。

那麼這個連結串列中的操作什麼時候被執行呢,看一下commit()操作:

public int commit() {
    return commitInternal(false);
}

int commitInternal(boolean allowStateLoss) {
    if (mCommitted) {
        //已經有事務處理,丟擲異常。
    }
    mCommited = true;
    if (mAddToBackState) {
         mIndex = mManager.allocBackStackIndex(this);
    } else {
         mIndex = -1;
    }
    mManager.enqueueAction(this, allowStateLoss);
}
複製程式碼

它最後呼叫了FragmentManagerenqueueAction方法,我們進去看一下里面做了什麼:

public void enqueueAction(Runnable action, boolean allowStateLoss) {    
    if (!allowStateLoss) {        
        checkStateLoss();    
    }    
    synchronized (this) {        
        if (mDestroyed || mHost == null) {            
           //如果Activity已經被銷燬,那麼丟擲異常。          
        }       
        if (mPendingActions == null) {            
            mPendingActions = new ArrayList<Runnable>();        
        }
        //因為BackStackRecord實現了Runnable介面,把加入到其中。        
        mPendingActions.add(action);        
        if (mPendingActions.size() == 1) {        
           mHost.getHandler().removeCallbacks(mExecCommit);            
           mHost.getHandler().post(mExecCommit);        
        }   
    }
}

Runnable mExecCommit = new Runnable {
    public void run() {
        execPendingActions();
    }
}

public boolean execPendingActions() {
   if (mExecutingActions) {
      //已經有操作在執行,丟擲異常
   }
   if (Looper.myLooper() != mHost.getHandler().getLooper()) {
       //不在主執行緒中執行,丟擲異常
   }
   boolean didSomething = false;
   while (true) {
      int numActions;
      sychronized(this) {
          if (mPendingActions == null || mPendingActions.size == 0) {
               break;
          }
          //期望需要執行的事務個數
          numActions = mPendingActions.size();
          if (mTmpActions == null || mTmpActions.length < numActions) {
              mTmpActions = new Runnable[numActions];
          }
          mPendingActions.toArray(mTmpActions);
          mPendingActions.clear();
          mHost.getHandler().removeCallbacks(mExecCommit);
       }
       mExecutingActions = true;
       for (int i = 0; i < numActions; i++) {
            //好吧,這裡就又回撥了BackStackRecord的run()方法
            mTmpActions[i].run();
            mTmpActions[i] = null;
       }
       mExecutingActions = false;
       didSomething = true; 
   }
}
複製程式碼
  • 在呼叫commit之後,把BackStackRecord加入到FragmentManagerImplmPendingActions中,而且通過檢視FragmentManager的原始碼也可以發現,所有對mPendingActions的新增操作只有這一個地方呼叫,
  • 當其大小為1時,會通過主執行緒的Handler post一個Runnable mExecCommit出去,當這個Runnable執行時呼叫execPendingActions()方法。
  • execPendingActions它會拿出這個mPendingActions當中的所有Runnable執行(如果它是 BackStackRecord呼叫過來的,那麼就是呼叫BackStackRecordrun方法),並把這個列表清空。在每個BackStackRecordrun方法執行時,它是通過遍歷BackStackRecord連結串列當中每個節點的cmd來判斷我們之前通過FragmentTransation加入期望執行的那些操作的。
  • 可以看出execPendingActions這個方法很關鍵,因為它決定了我們新增的操作什麼時候會被執行,我們看下還有那些地方呼叫到了它,為什麼要分析這個呢,因為我們在專案當中發現很多來自於Fragment的異常都是由我們後面談論的moveToState方法丟擲的,而moveToState執行的原因是execPendingActions被呼叫了,因此瞭解它被呼叫的時機是我們追蹤問題的關鍵,關於呼叫的時機,我們都總結在下面的註釋當中了:
<!-- Activity.java -->
final void performStart() {
    mFragments.execPendingActions(); //有可能在 Activity 的 onStart 方法執行前
    mInstrumentation.callActivityOnStart(this);
}

final void performResume() {
    performRestart();
    mFragments.execPendingActions(); //如果是從 Stopped 過來的,那麼有可能在 onStart 到 onResume 之間。
    ....
    mInstrumentation.callActivityOnResume(this);
    ....
    mFragments.dispatchResume(); 
    mFragments.execPendingActions(); //有可能在 onResume 到 onPause 之間。
}

<!-- FragmentManager.java -->
public void dispatchDestroy() { //這個呼叫在 onDestroy 之前。
    execPendingActions();
}

public boolean popBackStackImmediate() {
    executePendingTransactions();
}

public boolean popBackStackImmediate(String name, int flags) {
    executePendingTransactions();
}

複製程式碼

關於FragmentManager的討論我們先暫時放一放,看一下BackStackRecord是怎麼執行連結串列內部的操作的:

public void run() {
    Op op = mHead;
    while (op != null) {
        switch(op.cmd) {
             case OP_ADD:
                 Fragment f = op.fragment;
                 f.mNextAnim = op.enterAnim;
                 mManager.addFragment(f, false);
                 break;
            case OP_REPLACE: {
                Fragment f = op.fragment;
                int containerId = f.mContainerId;
                if (mManager.mAdded != null) {
                    //遍歷 mAdded列表,找到 containerId 相同的 old Fragment.
                    if (old == f) {
                        op.fragment = f = null;
                    } else {
                        if (op.removed == null) {
                             op.removed = new ArrayList<Fragment>();
                        }
                        op.removed.add(old); //這裡要把replace之前的記下來是為了後退棧準備的。
                        if (mAddToBackStack) {
                             old.mBackStackNesting += 1;
                        }
                        mManager.removeFragment(old, transition, transitionStyle);
                    }
                }
                if (f != null) { 
                     f.addFragment(f, false);
                }
                break;
                //後面的remove,hide,show,attach,detach就是呼叫了FragmentManager中相應的方法,沒什麼特別的,就不貼出來了
                case xxx:
                   
            }
        }
        op = op.next;
    }
    //mCurState此時為FragmentManager當前的狀態,其餘的引數不用管。    
    mManager.moveToState(mManager.mCurState, mTransition, mTransitionStyle, true); 
    if (mAddToBackStack) {
        mManager.addBackStackState(this);
    } 
}
複製程式碼

我們來看一下FragmentManagerImpl對應的addFragment等操作:

public void addFragment(Fragment fragment, boolean moveToStateNow) {
   makeActive(fragment); //加入到mActive列表中。
   if (!fragment.mDetached) {
       if (mAdd.contains(fragment)) {
           //已經在mAdded列表,丟擲異常。
       }
       mAdded.add(fragment);
       fragment.mAdded = true;
       fragment.mRemoving = false;
       if (moveToStateNow) {
           moveToState(fragment);
       }
   }
}

public void removeFragment(Fragment fragment, int transition, int transitionStyle) {
    final boolean inactive = !fragment.isInBackStack();
    if (!fragment.mDetach || inactive) {
        if (mAdded != null) {
            mAdded.remove(fragment); //從mAdded列表中移除。
        }
        fragment.mAdded = false;
        fragment.mRemoving = true;
        //這裡會根據是否加入後退棧來判斷新的狀態,最後會影響到Fragment生命週期的呼叫,如果是沒有加入後退棧的,那麼會多呼叫onDestroy、onDetach方法。
        moveToState(fragment, inactive ? Fragment.INITIALZING : Fragment.CREATED, transition, transitionStyle, false); 
    }
}

public void hideFragment(Fragment fragment, int transition, int transitionStyle) {
    if (!fragment.mHidden) {
       fragment.mHidden = true;
       if (fragment.mView != null) {
          fragment.mView.setVisibility(View.GONE);
       }
    }
    fragment.onHiddenChanged(true);
}

public void showFragment(Fragment fragment, int transition, int transitionStyle) {
    if (fragment.mHidden) {
        fragment.mHidden = false;
        if (fragment.mView != null) {
             fragment.mView.setVisibility(View.VISIBLE);
        }
        fragment.onHiddenChanged(false);
    }
}

public void detachFragment(Fragment fragment, int transition, int transitionStyle) {
    if (!fragment.mDetached) {
        fragment.mDetached = true;
        if (fragment.mAdded) {
             if (mAdded != null) {
                 mAdded.remove(fragment);
             }
             fragment.mAdded = false;
             moveToState(fragment, Fragment.CREATED, transition, transitionStyle, false);
        }
    }
}

public void attachFragment(Fragment fragment, int transition, int transitionStyle) {
    if (fragment.mDetached) {
         if (!fragment.mAdded) {
              if (mAdded.contains(fragment)) {
                  //mAdded列表中已經有,丟擲異常,
              }
              mAdded.add(fragment);
              fragment.mAdded = true;
              moveToState(fragment, mCurState, transition, transitionStyle, false);
         }
    }

}
複製程式碼

這裡的操作很多,我們需要明白以下幾點:

  • mActivemAdded的區別:mActive表示執行過add操作,並且其沒有被移除(移除表示的是在不加入後退棧的情況下被removeFragment),所有被動改變Fragment狀態的呼叫都是遍歷這個列表;而 mAdded則表示執行過add操作,並且沒有執行detachFragment/removeFragment,也就是說Fragment 是存在容器當中的,但是有可能是被隱藏的(hideFragment)。
  • attachFragment 必須保證其狀態是 mDetach 的,而該屬性的預設值是 false,只有在執行過 detach 方法後,才能執行,執行它會把 f.mView 重新加入到 container 中。
  • detachFragment 會把 f.mViewcontainer 中移除。
  • removeFragmentdetachFragment 會強制改變 Fragment 的狀態,這是因為它們需要改變 Fragment 在佈局中的位置,而這通過被動地接收 FragmentManager 狀態(即所在Activity的狀態)是無法實現的。在 removeFragment 時,會根據是否加入後退棧來區分,如果假如了後退棧,因為有可能之後會回退,而回退時遍歷的是 mActive 列表,如果把它的狀態置為Fragment.INITIALZING,那麼在 moveToState方法中就會走到最後一步,把它從mActive列表中移除,就找不到了也就無法恢復,因此這種情況下Fragment最終的狀態的和detachFragment是相同的。
  • 在加入後退棧時,detachFragment 時,會把 mDetach置為true,這種情況下之後可以執行 attachFragment操作但不能執行 addFragment操作;removeFragment 之後可以執行 addFragment操作但不能執行 attachFragment操作。
  • showFragmenthideFragment 並不會主動改變 Fragment 的狀態,它僅僅是回撥 onHiddenChanged方法,其狀態還是跟著 FragmentManager 來走。
mManager.moveToState(mManager.mCurState, mTransition, mTransitionStyle, true); 
複製程式碼

這個方法才是 Fragment的核心,它的思想就是根據 Fragment 期望進入的狀態和之前的狀態進行對比,從而呼叫 Fragment相應的生命週期,那麼問題就來,什麼是期望進入的狀態呢,我認為可以這麼理解:

  • 使用者沒有進行主動操作,但是 FragmentFragmentManager的狀態不一致,這時需要發生變化。
  • 使用者進行了主動操作,無論FragmentFragmentManager的狀態是否一致,因為 Fragment 的狀態發生了變化,因此這時也需要執行。

這裡我們為了簡便起見先看一下和生命週期有關的程式碼:


static final int INITIALIZING = 0;     // Not yet created.
static final int CREATED = 1;          // Created.
static final int ACTIVITY_CREATED = 2; // The activity has finished its creation.
static final int STOPPED = 3;          // Fully created, not started.
static final int STARTED = 4;          // Created and started, not resumed.
static final int RESUMED = 5;          // Created started and resumed.

void moveToState(int newState, int transit, int transitStyle, boolean always) {
   if (!always && mCurState == newState) {
      return;
   }
   mCurState = newState;
   if (mActive != null) {
       for (int i = 0; i < mActive.size; i++) {
           //在addFragment中通過makeActive加入進去
           Fragment f = mActive.get(i);
           moveToState(f, newState, transit, transitStyle, false);
       }
   }
}

void moveToState(Fragment f, int newState, int transit, int transitionStyle, boolean keepActive) {
    if (f.mState < newState) { //新的狀態高於當前狀態
        switch(f.mState) {
             case Fragment.INITIALZING:
                 f.onAttach(mHost.getContext());
                 if (f.mParentFragment == null) {
                     mHost.onAttachFragment(f);
                 }
                 if (!f.mRetaining) {
                     f.performCreate(f.mSavedFragmentState);
                 }
                 if (f.mFromLayout) {
                     f.mView = f.performCreateView(f.getLayoutInflater(f.mSavedFragmentState), null, f.mSavedFragmentState);
                     if (f.mHidden) f.mView.setVisibility(View.GONE);
                     f.onViewCreated(f.mView, f.mSavedFragmentState);
                 }
             case Fragment.CREATED:
                  if (newState > Fragment.CREATED) {
                      if (!f.mFromLayout) {
                          ViewGroup container = null;
                          if (f.mCotainerId != null) {
                               cotainer = (ViewGroup) mContainer.onFindViewById(f.mContainerId);
                               if (container == null && !f.mRestored) {
                                  //no view found
                               }
                          }
                          f.mContainer = container;
                          f.mView = f.performCreateView(f.getLayoutInflater(f.mSavedFragmentState), null, f.mSavedFragmentState); 
                          if (f.mView != null) {
                               f.mInnerView = f.mView;
                               if (Build.VERSION.SDK >= 11) {
                                   ViewCompact.setSaveFromParentEnable(f.mView, false);
                               } else {
                                   f.mView = NoSaveStateFrameLayout.wap(f.mView);
                               }
                               if (f.mHidden) f.mView.setVisibility(View.GONE);
                               f.onViewCreated(f.mView, f.mSavedFragmentState);
                          } else {
                               f.mInnerView = null;
                          }
                      }
                      f.performActivityCreated(f.mSavedFragmentState);
                  }
             case Fragment.ACTIVITY_CRREATED:
             case Fragment.STOPPED:
                 if (newState > Fragment.STOPPED) {
                      f.performStart();
                 }
             case Fragment.STARTED:
                 if (newState > Fragment.STARTED) {
                      f.performResume();
                 }
        }
    } else if (f.mState > newState) { //新的狀態低於當前狀態
        switch(f.mState) {
            case Fragment.RESUMED:
                if (newState < Fragment.RESUMED) {
                    f.performPause();
                }
            case Fragment.STARTED:
                if (newState < Fragment.STARTED) {
                    f.performStop();
                }
            case Fragment.STOPPED::
                if (newState < Fragment.STOPPED) {
                    f.performReallyStop();
                }
            case Fragment.ACTIVITY_CREATED:
                if (newState < Fragment.ACTIVITY_CREATED) {
                    f.performDestroyView(); //呼叫onDestory()
                    if (f.mView != null && f.mContainer != null) {
                        f.mContainer.removeView(f.mView); //把Fragment的View從檢視中移除。
                   }
                }
            case Fragment.CREATED:
                if (newState < Fragment.CREATED) {
                      if (f.mAnimationAway != null) {
                          ...
                      } else {
                           if (!f.mRetaining) {
                               f.performDestory();
                           } else {
                              f.mState = Fragment.INITIALIZING;
                           }
                           f.onDetach();
                           if (!f.mRetaining) {
                               makeInActive(f);//把Fragment從mActive中移除,並把Fragment的所有狀態恢復成初始狀態,相當於它是一個全新的Fragment。
                           } else {
                               f.mHost = null;
                               f.mParentFragment = null;
                               f.mFragmentManager = null;
                               f.mChildFragmentManager = null;
                           }
                      }
                }
        }
}
複製程式碼

moveToState方法中,我們看到了許多熟悉的面孔,就是我們平時最常談到的 Fragment 的生命週期,通過這段程式碼我們就可以對它的生命週期有更加直觀的理解。

相關文章