因为官方的FragmentTabHost每次切换时会重建Fragment视图,所以找到了网上的翻版,成功入坑……
来看一下问题点
首先建一个activity,包含2个fragment和一个FragmentTabHost。
/**
* Created by zhanglei on 17/6/14.
* An activity with two fragments to find out the issue of {@link FragmentTabHost}
*/
public class TabHostIssueTestActivity extends AppCompatActivity{
public static final String TAG = "TabHostActivity";
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_tabhost_issue_test);
// Retrieve the tab host and setup.
FragmentTabHost tabHost = (FragmentTabHost)findViewById(R.id.tabhost);
if(tabHost != null){
tabHost.setup(this, getSupportFragmentManager(), R.id.content);
// Add any fragment to tab host.
tabHost.addTab(tabHost.newTabSpec("first").setIndicator("first"), FirstFragment.class, null);
tabHost.addTab(tabHost.newTabSpec("second").setIndicator("second"), SecondFragment.class, null);
}else{
Log.i(TAG, "Failed to resolve the tabhost.");
}
}
}
运行效果如下:
可以看出,屏幕旋转之后FirstFragment显示空白。debug后发现,切换到FirstFragment后代码走的是这里:
if (newTab.fragment.isHidden()){
ft.show(newTab.fragment);
}
else{
ft.attach(newTab.fragment);
}
并且此时newTab.fragment的isHidden = false, mView= null。很显然代码进入ft.show(),但不进入ft.attach()。
我们再看下ft.show(fragment)源码:
FragmentManager#showFragment
if (fragment.mView != null) {
Animator anim = loadAnimator(fragment, transition, true,
transitionStyle);
if (anim != null) {
anim.setTarget(fragment.mView);
setHWLayerAnimListenerIfAlpha(fragment.mView, anim);
anim.start();
}
fragment.mView.setVisibility(View.VISIBLE);
}
所以,if里面进不去,所以在界面上我们看到的是空白。好,那么问题来了。
FirstFragment什么时候被隐藏的?
在FragmentTabHost有下面有段代码:
protected void onAttachedToWindow() {
super.onAttachedToWindow();
String currentTab = getCurrentTabTag();
// Go through all tabs and make sure their fragments match
// the correct state.
FragmentTransaction ft = null;
for (int i=0; i<mTabs.size(); i++) {
TabInfo tab = mTabs.get(i);
tab.fragment = mFragmentManager.findFragmentByTag(tab.tag);
if (tab.fragment != null && !tab.fragment.isDetached()) {
if (tab.tag.equals(currentTab)) {
// The fragment for this tab is already there and
// active, and it is what we really want to have
// as the current tab. Nothing to do.
mLastTab = tab;
} else {
// This fragment was restored in the active state,
// but is not the current tab. Deactivate it.
if (ft == null) {
ft = mFragmentManager.beginTransaction();
}
ft.detach(tab.fragment);
}
}
}
// We are now ready to go. Make sure we are switched to the
// correct tab.
mAttached = true;
ft = doTabChanged(currentTab, ft);
if (ft != null) {
ft.commitAllowingStateLoss();
mFragmentManager.executePendingTransactions();
}
}
从FragmentTabHost继承关系来看,它终究也是个View,所以被添加到窗口时会调用上面方法,每一次activity重建此方法会被调用一次。从代码逻辑来看,如果某个TabInfo与系统之前保存的tab不是同一个,则把这个TabInfo对应的fragment执行detach操作。所谓“系统之前保存的tab”,就是低内存或设置配置变化时保存当前显示的tab。在我们这里,就是屏幕旋转时保存的tab,如果竖屏时显示的是SecondFragment,再把屏幕旋转,此时系统保存的tab就是SecondFragment对应的tab,所以会detach掉FirstFragment,注意,由于屏幕旋转后activity会重建,所以此时系统会重新创建一个FirstFragment对象,就也是这个新创建的FirstFragment只是从FragmentManager中恢复了状态state,但是被detach了。从下面的代码可得出,这个fragment会停留在onCreate这个生命周期中,由于没有走到onCreateView,所以mView = null。因为SecondFragment没有被detach,所以走了正常的生命周期,所以看到屏幕旋转后SecondFragment仍然显示了。
FragmentManger#moveToState
// Fragments that are not currently added will sit in the onCreate() state.
if ((!f.mAdded || f.mDetached) && newState > Fragment.CREATED) {
newState = Fragment.CREATED;
}
该如何修复此”bug”?(引号请原谅我的误解)
我们再回到翻版FragmentTabHost的代码。
if (newTab.fragment.isHidden()){
ft.show(newTab.fragment);
}
else{
ft.attach(newTab.fragment);
}
只要把上面的else去掉就可以了。如下:
if (newTab.fragment.isHidden()){
ft.show(newTab.fragment);
}
if(newTab.fragment.isDetached()){
ft.attach(newTab.fragment);
}
项目都建了,你就给我看这个?