2014-04-18 75 views
2

下面是一段測試代碼,用於理解片段處理。第一個輸出是記錄片段列表。有2個:GameMenuFragment和Game1Fragment0。 然後記錄返回堆棧。它包含Game1Fragment0。 然後彈出後退堆棧。所以後來堆棧日誌顯示它沒有任何東西。 Bueno。 但是現在,通過片段列表再次循環,而size()仍然回到2,它在NPE上崩潰,因爲(顯然)Game1Fragment0不存在。
因此,顯然,彈出後退堆棧已從片段列表中刪除該片段,但大小仍然爲2. 有人可以解釋嗎?片段列表和BackStackEntry動態

輸出:
Game1Fragment(1617):fragmentList大小:2
Game1Fragment(1617):fragmentList:2131492865:GameMenuFragment
Game1Fragment(1617):fragmentList:2131492865:Game1Fragment0
Game1Fragment(1617):getBackStackEntryCount :1(從0開始)
Game1Fragment(1617):BackStackEntry:Game1Fragment0
Game1Fragment(1617):getBackStackEntryCount2:0(從0開始)
Game1Fragment(1617):fragmentList2大小:2
Game1Fragment(1617):fragmentList2:2131492865:GameMenuFragment
AndroidRuntime(1617):致命異常:主要
AndroidRuntime(1617):顯示java.lang.NullPointerException

FragmentManager fragmentManager = fragmentActivity.getSupportFragmentManager(); 

List<Fragment> fragmentList = fragmentManager.getFragments(); 
Log.w("Game1Fragment", "fragmentList size: " + fragmentList.size()); 
for (Fragment fragment : fragmentList) { 
    Log.w("Game1Fragment", "fragmentList: " + fragment.getId() + " : "+ fragment.getTag()); 
} 

Log.w("Game1Fragment", "getBackStackEntryCount: " + fragmentManager.getBackStackEntryCount() + " (0-based)"); 
for(int entry = 0; entry < fragmentManager.getBackStackEntryCount(); entry++){ 
    Log.w("Game1Fragment", "BackStackEntry: " + fragmentManager.getBackStackEntryAt(entry).getName()); 
} 

fragmentManager.popBackStackImmediate(); // pop Game1Fragment0 

Log.w("Game1Fragment", "getBackStackEntryCount2: " + fragmentManager.getBackStackEntryCount() + " (0-based)"); 
for(int entry = 0; entry < fragmentManager.getBackStackEntryCount(); entry++){ 
    Log.w("Game1Fragment", "BackStackEntry: " + fragmentManager.getBackStackEntryAt(entry).getName()); 
} 

List<Fragment> fragmentList2 = fragmentManager.getFragments(); 
Log.w("Game1Fragment", "fragmentList2 size: " + fragmentList2.size()); 
for (Fragment fragment : fragmentList2) { 
    Log.w("Game1Fragment", "fragmentList2: " + fragment.getId() + " : "+ fragment.getTag()); // throws NPE on 2nd time through loop 
} 

回答

7

當創建Fragment,參考被保持它的活性Fragment陣列是ArrayList<Fragment>類型的英寸將索引分配給Fragment實例,該實例是保留引用的數組元素的索引。

現在,當Fragment被銷燬時,應從ArrayList<Fragment>中刪除該引用。如果引用Fragment的元素從ArrayList中刪除,則ArrayList中的其他元素可能會重新定位(例如,如果刪除了第0個元素,則第1個元素現在將變爲第0個元素)。這將使索引保持在Fragments實例內無效。因此元素不會從ArrayList中銷燬,而是將空值分配給該索引。

這是處理它的code。看看行mActive.set(f.mIndex, null);

void makeInactive(Fragment f) { 
    if (f.mIndex < 0) { 
     return; 
    } 

    if (DEBUG) Log.v(TAG, "Freeing fragment index " + f); 
    mActive.set(f.mIndex, null); 
    if (mAvailIndices == null) { 
     mAvailIndices = new ArrayList<Integer>(); 
    } 
    mAvailIndices.add(f.mIndex); 
    mActivity.invalidateFragment(f.mWho); 
    f.initState(); 
} 

要跟蹤發佈的指標,ArrayList<Integer> mAvailIndices維持,這將同時分配給未來的新創建Fragments指數使用。

這裏是getFraments方法,它返回mActive的代碼:當你調用fragmentManager.getFragments()第二次

@Override 
public List<Fragment> getFragments() { 
    return mActive; 
} 

,列表中包含空爲該發佈指數。因此,在使用Fragment實例之前,我們應該始終對其進行空值檢查。

從背面堆棧彈出將顛倒交易。如果它在交易中被添加,那麼它將被移除並被進一步銷燬。 這裏是一個示例應用程序的調用堆棧我曾寫過:

MainActivity$Fragment2.onDestroy() line: 53 
MainActivity$Fragment2(Fragment).performDestroy() line: 1912  
FragmentManagerImpl.moveToState(Fragment, int, int, int, boolean) line: 1008  
FragmentManagerImpl.removeFragment(Fragment, int, int) line: 1162 
BackStackRecord.popFromBackStack(boolean) line: 714 
+0

感謝您解釋列表中維護空引用的原因,而不是將它從列表中刪除。這回答了我的問題的一半。另一半涉及到後臺。從上面重申,我對後臺堆棧的理解是從後臺堆棧中彈出片段只是將其從後退按鈕的導航路徑中取出,因此我認爲這不會影響片段列表。然而我的測試代碼顯示,彈出'Fragment'會導致'Fragment'在列表中被設置爲null。 –

+0

我已經更新了我的答案。 –

+0

@mickey彈出後退堆棧後,您再次調用fragmentManager.getFragments()。這返回mActive,即活動片段的數組,其中彈出的片段被設置爲空。因此它是被接受的行爲。 –

2

好, 它看起來像你的FragmentGame1Fragment0是最初添加到後臺的唯一Fragment。 但是你的FragmentManager中有2 Fragments。 一旦彈出,您的FragmentGame1Fragment0被釋放,因爲它不再被任何人保留。 所以方法getFragments()爲這個入口返回一個空的Fragment。

從文檔:

/** 
* Get a list of all fragments that have been added to the fragment manager. 
* 
* @return The list of all fragments or null if none. 
*/ 

https://github.com/android/platform_frameworks_support/blob/master/v4/java/android/support/v4/app/FragmentManager.java

+0

因此,從後臺彈出'Game1Fragment0'使其可用於垃圾收集,並收集它,但它只是從片段列表中刪除了一半(片段列表的size()仍然是2)。 –

+1

那麼你可以在源代碼中看到getFragments()返回一個名爲mActive的ArrayList。您可以檢查此ArrayList何時被修改。在ListView中有一個空對象並不重要,只要檢查它是否爲空即可。 – Andros

+0

我明白你說關於檢查null的意思,謝謝你指出。 然而,我發現它很好奇,彈出背層堆棧導致片段有資格進行垃圾回收。 從後臺堆棧彈出只是將其從後退按鈕的導航路徑中取出。至少,這是我對它的理解。 看起來完全可能的是,人們可能希望碎片離開後臺堆棧,但仍想使用它。 –

1

我用這個:

public ArrayList<Fragment> getAllFragments() { 
    ArrayList<Fragment> lista = new ArrayList<Fragment>(); 

    for (Fragment fragment : getSupportFragmentManager().getFragments()) { 
     try { 
      fragment.getTag(); 
      lista.add(fragment); 
     } catch (NullPointerException e) { 

     } 
    } 

    return lista; 

} 
2

我爲了添加此代碼找到最後活性片段

private Fragment SearchForLastActiveFragment(){ 
    int numberOfFragments = getSupportFragmentManager().getFragments().size(); 
    Fragment fragmnet=null; 
    for(int i=numberOfFragments-1; i>0 && fragmnet == null; i--){ 
     fragmnet = getSupportFragmentManager().getFragments().get(i); 
    } 
    return fragmnet; 
}