FragmentTabHost屏幕切换时Fragment显示空白

在Android中使用FragmentTabHost时,屏幕旋转可能导致Fragment显示为空白。原因是FragmentTabHost在切换时会重建Fragment视图。文章分析了Fragment隐藏的时机,指出在activity重建时,FragmentTabHost会detach非当前显示的Fragment,导致其视图丢失。为修复这个问题,建议修改源码,避免detach操作。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

因为官方的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.");
        }
    }
}

运行效果如下:
竖屏没问题

竖屏没问题

横屏Second没问题

横屏First显示空白
可以看出,屏幕旋转之后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);
                }

项目都建了,你就给我看这个?

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值