【问题标题】:Android findFragmentByTag returns null when debuggingAndroid findFragmentByTag 调试时返回null
【发布时间】:2016-04-03 14:49:53
【问题描述】:

背景:

我正在编写一个 android 应用程序,其 Activity 可以由多个片段之一填充。相对而言,我需要将一组数据传递给活动片段,以便它可以相应地更新 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() 有时会返回空值。

我的 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 添加了预期的标签。

在我的代码中,我没有设置标签就添加片段。这是否与正在重新创建的活动和片段管理器丢失标签有关,即使它保留了片段本身?

【问题讨论】:

  • 根据您的更新,您是否在活动中覆盖onSaveInstanceState?如果是这样,请确保您拨打的是super.onSaveInstanceState。我相信这是保存片段状态的地方。
  • 我没有覆盖onSaveInsanceState
  • 很奇怪。您可以发布重现问题的活动和片段的简化版本吗?它一定是某处简单的东西......我想不出除了对片段管理器或片段本身做错什么之外,可能导致片段丢失其标签的原因。
  • 确实很奇怪。这不是您问题的答案,但是使用 findFragmentById(R.id.container) 代替呢?这将为您返回当前附加到您的 R.id.container 视图的 Fragment,因此您甚至可以摆脱 currentFragmentTag 变量。
  • 另外,您的compiledSdkVersion 是什么,以确保它不依赖于它?

标签: java android android-fragments android-studio


【解决方案1】:

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

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

【讨论】:

  • 感谢您的回复。问题是我需要经常调用“updateFragment()”(例如,每当 gps 位置发生变化时)。所以我无法准确预测它何时会被调用。让我感到惊讶的是,这仅在我从 Android Studio 运行时才会发生
【解决方案2】:

这不是对您问题的直接回答,而是一开始就避免该问题的建议。

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

我没有获取对 Fragment 的引用并直接在其上调用方法,而是设置了消息总线(例如 Otto 或 GreenRobot),在您的情况下,将让 Activity 将消息发布到总线,并让片段订阅该消息。干净得多,如果 Fragment 碰巧没有准备好(还没有在总线上注册),就没有错误条件。

【讨论】:

  • 有趣的想法。然而,重新编写代码以使用这样的总线需要相当多的工作(和时间)。但如果没有其他办法,我会考虑这个
【解决方案3】:

对此有几点需要注意。 在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 之前添加了空检查。它没有解决我的问题,但是当我这样做时我注意到了一些奇怪的事情。查看我的更新
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-12-22
  • 2018-07-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-03-04
相关资源
最近更新 更多