1. 每個(gè)事務(wù)(FragmentTranscation)只能被commit一次
承接Fragment進(jìn)階 - 基本用法中“Fragment動(dòng)態(tài)加載”的事例,如果界面里有多個(gè)Fragment需要提交,而且我不想一次性全部提交,而是分幾次提交...(事例代碼如下)
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
getSupportFragmentManager().beginTransaction().add(R.id.fl_main, new ContentFragment(), null).commit();
getSupportFragmentManager().beginTransaction().add(R.id.fl_main, new ContentFragment(), null).commit();
getSupportFragmentManager().beginTransaction().add(R.id.fl_main, new ContentFragment(), null).commit();
}
}
運(yùn)行程序,沒(méi)有問(wèn)題程序正常,然后看下此時(shí)的視圖結(jié)構(gòu):

我們add了3次,我們的Activty里有了3個(gè)Fragment的視圖(這也是小編踩坑之后才成功的)。
在最初進(jìn)行編寫(xiě)代碼的時(shí)候,小編遇到一個(gè)坑,最初的代碼是這樣寫(xiě)的:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.add(R.id.fl_main, new ContentFragment(), null).commit();
transaction.add(R.id.fl_main, new ContentFragment(), null).commit();
transaction.add(R.id.fl_main, new ContentFragment(), null).commit();
}
}
我們把事務(wù)對(duì)象提取了出來(lái)(相信這是有潔癖的程序員的愛(ài)好),進(jìn)行了多次提交,運(yùn)行一下結(jié)果程序崩潰了(崩潰日志如下)...
Caused by: java.lang.IllegalStateException: commit already called
日志告訴我們commit已經(jīng)被調(diào)用了,看來(lái)每個(gè)事務(wù)對(duì)象只能commit一次...
但是之前的寫(xiě)法是沒(méi)問(wèn)題的,由此可以推測(cè):每次調(diào)用getSupportFragmentManager().beginTransaction()獲取的都是一個(gè)新的實(shí)例。
查看源碼,證實(shí)了我們的推測(cè)...

原來(lái),我們開(kāi)啟的每一個(gè)事務(wù)都是一個(gè)回退棧記錄(BackStackRecord是FragmentTransaction的一個(gè)具體實(shí)現(xiàn)類(lèi))
接下來(lái),我們研究下BackStackRecord,看看commit異常是怎么拋出的。

BackStackRecord的commit操作會(huì)調(diào)用一個(gè)commitInternal方法。
mCommitted記錄了提交狀態(tài),我們的第1次提交mCommitted被置為true,第2次提交就拋異常了(整個(gè)源碼中對(duì)mCommitted進(jìn)行賦值的地方僅此1處(628行))。
結(jié)論:每個(gè)事務(wù)對(duì)象只能被commit一次。
2. Activity執(zhí)行完onSaveInstanceState()方法后不能再執(zhí)行commit()方法
小編想手動(dòng)控制Fragment的添加顯示,在Activity被銷(xiāo)毀的時(shí)候?qū)ragment移除掉(代碼如下)
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
getSupportFragmentManager().beginTransaction().add(R.id.fl_main, new ContentFragment(), "ContentFragment").commit();
}
@Override
protected void onDestroy() {
getSupportFragmentManager().beginTransaction().remove(getSupportFragmentManager().findFragmentByTag("ContentFragment")).commit();
super.onDestroy();
}
}
運(yùn)行下程序,發(fā)現(xiàn)退出時(shí)程序崩潰了...我們得到如下崩潰日志:
Caused by: java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
異常日志告訴我們:不能在onSaveInstanceState()方法被執(zhí)行之后調(diào)用commit()方法。
那么我們就提到onSaveInstanceState()之前調(diào)用,代碼如下:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
getSupportFragmentManager().beginTransaction().add(R.id.fl_main, new ContentFragment(), "ContentFragment").commit();
}
@Override
protected void onSaveInstanceState(Bundle outState) {
getSupportFragmentManager().beginTransaction().remove(getSupportFragmentManager().findFragmentByTag("ContentFragment")).commit();
super.onSaveInstanceState(outState);
}
}
運(yùn)行下程序,發(fā)現(xiàn)退出程序不蹦了...但又有一個(gè)新問(wèn)題,手機(jī)滅屏?xí)r也會(huì)調(diào)用onSaveInstanceState方法,在打開(kāi)界面發(fā)現(xiàn)界面變成空白了...這種方法(手動(dòng)移除Fragment)顯然不行,這點(diǎn)需要注意。
3. commitAllowingStateLoss()方法
FragmentTranscation 給我們提供了另外一個(gè)方法 commitAllowingStateLoss(),從名字我們也能看出這個(gè)方法的作用:允許狀態(tài)丟失的提交。
看下官方文檔的解釋?zhuān)覀兙湍苊靼自蛄?
FragmentTransaction API commitAllowingStateLoss() (需要翻墻)

原來(lái),在Activity的狀態(tài)被保存之后的提交操作是沒(méi)有被Activity所記錄的,恢復(fù)時(shí)也就沒(méi)辦法恢復(fù)這些提交操作,所以官方文檔稱(chēng)這個(gè)方法是一個(gè)危險(xiǎn)的操作。如果這些提交操作不是很重要,丟不丟失無(wú)所謂的話你就可以使用commitAllowingStateLoss()這個(gè)方法了。
我們調(diào)整下代碼,用commitAllowingStateLoss()代替commit()方法(如下):
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
getSupportFragmentManager().beginTransaction().add(R.id.fl_main, new ContentFragment(), "ContentFragment").commit();
}
@Override
protected void onDestroy() {
getSupportFragmentManager().beginTransaction().remove(getSupportFragmentManager().findFragmentByTag("ContentFragment")).commitAllowingStateLoss();
super.onDestroy();
}
}
運(yùn)行下代碼,現(xiàn)在看起來(lái)沒(méi)問(wèn)題了...但我們知道如果出現(xiàn)了Activity異常銷(xiāo)毀重啟的情況,我們的移除操作在恢復(fù)時(shí)就丟失了,我們的Fragment將會(huì)出現(xiàn)重復(fù)疊加的問(wèn)題。
之前小編在Fragment進(jìn)階 - FragmentTransaction詳解最開(kāi)始的時(shí)候闡述過(guò)這個(gè)問(wèn)題,給出過(guò)最佳的解決辦法,就不再重復(fù)說(shuō)明了。
最后,我們從源碼上看下commit()和commitAllowingStateLoss()有什么區(qū)別。
下面為BackStackRecord(FragmentTransaction的具體實(shí)現(xiàn)類(lèi))的部分源碼。
final class BackStackRecord extends FragmentTransaction implements
FragmentManager.BackStackEntry, Runnable {
......
public int commit() {
return commitInternal(false);
}
public int commitAllowingStateLoss() {
return commitInternal(true);
}
int commitInternal(boolean allowStateLoss) {
......
mManager.enqueueAction(this, allowStateLoss);
......
}
......
}
我們看到commit()和commitAllowingStateLoss()都調(diào)用了commitInternal(boolean allowStateLoss)這個(gè)方法只不過(guò)傳入?yún)?shù)不同而已(commit()傳入的false,commitAllowingStateLoss()傳入的true),接下來(lái)會(huì)調(diào)用FragmentManagerImpl(FragmentManager的具體實(shí)現(xiàn)類(lèi))的enqueueAction方法。
final class FragmentManagerImpl extends FragmentManager implements LayoutInflaterFactory {
......
public void enqueueAction(Runnable action, boolean allowStateLoss) {
if (!allowStateLoss) {
checkStateLoss();
}
......
}
.....
}
我們看到,如果commitAllowingStateLoss()傳的是true所以忽略掉了檢查,commit()傳的是false,所以進(jìn)行了檢查。
最后我們看下檢查的方法checkStateLoss():
final class FragmentManagerImpl extends FragmentManager implements LayoutInflaterFactory {
......
private void checkStateLoss() {
if (mStateSaved) {
throw new IllegalStateException(
"Can not perform this action after onSaveInstanceState");
}
......
}
......
}
看來(lái)我們之前的異常就是在這拋出的,也算是找到根源了。
4. commit()方法被調(diào)用時(shí)并不會(huì)立即執(zhí)行
舉個(gè)簡(jiǎn)單的例子:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
getSupportFragmentManager().beginTransaction().add(R.id.fl_main, new ContentFragment(), null).commit();
Log.e("TAG", "MainActivity onCreated");
}
@Override
public void onAttachFragment(Fragment fragment) {
super.onAttachFragment(fragment);
Log.e("TAG", "MainActivity onAttachFragmented");
}
}
onAttachFragment(Fragment fragment)方法,會(huì)在添加進(jìn)來(lái)的Fragment執(zhí)行完onAttach方法后被回調(diào)。
我們得到如下的運(yùn)行日志:
10-17 17:26:51.076 9035-9035/com.sina.example.fragmentdemo E/TAG: MainActivity onCreated
10-17 17:26:51.078 9035-9035/com.sina.example.fragmentdemo E/TAG: Fragment onAttach()
10-17 17:26:51.078 9035-9035/com.sina.example.fragmentdemo E/TAG: MainActivity onAttachFragmented
我們看到commit()被調(diào)用后Fragment的生命周期沒(méi)有立即開(kāi)始,而是放在了onCreate(Bundle savedInstanceState)執(zhí)行完成之后的某個(gè)時(shí)間。
(具體開(kāi)始執(zhí)行的時(shí)間點(diǎn):在源碼中該任務(wù)是被放在一個(gè)mHost.getHandler().post(mExecCommit)方法的參數(shù)里,也就是Handler初始化完畢,開(kāi)始發(fā)送消息的時(shí)候,我們提交的任務(wù)將會(huì)被真正執(zhí)行)
如果不想等待Handler初始化完畢,想要立即執(zhí)行commit的操作可以使用FragmentManager里提供的executePendingTransactions()方法,這個(gè)方法將會(huì)將你所有提交的操作一并執(zhí)行,而且是立即執(zhí)行。
我們更改代碼,添加上這個(gè)方法(如下所示)。
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
getSupportFragmentManager().beginTransaction().add(R.id.fl_main, new ContentFragment(), null).commit();
getSupportFragmentManager().executePendingTransactions();
Log.e("TAG", "MainActivity onCreated");
}
@Override
public void onAttachFragment(Fragment fragment) {
super.onAttachFragment(fragment);
Log.e("TAG", "MainActivity onAttachFragmented");
}
}
運(yùn)行程序,打印下日志:
10-17 17:51:37.626 22400-22400/com.sina.example.fragmentdemo E/TAG: Fragment onAttach()
10-17 17:51:37.626 22400-22400/com.sina.example.fragmentdemo E/TAG: MainActivity onAttachFragmented
10-17 17:51:37.627 22400-22400/com.sina.example.fragmentdemo E/TAG: MainActivity onCreated
我們看到在"MainActivity onCreated"日志還未被打印之前,我們提交的Fragment的生命周期已經(jīng)開(kāi)始了。
5. commitNow()和commitNowAllowingStateLoss()
在最新的API_24文檔里FragmentTranslation里添加了兩個(gè)方法:
commitNow()commitNowAllowingStateLoss()
調(diào)用這兩個(gè)方法類(lèi)似于先執(zhí)行commit()/commitAllowingStateLoss()然后執(zhí)行executePendingTransactions()方法。但也有區(qū)別。
區(qū)別一:不支持添加到回退棧的操作(源碼如下)
final class BackStackRecord extends FragmentTransaction implements
FragmentManager.BackStackEntry, Runnable {
......
@Override
public void commitNow() {
disallowAddToBackStack();
mManager.execSingleAction(this, false);
}
@Override
public void commitNowAllowingStateLoss() {
disallowAddToBackStack();
mManager.execSingleAction(this, true);
}
......
}
如果還調(diào)用addToBackStack(String name)方法會(huì)報(bào)一個(gè)IllegalStateException異常,告訴你不能向回退棧中添加FragmentTransaction。
區(qū)別二:源碼沒(méi)有再使用Handler,而是直接執(zhí)行(源碼如下)
final class FragmentManagerImpl extends FragmentManager implements LayoutInflaterFactory {
......
public void execSingleAction(Runnable action, boolean allowStateLoss) {
......
action.run();
......
}
......
}
BackStackRecord實(shí)現(xiàn)了Runnable接口重寫(xiě)了run方法,action.run()會(huì)直接執(zhí)行BackStackRecord的run方法。
補(bǔ)充:
官方更推薦使用commitNow()和commitNowAllowingStateLoss()來(lái)代替先執(zhí)行commit()/commitAllowingStateLoss()然后執(zhí)行executePendingTransactions()這種方式,因?yàn)楹笳邥?huì)有不可預(yù)料的副作用。
總結(jié):
如果你需要同步提交Fragment并且無(wú)需添加到回退棧中,則使用
commitNow()。Support庫(kù)中在 FragmentPagerAdapter中使用這個(gè)函數(shù),來(lái)確保更新Adapter的時(shí)候頁(yè)面被正確的添加和刪除。一般來(lái)說(shuō),只要不添加到回退棧中,都可以使用這個(gè)函數(shù)來(lái)提交。如果執(zhí)行的提交不需要是同步的,或者需要將提交都添加到回退棧中,那么就使用
commit()。如果你需要把多次提交的操作在同一個(gè)時(shí)間點(diǎn)一起執(zhí)行,則使用
executePendingTransactions()如果你需要在Activity執(zhí)行完onSaveInstanceState()之后還要進(jìn)行提交,而且不關(guān)心恢復(fù)時(shí)是否會(huì)丟失此次提交,那么可以使用
commitAllowingStateLoss()或commitNowAllowingStateLoss()。