什么是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 方法移除掉所有。