起因
最近在把項(xiàng)目support庫從23.1.1升級至23.4.0時(shí),突然發(fā)現(xiàn)有些地方會莫名的找不到"已經(jīng)"add的fragment,從而導(dǎo)致了空指針引用異常。郁悶的我就仔細(xì)對比了23.1.1和23.4.0源碼闸天,發(fā)現(xiàn)原來是google悄悄地把FragmentManager add()機(jī)制更改了犬绒,真是坑爹-檩禾,-挂签。
探究
如下是一段很平常的代碼:
//In Activity.java
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getSupportFragmentManager()
.beginTransaction()
.add(android.R.id.content, fragmentA, fragmentA.TAG)
.add(android.R.id.content, fragmentB, fragmentB.TAG)
.commit();
}
//In FragmentA.java
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mFragmentB =(FragmentB)getFragmentManager()
.findFragmentByTag(FragmentB.TAG);
mFragmentB.performSomeAction();
... ...
}
在23.1.1之前FragmentManager的add機(jī)制:
//STEP0: 記錄已經(jīng)添加和有效的fragment集合
ArrayList<Fragment> mAdded;
ArrayList<Fragment> mActive;
... ...
public void run() {
... ...
//STEP1: 根據(jù)之前的add順序,從mHead中取出fragment并依次放入lastInFragments中盼产,為之后的transition做準(zhǔn)備饵婆。
calculateFragments(firstOutFragments,lastInFragments);
//STEP2: 再根據(jù)lastInFragments集合統(tǒng)計(jì)出fragments的過渡狀態(tài)(因?yàn)閮Hadd操作,所以firstOutFragments為空)
state=beginTransition(firstOutFragments,lastInFragments, false);
... ...
//STEP3:依次將mHead鏈表中的fragment加入至mAdded和mActive中戏售,但不會更改fragment的狀態(tài)(即不會觸發(fā)fragment的初始化操作)侨核。
while (op != null) {
int enterAnim = state != null ? 0 : op.enterAnim;
int exitAnim = state != null ? 0 : op.exitAnim;
switch (op.cmd) {
case OP_ADD: {
Fragment f = op.fragment;
f.mNextAnim = enterAnim;
mManager.addFragment(f, false);
} break;
... ...
}
}
//STEP4: 最后更新mActive集合中所有fragment的狀態(tài)(對于add操作而言即是觸發(fā)了fragment的初始化操作)
mManager.moveToState(mManager.mCurState
, transition, transitionStyle, true);
... ...
}
而在23.4.0版本的support庫中,它初始化fragment的時(shí)機(jī)發(fā)生了變化蜈项,主要變化是在上述的STEP1中芹关。
在23.4.0的STEP1中,除了根據(jù)之前的add順序紧卒,從mHead中取出fragment并依次放入lastInFragments中侥衬,為之后的transition做準(zhǔn)備。并且將新add的fragment放入mActive中跑芳,同時(shí)更新fragment的狀態(tài)(即觸發(fā)了fragment的初始化操作)轴总。
// 23.1.1版本:
private void setLastIn(SparseArray<Fragment> fragments, Fragment fragment) {
if (fragment != null) {
int containerId = fragment.mContainerId;
if (containerId != 0) {
fragments.put(containerId, fragment);
}
}
}
// 23.4.0版本:
private void setLastIn(... , Fragment fragment) {
if (fragment != null) {
... ...
if (fragment.mState < Fragment.CREATED
&& mManager.mCurState >= Fragment.CREATED) {
mManager.makeActive(fragment);
mManager.moveToState(fragment, Fragment.CREATED, 0, 0, false);
}
}
}
因?yàn)橛|發(fā)fragment初始化時(shí)機(jī)發(fā)生了改變,所以當(dāng)FragmentB在FragmentA之后add博个,此時(shí)在FragmentA的onCreate方法中通過findFragmentByTag找FragmentB時(shí)怀樟,必然找到的為空(即mAdded和mActive兩個(gè)集合中都無法找到),因?yàn)榇藭r(shí)FragmentB根本還未來得及被添加至mAdded或mActive集合中盆佣。
public Fragment findFragmentByTag(String tag) {
if (mAdded != null && tag != null) {
// First look through added fragments.
for (int i=mAdded.size()-1; i>=0; i--) {
Fragment f = mAdded.get(i);
if (f != null && tag.equals(f.mTag)) {
return f;
}
}
}
if (mActive != null && tag != null) {
// Now for any known fragment.
for (int i=mActive.size()-1; i>=0; i--) {
Fragment f = mActive.get(i);
if (f != null && tag.equals(f.mTag)) {
return f;
}
}
}
return null;
}
結(jié)語
所以謹(jǐn)記往堡,如果兩個(gè)fragment之間有調(diào)用關(guān)系的話,添加他們時(shí)一定要優(yōu)先add被調(diào)用的fragment(即例子中的FragmentB)共耍,以方support庫升級后引起空指針異常虑灰。
歡迎查看 個(gè)人博客.