示例代码段
在使用 Fragment 的过程中,我们通常会对 Fragment 做一些操作,例如下面的代码:
Code segment:
ExampleActivity.java
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Fragment fragment = new Fragment();
FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.add(R.id.frame_layout, fragment);
fragmentTransaction.commit();
fragmentTransaction.hide(fragment);
fragmentTransaction.commit();
}
上面的代码是指将一个 fragment 放入 id 为 R.id.frame_layout 的资源里,然后再将 fragment 隐藏。
运行一下,会抛出下面的错误:
这是一个怎样的异常呢?这个异常又是在哪里抛出的呢?为什么会抛出这样的异常?
Exception:commit already called
java.lang.IllegalStateException 定义:
package java.lang;
/**
* Thrown when an action is attempted at a time when the VM is not
* in the correct state.
*/
public class IllegalStateException extends RuntimeException {
……
}
为什么会抛出这样的异常呢?
首先来看一下是怎样一步一步从 FragmentManager 到 FragmentTransaction 再到 FragmentTransaction.commit()
FragmentTransaction.commit()
首先,是谁实例化了 FragmentManager
众所周知,抽象类是不能被实例化的,但抽象类的对象可以指向一个由抽象类派生出的非抽象类所实例化的对象。那么,这个被派生出的类又是谁呢?
来看代码:
Code segment:
ExampleActivity.java
……
FragmentManager fragmentManager = getFragmentManager();
……
Code segment:
Activity.java
……
public FragmentManager getFragmentManager() {
return mFragments;
}
……
找到 mFragment 声明和实例化的地方:
Code segment:
Activity.java
……
final FragmentManagerImpl mFragments = new FragmentManagerImpl();
……
所以,由 FragmentManager 派生出的非抽象类为 FragmentManagerImpl
那么,FragmentManagerImpl 这个类是在哪里定义的呢?
这个类比较难找,自己去翻源码无果后,去搜了一下,然后在 StackOverflow 找到了答案(原帖):
本人找不到是因为这个类是定义在 android 支持包 support-v4 里的 FragmentManager.java(在线源码查看) 中的:
Code segment:
FragmentManager.java
package android.support.v4.app;
public abstract class FragmentManager {
……
}
final class FragmentManagerImpl extends FragmentManager implements LayoutInflater.Factory {
……
}
那么接下来,调用的即为在 FragmentManagerImpl 中实现了的方法
Code segment:
ExampleActivity.java
……
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
在 FragmentManagerImpl 中:
Code segment:
FragmentManager.java
package android.support.v4.app;
public abstract class FragmentManager {
……
}
final class FragmentManagerImpl extends FragmentManager implements LayoutInflater.Factory {
}
而 BackStackRecord 是 FragmentTransaction 派生出的非抽象类
Code segment:
BackStackRecord.java
package android.support.v4.app;
final class BackStackRecord extends FragmentTransaction implements
FragmentManager.BackStackEntry, Runnable {
……
}
最后找到 commit() 及其相关代码段:
Code segment:
BackStackRecord.java
package android.support.v4.app;
final class BackStackRecord extends FragmentTransaction implements
FragmentManager.BackStackEntry, Runnable {
……
public BackStackRecord(FragmentManagerImpl manager) {
mManager = manager;
}
……
public int commit() {
return commitInternal(false);
}
int commitInternal(boolean allowStateLoss) {
if (mCommitted) throw new IllegalStateException("commit already called");
if (FragmentManagerImpl.DEBUG) {
Log.v(TAG, "Commit: " + this);
LogWriter logw = new LogWriter(TAG);
PrintWriter pw = new PrintWriter(logw);
dump(" ", null, pw, null);
}
mCommitted = true;
if (mAddToBackStack) {
mIndex = mManager.allocBackStackIndex(this);
} else {
mIndex = -1;
}
mManager.enqueueAction(this, allowStateLoss);
return mIndex;
}
……
}
从代码中可以看出,一旦 mCommitted 为 true,就会抛出异常,而 mCommitted 在整个类中被设置为 true 的情况只有一种:那就是 mCommitted 最开始被声明之后(默认为 false),commitInternal() 第一次顺序执行到 “mCommited = true;” 。之后再调用 commitInternal, 就只能抛出异常了。即:commit() 这个方法在一个 BackStackRecord 实例里只能被使用一次。
解决方案
再实例一个不同的 BackStackRecord 就可以了,即:再次调用 FragmentManager.beginTransaction()
总结
1.一个 BakStackRecord 实例的 commit 只能调用一次
2.各类关系图:
(因下面的问题未解决,暂空)
提出问题:什么时候使用 support 包中的内容,什么时候又使用非 support 包中的内容呢?
support-v4 包中也有 FragmentManager,那么 FragmentManagerImpl 继承的是哪个 FragmentManager 呢?