Fragment那點事①Fragment棧管理

文章里所有分析都是根據Android Sdk 25.3.1

在分析棧管理之前先來了解幾個基本的概念和 FragmentManager 中主要屬性代表什么意思。

FragmentManagerImpl

FragmentManagerImplFragmentManager 的實現類,具體實現了管理 Fragment 的各種操作。

BackStackRecord

BackStackRecord 實現了 FragmentTransaction 是 FragmentManager 執行操作的事務單元,一次 commit 就會產生一個 BackStackRecord 記錄,而在一個 BackStackRecord 記錄中有一個 OP 列表。popBackStack() 方法也會產生一個相應的 BackStackRecord 記錄單元。

OP

OP 是 BackStackRecord 的一個子類,表示一個事務中的一個對 Fragment 具體的操作。里面包含了操作類型、操作對象 Fragment、入棧進入退出動畫、出棧進入退出動畫。

static final class Op {
  int cmd;
  Fragment fragment;
  int enterAnim;
  int exitAnim;
  int popEnterAnim;
  int popExitAnim;
}

具體有下面幾種對 Fragment 的操作:

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;

FragmentManager 變量

  • mActive 不僅有活著的 fragments 還有在返回棧中等待被復原的 fragments。

  • mAdded 是 mActive 的子集表示活著的 fragments

  • ArrayList<OpGenerator> mPendingActions 待執行動作

  • ArrayList<BackStackRecord> mTmpRecords
    用于優化執行的臨時變量

  • ArrayList<Boolean> mTmpIsPop
    用于優化執行的臨時變量

  • ArrayList<Integer> mAvailIndices

    mActive 中可用的索引列表

  • ArrayList<BackStackRecord> mBackStack

    返回棧 BackStackRecord的返回棧

  • ArrayList <BackStackRecord> mBackStackIndices
    返回棧索引列表,用來給 BackStackRecord 分配 mIndex 需要。

  • ArrayList<Integer> mAvailBackStackIndices
    返回??捎盟饕斜?/strong>,存儲著 mBackStackIndices 列表中值為 null 的下標即可以插入 BackStackRecord 。

流程圖

流程圖

下面是最基本的 add Fragment 的代碼片段:

getSupportFragmentManager().beginTransaction()
  .add(R.id.content, fragment)
  //.remove(fragment)
  //.replace(R.id.content,fragment2)
  .commit();

通過 beginTransaction() 獲取一個 FragmentTransaction 然后調用 add() 方法,其實不管是 add() 還是 remove()replace() 等方法,最后都是調用的 doAddOp() 方法添加一個 OP 對象到 BackStackRecord.mOps 列表中。然后再調用 commit() 提交該事務。

BackStackRecord.commitInternal()

commit() 方法調用 commitInternal() 方法,在該方法中用來給每個 BackStackRecord 事務分配 mIndex 標識。如果該事務不需要加入返回棧就分配 -1 否則就調用 mManager.allocBackStackIndex(this) 返回標識。

int commitInternal(boolean allowStateLoss) {
  if (mCommitted) throw new IllegalStateException("commit already called");
  mCommitted = true;
  if (mAddToBackStack) {
    mIndex = mManager.allocBackStackIndex(this);
  } else {
    mIndex = -1;
  }
  mManager.enqueueAction(this, allowStateLoss);
  return mIndex;
}

BackStackRecord.mIndex 的管理

FragmentManager.allocBackStackIndex()

該方法用于給加入到返回棧的 BackStackRecord 分配 mIndex 下標,分配方法不是持續的遞增而是使用 mBackStackIndicesmAvailBackStackIndices 配合避免頻繁為索引列表開辟新的內存空間。從代碼片段中可知,當 mAvailBackStackIndices 為空時說明沒有可用的下標,就直接返回 mBackStackIndices.size() 作為下標并把該 BackStackRecord 添加到 mBackStackIndices 中,反之返回 mAvailBackStackIndices 末尾值作為下標并將 BackStackRecord 添加到該位置。

public int allocBackStackIndex(BackStackRecord bse) {
  synchronized (this) {
    if (mAvailBackStackIndices == null || mAvailBackStackIndices.size() <= 0) {
      if (mBackStackIndices == null) {
        mBackStackIndices = new ArrayList<BackStackRecord>();
      }
      int index = mBackStackIndices.size();
      if (DEBUG) Log.v(TAG, "Setting back stack index " + index + " to " + bse);
      mBackStackIndices.add(bse);
      return index;
    } else {
      int index = mAvailBackStackIndices.remove(mAvailBackStackIndices.size()-1);
      if (DEBUG) Log.v(TAG, "Adding back stack index " + index + " with " + bse);
      mBackStackIndices.set(index, bse);
      return index;
    }
  }
}

FragmentManager.freeBackStackIndex()

該方法是出棧的時候調用的,當 BackStackRecord 出棧后來更新返回??捎盟饕斜淼摹S上旅娴拇a可知,是吧相應位置設置成 null,然后把該下標加入可用返回棧列表的列尾。

public void freeBackStackIndex(int index) {
  synchronized (this) {
    mBackStackIndices.set(index, null);
    if (mAvailBackStackIndices == null) {
      mAvailBackStackIndices = new ArrayList<Integer>();
    }
    if (DEBUG) Log.v(TAG, "Freeing back stack index " + index);
    mAvailBackStackIndices.add(index);
  }
}

FragmentManager.setBackStackIndex()

該方法是在 restoreAllState() 方法中調用的,用來處理當 Activity 意外內存重啟后恢復 Fragment 返回棧索引列表(mBackStackIndices)和可用索引列表(mAvailBackStackIndices)的。當傳進來的 index 大于 N 時,遍歷從 N 開始到 index 的位置 add(null) 并把下標添加到 mAvailBackStackIndices 中,反之直接把 BackStackRecord 填充到 index 位置。

public void setBackStackIndex(int index, BackStackRecord bse) {
  synchronized (this) {
    if (mBackStackIndices == null) {
      mBackStackIndices = new ArrayList<BackStackRecord>();
    }
    int N = mBackStackIndices.size();
    if (index < N) {
      if (DEBUG) Log.v(TAG, "Setting back stack index " + index + " to " + bse);
      mBackStackIndices.set(index, bse);
    } else {
      while (N < index) {
        mBackStackIndices.add(null);
        if (mAvailBackStackIndices == null) {
          mAvailBackStackIndices = new ArrayList<Integer>();
        }
        if (DEBUG) Log.v(TAG, "Adding available back stack index " + N);
        mAvailBackStackIndices.add(N);
        N++;
      }
      if (DEBUG) Log.v(TAG, "Adding back stack index " + index + " with " + bse);
      mBackStackIndices.add(bse);
    }
  }
}

FragmentManager.execPendingActions()

commitInternal() 中分配完 mIndex 標識后調用 mManager.enqueueAction(this, allowStateLoss) 把 BackStackRecord 添加到 mPendingActions 待執行動作列表中,然后通過 Handler 發送 Runnable 消息到主線程消息隊列中等待被執行。

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

在 Runnable 中執行 execPendingActions() 方法,該方法代碼片段如下。分為幾點來分析:

  1. mTmpRecords,mTmpIsPop 前者用來臨時存儲所有待執行的動作(mPendingActions)生成的 BackStackRecord,后者用來存儲 BackStackRecord 是否為出棧。
  2. generateOpsForPendingActions 方法遍歷 mPendingActions 調用 OpGenerator.generateOps() 方法生成 BackStackRecord 添加到 mTmpRecords 并把是否為出棧添加到 mTmpIsPop 中,然后調用 mPendingActions.clear() 把待執行動作列表清空,因為已經被轉化為 BackStackRecord 列表了。
  3. 調用 optimizeAndExecuteOps(mTmpRecords, mTmpIsPop) 方法優化并執行 BackStackRecord 列表。
  4. 最后調用 cleanupExec() 清空列表。
/**
* Only call from main thread!
*/
public boolean execPendingActions() {
  ensureExecReady(true);

  boolean didSomething = false;
  while (generateOpsForPendingActions(mTmpRecords, mTmpIsPop)) {
    mExecutingActions = true;
    try {
      optimizeAndExecuteOps(mTmpRecords, mTmpIsPop);
    } finally {
      cleanupExec();
    }
    didSomething = true;
  }
  doPendingDeferredStart();
  return didSomething;
}

OpGenerator.generateOps()

關于該接口的實現有兩個地方,即 BackStackRecord.generateOps()PopBackStackState.generateOps() 兩處。前者為 非 pop 出棧,對應 commit() 操作提交的各種類型 BackStackRecord,后者為 pop 出棧,對應 popBackStack() 等方法提交的 PopBackStackState。由下面 BackStackRecord.generateOps() 的實現可知,如果設置了 addToBackStack 是在這里把 BackStackRecord 添加到返回棧 mBackStack 中去的。

//BackStackRecord.generateOps()
@Override
public boolean generateOps(ArrayList<BackStackRecord> records, ArrayList<Boolean> isRecordPop) {
  if (FragmentManagerImpl.DEBUG) {
    Log.v(TAG, "Run: " + this);
  }

  records.add(this);
  isRecordPop.add(false);
  if (mAddToBackStack) {
    mManager.addBackStackState(this);
  }
  return true;
}

PopBackStackState.generateOps() 源碼先不分析,等到出棧的時候再詳細分析。

FragmentManager.optimizeAndExecuteOps()

該方法合并相鄰的允許被優化(mAllowOptimization = true)的 BackStackRecord 。比如有一個 Transaction 添加到了返回棧,然后另一個 Transaction 是要把前面的 Transaction pop 出棧,這兩個 Transaction 將會被優化。

比如下面的代碼片段,不允許優化時 a1Fragment 被 add 進 FragmentManager,走一遍 Fragment 的生命周期,然后 popBackStack() 再把 Fragment remove 掉。而優化后不會再執行 Fragment 的生命周期就被直接 remove 掉。

getSupportFragmentManager().beginTransaction()
  .add(R.id.content, a1Fragment)
  .setAllowOptimization(true)
  .addToBackStack("a1")
  .commit();
getSupportFragmentManager().popBackStack();

FragmentManager.executeOpsTogether()

在該方法中會執行 expandReplaceOps 方法把 replace 替換(目標 fragment 已經被 add )成相應的 remove 和 add 兩個操作,或者(目標 fragment 沒有被 add )只替換成 add 操作。

if (!isPop) {
   record.expandReplaceOps(mTmpAddedFragments);
} else {
   record.trackAddedFragmentsInPop(mTmpAddedFragments);
}

然后調用 executeOps 方法,根據不同 Op.cmd 調用不同的方法對 fragment 進行操作。我們就先來分析 add 操作的流程。會調用 mManager.addFragment(f, false) 方法。在 addFragment 方法中先執行 makeActive 方法,把 add 進來的 fragment 添加到 mActive 列表中,看這個分配 index 的步驟是不是很熟悉?沒錯和給 BackStackRecord 分配 index 的步驟非常像,即移除 fragment 的時候是把相應下標處置 null,然后把該下標保存在 mAvailIndices 列表中,添加 fragment 的時候就會去這個列表中取出最后一個下標。在 SDK23 版本時因為就因為這種分配方法導致了一個 bug :①添加 A1,B1,C1 三個Fragment。② remove 掉這三個 fragment 并且 add 進去 a2,b2 兩個 fragment,期望是要顯示 b2 但是實際情況確實顯示的 a2。在 Sdk >= 24 中這個問題已經被修復。

void makeActive(Fragment f) {
    if (f.mIndex >= 0) {
        return;
    }

    if (mAvailIndices == null || mAvailIndices.size() <= 0) {
        if (mActive == null) {
            mActive = new ArrayList<Fragment>();
        }
        f.setIndex(mActive.size(), mParent);
        mActive.add(f);

    } else {
        f.setIndex(mAvailIndices.remove(mAvailIndices.size()-1), mParent);
        mActive.set(f.mIndex, f);
    }
    if (DEBUG) Log.v(TAG, "Allocated fragment index " + f);
}

然后當 fragment 沒有 Detached 的時候執行下面邏輯,由此可見 mAddedmActive 的一個子集,沒有detached 的 fragment 才會被加入到 mAdded 。

if (!fragment.mDetached) {
    if (mAdded.contains(fragment)) {
        throw new IllegalStateException("Fragment already added: " + fragment);
    }
    mAdded.add(fragment);
    ...        
}

然后執行 moveToState 方法改變 fragment 的狀態(添加 fragment 的不同情況會有所不同,但最后都是要 moveToState 改變 fragment 的狀態的,進而觸發 fragment 的生命周期函數) 。

moveToState(Fragment f, int newState, int transit, int transitionStyle,
            boolean keepActive)

在moveToState() 方法中有下面這段代碼,我查看了 SDK 23 的 moveToState() 方法的源碼是遍歷的 mActive列表里的 fragments,而現在是遍歷的 mAdded,因為由上面提到的bug可知 mActive fragments 有可能已經亂序了所以在 SDK 25里是不會出現亂序的這個 bug 了。

// Must add them in the proper order. mActive fragments may be out of order
if (mAdded != null) {
    final int numAdded = mAdded.size();
    for (int i = 0; i < numAdded; i++) {
        Fragment f = mAdded.get(i);
        moveFragmentToExpectedState(f);
        if (f.mLoaderManager != null) {
            loadersRunning |= f.mLoaderManager.hasRunningLoaders();
        }
    }
}

BackStackRecord 出棧

BackStackRecord 出棧的方法有如下幾個:

  • popBackStack()
  • popBackStackImmediate()
  • popBackStack(int id/String name, int flags)
  • popBackStackImmediate(int id/String name, int flags)

popBackStack()

PopBackStackState 實現了 OpGenerator 接口,具體實現如下:

  • 參數 records 用來存放出棧的 BackStackRecord
  • 參數 isRecordPop 用來存放相應 BackStackRecord 是否為出棧(顯然為 true)
  • 參數 name 表示出棧到相應 name 的 BackStackRecord
  • 參數 id 表示出棧到相應 id 的 BackStackRecord
  • 參數 flags (0 或者 POP_BACK_STACK_INCLUSIVE)
  • POP_BACK_STACK_INCLUSIVE 如果參數 flags ==POP_BACK_STACK_INCLUSIVE 并且設置了 name 或者 id 那么,所有符合該 name 或者 id 的 BackStackRecord 都將被匹配,直到遇到一個不匹配的或者到達了棧底,然后出棧所有 BackStackRecord 直到最終匹配到的下標位置。否則只匹配第一次 name 或者 id 相符的 BackStackRecord,然后出棧所有 BackStackRecord 直到但不包括匹配到的下標位置。
    boolean popBackStackState(ArrayList<BackStackRecord> records, ArrayList<Boolean> isRecordPop,
            String name, int id, int flags) {
        if (mBackStack == null) {
            return false;
        }
        if (name == null && id < 0 && (flags & POP_BACK_STACK_INCLUSIVE) == 0) {
            int last = mBackStack.size() - 1;
            if (last < 0) {
                return false;
            }
            records.add(mBackStack.remove(last));
            isRecordPop.add(true);
        } else {
            int index = -1;
            if (name != null || id >= 0) {
                // If a name or ID is specified, look for that place in
                // the stack.
                index = mBackStack.size()-1;
                while (index >= 0) {
                    BackStackRecord bss = mBackStack.get(index);
                    if (name != null && name.equals(bss.getName())) {
                        break;
                    }
                    if (id >= 0 && id == bss.mIndex) {
                        break;
                    }
                    index--;
                }
                if (index < 0) {
                    return false;
                }
                // flags&POP_BACK_STACK_INCLUSIVE 位與運算 
                if ((flags&POP_BACK_STACK_INCLUSIVE) != 0) {
                    index--;
                    // Consume all following entries that match.
                    while (index >= 0) {
                        BackStackRecord bss = mBackStack.get(index);
                        if ((name != null && name.equals(bss.getName()))
                                || (id >= 0 && id == bss.mIndex)) {
                            index--;
                            continue;
                        }
                        break;
                    }
                }
            }
            if (index == mBackStack.size()-1) {
                return false;
            }
            for (int i = mBackStack.size() - 1; i > index; i--) {
                records.add(mBackStack.remove(i));
                isRecordPop.add(true);
            }
        }
        return true;
    }

如果 返回棧 mBackStack 為空就終止出棧操作并返回 false,當name == null && id < 0 && (flags & POP_BACK_STACK_INCLUSIVE) == 0 (調用的是popBackStack()方法)時,把返回棧最后一個 BackStackRecord出棧。當 name 或者 id 被指定的時候,倒序遍歷 mBackStack ,如果遇到 name 或者 id 相符就退出循環,此時 index 為第一次匹配到的下標,如果flags==POP_BACK_STACK_INCLUSIVE 繼續遍歷返回棧,直至棧底或者遇到不匹配的跳出循環。最后出棧所有 BackStackRecord。

popBackStack() 方法調用 enqueueAction 方法,添加出棧動作操作到隊列中,這樣又返回到 BackStackRecord 入棧流程那里的 generateOpsForPendingActions() 方法,分別調用 BackStackRecord.generateOps(),PopBackStackState.generateOps() 實現。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,825評論 6 546
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,814評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 178,980評論 0 384
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 64,064評論 1 319
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,779評論 6 414
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,109評論 1 330
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,099評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,287評論 0 291
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,799評論 1 338
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,515評論 3 361
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,750評論 1 375
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,221評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,933評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,327評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,667評論 1 296
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,492評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,703評論 2 380

推薦閱讀更多精彩內容