FragmentManager实际上是用链表来管理Fragment的

Chante15Y 8年前
   <p>之前一直有一个误解,认为FragmentManager是用栈来管理Fragment的,直到今天深扒了Framework源码后,才发现一直搞错了。可能也有人跟我有一样的误解,希望这篇文章能让你树立正确的观点。</p>    <h2><strong>一、我是怎么开始怀疑自己原来的观点的</strong></h2>    <p>今天在复习Fragment相关知识的时候,突然想到一个有意思的话题:假设在Activity的界面上有一个FrameLayout,它的id是container1,那么,能不能在这个container1中添加多个Fragment呢?</p>    <p>于是果断建立一个Demo项目进行实验(文末有源码地址),在container1中添加了一个Fragment1,然后又添加了一个Fragment2,发现没有报错!当然,此时还不能确定两个Fragment都添加到了container1中了。</p>    <p>紧接着,我调用FragmentManager的findFragmentById(R.id.container1)方法,测试发现,返回的是Fragment2。</p>    <p>从结果来看,貌似Fragment1没有在container1中,于是我又做了一个实验,那就是在添加Fragment1时为它添加了一个Tag,即"fragment1",然后,我再次调用FragmentManager的findFragmentByTag("fragment1"),果然,查找到了Fragment1,这说明Fragment1和Fragment2都添加成功了。</p>    <p><strong>那么问题来了!!</strong></p>    <p>很多文章和书上都说,FragmentManager是靠container的id来区分Fragment的,现在Fragment1和Fragment2是同一个container id 。FragmentManager是怎么管理它们的呢?</p>    <p>到这一步,我还是觉得用栈可以解释通,后添加的Fragment在栈顶,之前添加的在下面,只让栈顶的Fragment显示出来,在调用findFragmentById时也只返回栈顶的Fragment。</p>    <p>于是,我又做了一个实验,上面的container1不是一个FrameLayout吗,我把它改成vertical的LinearLayout,再次运行Demo项目,WORD 天,Fragment1和Fragment2的界面都显示出来了,如下所示:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/c74c63d9f90039839d5f5abebfdccb64.png"></p>    <p style="text-align:center">two_fragment_in_one_container.PNG</p>    <p>到这一步,我已经很怀疑FragmentManager会用栈来管理Fragment了。</p>    <p>然后,我进一步想,一个Activity的界面上可以有许多个FrameLayout,它们可以作为container2,container3......,每一个container中都可以添加许多的Fragment,如果FragmentManager使用一个栈来管理这么多的Fragment,遇到remove一个非栈顶的Fragment时,岂不费劲死!</p>    <p>至此,我已经不相信自己之前的观点了,所谓一言不和,就扒源码,我开始了自己的探索路程。</p>    <h2><strong>二、先找到FragmentManager</strong></h2>    <p>因为在Activitty中是通过getSupportFragmentM这个方法获取FragmentManager的实例的,我毫不留情地在这个方法上点击了。这种感觉就像潜水,现在的深度是5米,感觉棒棒哒!</p>    <p>眼前的景象是:我进入了FragmentActivity内部,并且看到了这个方法:</p>    <pre>  <code class="language-java">/**       * Return the FragmentManager for interacting with fragments associated       * with this activity.       */      public FragmentManager getSupportFragmentManager() {          return mFragments.getSupportFragmentManager();      }</code></pre>    <p>我二话不说,继续点,再点,再点,终于潜到了一个完全没有光的深度,号黑啊,我打开了头顶上的探照灯,发现自己到了FragmentManager的一个内部类:FragmentManagerImpl</p>    <p>哦,终于见到了FragmentManager的真身啦!</p>    <h2><strong>三、再找到 FragmentTransaction</strong></h2>    <p>稍加停留后,我就继续往下潜了,我找到FragmentManagerImpl的beginTransaction方法,勇敢地点击了进去,经过几次点击,终于找到了FragmentTransaction的真身,原来是一个叫做BackStackRecord的类。</p>    <p>我找到它的add()方法,继续往下点,来到了一个私有方法处,该方法的核心代码如下:</p>    <pre>  <code class="language-java">private void doAddOp(int containerViewId, Fragment fragment, String tag, int opcmd){            ...          fragment.mTag = tag;            ...          fragment.mContainerId = fragment.mFragmentId = containerViewId;            ...          Op op = new Op();          op.cmd = opcmd;          op.fragment = fragment;          addOp(op);    }</code></pre>    <p>看到这几行代码,我已经觉得不虚此潜了!但对真理的崇高追求让我勇敢地在addOp(op)上点了进去......</p>    <h2><strong>四、发现真相!!</strong></h2>    <p>进到addOp方法内部,我看到这样几行闪闪发光的代码:</p>    <pre>  <code class="language-java">//这里的op就是Fragment的载体  void addOp(Op op) {          if (mHead == null) {              mHead = mTail = op;          } else {              op.prev = mTail;              mTail.next = op;              mTail = op;          }          op.enterAnim = mEnterAnim;          op.exitAnim = mExitAnim;          op.popEnterAnim = mPopEnterAnim;          op.popExitAnim = mPopExitAnim;          mNumOp++;      }</code></pre>    <p>看到mHead、mTail字样,终于确定了,FragmentManager是用链表来管理Fragment的。</p>    <p>不是用栈</p>    <p>不是用栈</p>    <p>不是用栈</p>    <h2><strong>五、进一步探索findFragmentById方法</strong></h2>    <p>文章开头的例子中,我们看到findFragmentById返回了Fragment2,于是我很好奇,这个方法是怎么实现的,潜一次水不容易,我决定一并弄清它的真相。</p>    <p>这个方法的代码是这样的:</p>    <pre>  <code class="language-java">@Override      public Fragment findFragmentById(int id) {  /*mAdded是一个ArrayList<Fragment>,里面存的是该Activity界面上所有  的container中最新添加的Fragment*/          if (mAdded != null) {              // First look through added fragments.              for (int i=mAdded.size()-1; i>=0; i--) {                  Fragment f = mAdded.get(i);                  if (f != null && f.mFragmentId == id) {                      return f;                  }              }          }  /* mActive也是一个ArrayList<Fragment>,里面保存该Activity上所有添加  过的Fragment,在我们上文提到的Fragment1和Fragment2同时显示在一个  LinearLayout的例子中,两个Fragment在mActive中,但是只有Fragment2  在mAdded,这也就解释了为什么findFragmentById会返回Fragment2啦,  因为是先从mAdded查找的,而且是倒着查的*/          if (mActive != null) {              // Now for any known fragment.              for (int i=mActive.size()-1; i>=0; i--) {                  Fragment f = mActive.get(i);                  if (f != null && f.mFragmentId == id) {                      return f;                  }              }          }          return null;      }</code></pre>    <p>至此,我的潜水过程完全结束了。有句话讲:太阳底下没有新鲜事,现在我觉得源码面前没有秘密。</p>    <p> </p>    <p> </p>    <p>来自:http://www.jianshu.com/p/2412fca60cfe</p>    <p> </p>