Android開發(fā)中,fragment 的replace方法使用問題

什么是Activity??

官方文檔解釋如下:

/**
 * An activity is a single, focused thing that the user can do.  Almost all
 * activities interact with the user, so the Activity class takes care of
 * creating a window for you in which you can place your UI with
 * {@link #setContentView}.  */

簡單解釋:
Activity是一個獨立的、可聚焦的東東,幾乎所有的Activity都與用戶進行交互。更簡單的說,我們在Android看到的每一個全屏界面,幾乎都是一個Activity。它是UI的載體。

什么是Fragment??

官方文檔解釋如下:

 /**
  * A Fragment is a piece of an application's user interface or behavior
  * that can be placed in an {@link Activity}.  Interaction with fragments
  * is done through {@link FragmentManager}, which can be obtained via
  * {@link Activity#getFragmentManager() Activity.getFragmentManager()} and
  * {@link Fragment#getFragmentManager() Fragment.getFragmentManager()}.*/

簡單解釋:
用戶接口或行為的一個區(qū)塊,它可以放到Activity中。

Fragment能做什么??

如下圖所示:
紅色區(qū)域Topfragment 、藍色區(qū)域Fragment1、白色區(qū)域Fragment2,是三個不同的區(qū)域。他們可以分別做不同的事情,比如Topfragment 播放視頻、Fragment1 輪播圖片、Fragment2 展示列表。


遇到了什么問題??

時間背景:編寫向?qū)pp的時候。
向?qū)pp包括幾大部分:
- 藍牙連接
- 語言設(shè)置
- Wifi 連接(包括幾個子界面)
- 安裝方式介紹(包括幾個子界面)

舊的代碼解決方案 :


舊方案

如上圖所示:一次性把所有Fragment全部添加進來,顯示Fragment1的時候,隱藏 Fragment2、Fragment3、Fragment4,顯示Fragment2的時候,隱藏Fragment1、Fragment3、Fragment4,等等以此類推。

接口方法: Add()、Hide()、Show()
缺點:

  • 如果有很多很多界面,一次性添加進來,需要很大的資源消耗,就會遇到我們常說的“程序很卡”
  • Show 與 Hide 的邏輯復雜,添加新界面容易出錯?。?/li>

新的代碼解決方案 :
接口方法:Replace()
每次只是初始化一個Fragment,不需要考慮跟別的Fragment的關(guān)系。

replace( ) 的接口說明

 /**
 * Replace an existing fragment that was added to a container.  This is
 * essentially the same as calling {@link #remove(Fragment)} for all
 * currently added fragments that were added with the same containerViewId
 * and then {@link #add(int, Fragment, String)} with the same arguments
 * given here. */
public FragmentTransaction replace(@IdRes int containerViewId,Fragment fragment,  String tag);

說明:先移除所有存在的Fragment, 然后把新的Fragment 添加進來。

問題:
當前容器里有4個Fragment,但是當調(diào)用replace接口之后,只有其中的2個Fragment被釋放掉了,其余的兩個還是繼續(xù)存在,why??這已經(jīng)和文檔的說明相矛盾了!!

先休息一下眼睛


Framework 代碼追查

重新創(chuàng)建App,專門來研究這個問題(replace不能刪除之前所有)
Activity中主要測試代碼如下:

    @Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    mFragmentA = new FragmentA();
    mFragmentB = new FragmentB();
    mFragmentC = new FragmentC();
    mFragmentD = new FragmentD();
    mFragmentE = new FragmentE();
    
    FragmentTransaction ft = getFragmentManager().beginTransaction();
    
    
    ft.add(R.id.container, mFragmentA);
    ft.add(R.id.container, mFragmentB);
    ft.add(R.id.container, mFragmentC);
    ft.add(R.id.container, mFragmentD);
    
    ft.commit();
    CustomLog.d(TAG , "getFragmentManager() name =" + getFragmentManager().getClass().getName());
    CustomLog.d(TAG , "FragmentTransaction name =" + ft.getClass().getName());
    
}
  public void onBtnClick(View v){
    Toast.makeText(this, "on click~~~", Toast.LENGTH_LONG).show();
    FragmentTransaction ft = getFragmentManager().beginTransaction();
    ft.replace(R.id.container, mFragmentE);
    ft.commit();
}

App運行,log如下:



說明:四個Fragment都完成了創(chuàng)建
然后點擊替換按鈕;



Log結(jié)果看來,E成功添加進來,但是只有A C 被終止掉,BD 還在。

探究transaction.replace到底做了什么

repalce()方法來自 FragmentTransaction抽象類,小伙伴們都知道抽象類是不能直接使用的,他的實現(xiàn)類是 BackStackRecord.java。

對過框架層代碼進行了對比,發(fā)現(xiàn) Android 4.0 Android 5.0 Android 6.0 ,關(guān)于這一塊的代碼,都是一樣的。

一路追查代碼,最終實現(xiàn)的地方在BackStackRecord 的run()方法里:
下面篩選了最核心的代碼,

public void run() {
...
...
  switch (op.cmd) {

            caseOP_REPLACE: {

                Fragment f = op.fragment;

                if (mManager.mAdded !=null) {

                    for (int i=0;i<mManager.mAdded.size(); i++) {

                        Fragment old =mManager.mAdded.get(i);

                        if (f == null ||old.mContainerId == f.mContainerId) {

                            if (old== f) {

                                op.fragment = f =null;

                            } else {                  

                                mManager.removeFragment(old,mTransition,mTransitionStyle); //delete  all using for

                            }

                        }

                    }

                }

                if (f !=null) {

                         mManager.addFragment(f,false); //Add the new one

                }

            } break;
   ...
   ...
}

可以看到
replace 則是先刪除fragmentmanager中所有已添加的fragment,然后再添加當前fragment;

得出結(jié)論:
replace 會先刪除所有fragment ,然后再添加傳入的fragment對象;

好,問題來了:
點擊replace按鈕只有A C 被終止掉,BD 還在,并沒有刪除全部,這又是為什么?

帶著疑問的態(tài)度進行了一次調(diào)試,在調(diào)試中終于找到了原因,問題就在這段代碼:

for (int i=0; i<mManager.mAdded.size(); i++) {
  Fragment old = mManager.mAdded.get(i);
  if (f ==null ||old.mContainerId == f.mContainerId) {
      mManager.removeFragment(old,mTransition, mTransitionStyle);
  }
}

mManager.mAdded 是一個ArrayList<Fragment> 列表,在遍歷的時候調(diào)用了mManager.removeFragment方法,而該方法調(diào)用了ArrayList的remove方法;

public void removeFragment(Fragmentfragment, int transition, inttransitionStyle) {
            mAdded.remove(fragment);
}  

也就是說在用for循環(huán)遍歷ArrayList列表的時候使用了remove;
For循環(huán)遍歷過程刪除會造成ArrayList.size()不斷變小,所以造成刪除不完全的問題;你是否也被坑過。。。


Android 7.0核心代碼如下:

public void run() {
...
...
case OP_REPLACE: {
                Fragment f = op.fragment;
                int containerId = f.mContainerId;
                if (mManager.mAdded != null) {
                    for (int i = mManager.mAdded.size() - 1; i >= 0; i--) {
                        Fragment old = mManager.mAdded.get(i);
                        if (old.mContainerId == containerId) {
                            if (old == f) {
                                op.fragment = f = null;
                            } else {
                                if (op.removed == null) {
                                    op.removed = new ArrayList<Fragment>();
                                }
                                op.removed.add(old);
                                old.mNextAnim = op.exitAnim;
                                if (mAddToBackStack) {
                                    old.mBackStackNesting += 1;
                                }
                                mManager.removeFragment(old, mTransition, mTransitionStyle);  //delete  all using for
                            }
                        }
                    }
                }
                if (f != null) {
                    f.mNextAnim = op.enterAnim;
                    mManager.addFragment(f, false);  //Add the new one 
                }
            }
            break;
      ...
      ...
}

聰明的你,應(yīng)該能看出,在Android7.0上,問題已經(jīng)修復。

問題總結(jié):

雖然在7.0已經(jīng)修復這個framework bug,但是為了向下兼容,我們在使用replace() 方法之前,還是要用remove 方法移除掉所有。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容