Android调试时,使用findFragmentByTag返回null

8

背景:

我正在编写一个Android应用程序,其中一个活动可以由多个片段中的任何一个填充。相对频繁地,我需要传递一束数据到活动的片段,以便它可以相应地更新UI。

为了获取当前片段,我调用 getfragmentManager()。findFragmentByTag()并将返回的片段强制转换为我的自定义片段类。

问题:

上述方法通常运行良好。然而, findFragmentByTag()偶尔会返回null。经过进一步的调查,我得出结论,这只会在我从Android studio运行或调试时发生(即使这样也不会每次都发生)。

相关代码:

protected void onCreate(Bundle savedInstanceState){

    //Do lots of stuff
    currentFragmentTag = "";
    getFragmentManager().addOnBackStackChangedListener(
            new FragmentManager.OnBackStackChangedListener() {
                public void onBackStackChanged() {
                    if (getFragmentManager().getBackStackEntryCount() > 0) {
                        currentFragmentTag = getFragmentManager().getBackStackEntryAt(getFragmentManager().getBackStackEntryCount() - 1).getName();
                    }

                }
            });
    init();

    //Do some more stuff

    //this should always be the case
    if (currentFragmentTag.equals(LOGIN_FRAGMENT_TAG)) {
        ((LoginFragment) getFragmentManager().findFragmentByTag(currentFragmentTag)).beginLogin();
    }

}


private void init(){
    //changed to reflect George Mulligan's advice
    Fragment currentFrag = getFragmentManager().findFragmentByTag(LOGIN_FRAGMENT_TAG);
    if(currentFrag == null) {
        currentFragmentTag = LOGIN_FRAGMENT_TAG;
        getFragmentManager().beginTransaction()
                .add(R.id.container, loginFrag, LOGIN_FRAGMENT_TAG)
                .addToBackStack(LOGIN_FRAGMENT_TAG)
                .commit();
        getFragmentManager().executePendingTransactions();

    }
}

public void updateFragment(){
    Bundle dataBundle = new Bundle();
    //put stuff in dataBundle

    if (currentFragmentTag.equals(LOGIN_FRAGMENT_TAG)) {
        LoginFragment currentFrag = (LoginFragment) getFragmentManager().findFragmentByTag(currentFragmentTag);
        if (currentFrag != null) {
            currentFrag.passBundleToFragment(dataBundle);
            Log.d(TAG, "Fragment returned is valid.");
        } else {
            Log.d(TAG, "Fragment returned is null.");
        }
    } 
    //else if a different fragment is active then update it in the same way
}

//Manually open the loginFragment. This can be called from other fragments. My problem always occurs before this is called however. 
@Override
public void openLoginScreen() {
    if(/*some conditions*/) {
        LoginFragment loginFrag = LoginFragment.newInstance();
        getFragmentManager().beginTransaction()
                .replace(R.id.container, loginFrag, LOGIN_FRAGMENT_TAG)
                .addToBackStack(LOGIN_FRAGMENT_TAG)
                .commit();
        getFragmentManager().executePendingTransactions();
        currentFragmentTag = LOGIN_FRAGMENT_TAG;
        updateFragment();
    }
}

通常,我的logcat看起来像这样:

Fragment returned is valid
Fragment returned is valid
Fragment returned is valid
Fragment returned is valid
Fragment returned is valid
Fragment returned is valid
Fragment returned is valid
Fragment returned is valid
...etc.

但是偶尔,只有当我从Android Studio启动应用程序时,会出现类似以下的情况:

Fragment returned is valid
Fragment returned is valid
Fragment returned is valid
Fragment returned is valid
Fragment returned is null
Fragment returned is null
Fragment returned is null
Fragment returned is null
Fragment returned is null
Fragment returned is null
Fragment returned is null
Fragment returned is null
Fragment returned is null
Fragment returned is null
Fragment returned is valid
Fragment returned is valid
Fragment returned is valid
Fragment returned is valid
Fragment returned is valid
Fragment returned is valid
Fragment returned is valid
Fragment returned is valid
Fragment returned is valid
Fragment returned is null
Fragment returned is null
Fragment returned is null
Fragment returned is null
Fragment returned is null
Fragment returned is null
Fragment returned is null
Fragment returned is valid
Fragment returned is valid

这到底是怎么回事?
更新:
我能够在断开 Android Studio 的情况下重现此错误,方法是点击应用程序切换按钮、关闭我的应用并立即重新启动它。只要我足够快地执行这些操作,就会始终按照我上面描述的方式运行。经过更多日志记录和追踪其他问题,我发现在这些特定情况下,onCreate() 会被调用两次。
我的应用程序设计为仅在横向模式下运行,部分原因是为了避免重新创建活动时出现的问题。然而,在应用程序关闭并快速重新启动时,似乎 Android 没有完成必要的旋转以使主屏幕进入纵向模式,之后再启动我的应用程序。我的假设是,这导致操作系统在应用程序运行后将其旋转回横向模式,从而重新启动应用程序。
所有这一切都很好,但它并不能解释为什么 findFragmentByTag() 有时会返回 null。
我的 Activity 类中的每个对象都应该被重新创建,不是吗?那么 FragmentManager 也应该被重新初始化,对吧?还是说 getFragmentManager() 是对 Activity 本身之外的某些静态引用?
第二次更新:
我尝试了 George 的想法,在调用 beginTransaction() 之前检查片段是否已被添加,虽然它没有解决问题,但我在调试时注意到了一些奇怪的地方:
我在 Log.d(TAG, "Fragment returned is null.") 处设置了断点。关闭应用程序并快速重新启动它可以保证会触发这段代码,就像我上面提到的那样。然后,如果我通过在 Evaluate Expression 窗口中调用 getFragmentManager() 来查看 Fragment Manager,我会注意到一个“Login Fragment”已经被添加了,但它没有与之关联的标签。
在同一应用程序会话中在 Log.d(TAG, "Fragment returned is valid.") 处设置断点,会显示 LoginFragment 已经添加,并带有预期的标签。
在我的代码中从未添加过未设置标签的片段。这可能与活动被重新创建以及 Fragment manager 虽然保存了片段本身,但却丢失标签有关吗?

非常奇怪。你能否发布一个简化版本的活动和片段,以重现问题?一定是某个地方出了问题...我想不出什么可能会导致片段失去其标记,除非在片段管理器或片段本身上做错了什么。 - George Mulligan
确实很奇怪。这不是解决您问题的答案,但是考虑使用findFragmentById(R.id.container)呢?这将返回当前附加到您的R.id.container视图的片段,因此您甚至可以摆脱currentFragmentTag变量。 - Mateusz Herych
另外,您的compiledSdkVersion是多少,只是为了确保它不依赖于它? - George Mulligan
@GeorgeMulligan 我添加了我认为在问题发生之前处理FragmentManager的每一行代码。此外,我的compiledSdkVersion是21。 - JohannB
请尝试使用子片段管理器而不是片段管理器。 - Choxmi
显示剩余4条评论
3个回答

8
在这里需要注意一些事情。在updateFragment方法中,你应该使用.equals而不是引用比较==来比较你的字符串,所以最好使用LOGIN_FRAGMENT_TAG.equals(currentFragmentTag)来作为最佳实践和避免混淆。
另外,FragmentManager会记住你添加到其中的片段在方向更改时的状态。所以在你提到的一个例子中,在onCreate方法中执行了两次,你可能会在后台栈上有两个LoginFrag的实例。
你可以通过在updateFragment方法中进行相同的查找,并且只在没有找到时才添加片段来避免这种情况发生。
LoginFragment currentFrag = (LoginFragment) getFragmentManager()
    .findFragmentByTag(LOGIN_FRAGMENT_TAG);

if(currentFrag == null) {
    currentFrag = getFragmentManager().beginTransaction()
        .replace(R.id.container, loginFrag, LOGIN_FRAGMENT_TAG)
        .addToBackStack(LOGIN_FRAGMENT_TAG)
        .commit();

    //executePendingTransactions() usually isn't necessary...
    getFragmentManager().executePendingTransactions();
}

我猜测在这个活动中使用了多个片段,因为你有名为currentFragmentTag的字符串,但如果没有,则最好将LoginFragment作为该类的字段引用,这样可以避免在updateFragment方法中进行额外的查找。
除此之外,我尝试复制您的错误,但是我无法,因此错误可能在您未显示的代码中。

关于字符串的处理你说得很好。你猜对了,我正在使用多个片段。我按照你的建议在beginTransaction之前添加了空值检查。虽然这没有解决我的问题,但我注意到了一些奇怪的事情。请看我的更新。 - JohannB

3

这并不是直接回答你的问题,而是提供一个避免问题出现的建议。

由于Fragment和Activity生命周期之间的交互非常复杂且难以理解,我发现如果完全将两者分离,会更容易处理。

我不是直接获取Fragment的引用并直接调用其方法,而是设置了一个消息总线(例如Otto或GreenRobot),在您的情况下,Activity会向总线发布一条消息,Fragment会订阅该消息。 这样更加简洁,并且如果Fragment尚未准备好(尚未向总线注册),则没有错误条件。


有趣的想法。然而,重新编写代码以使用类似总线的东西需要相当多的工作(和时间)。但是如果没有其他办法,这是我会考虑的事情。 - JohannB

0

好的,这应该是一条注释,但是代码格式作为注释很丑陋。也许你的片段被暂停了。 试试这个

@Override
public void onResume() {
    super.onResume();
    updateFragment();
}

谢谢回复。问题是我需要经常调用'updateFragment()'(例如每当GPS位置发生变化时)。因此,我无法准确预测它将何时被调用。让我困惑的是,这只会在我从Android Studio运行时发生。 - JohannB

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接