背景
我正在编写一个Android应用程序,其活动可以由许多片段中的一个填充。相对而言,我需要将一组数据传递给活动片段,以便它可以相应地更新UI。
要获取当前片段,我调用getfragmentManager().findFragmentByTag()
并将返回的片段转换为我的自定义片段类。
问题:
上述方法通常可以正常工作。但是,findFragmentByTag()
偶尔会返回null。经过进一步调查,我得出的结论是,只有当我从Android工作室运行或调试时才会出现这种情况(即使这样,它也不会每次都发生)。
相关代码:
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()
来查看片段管理器,我会注意到`Login Fragment'已添加,但它没有与之关联的标记。
然后,在同一应用会话中在Log.d(TAG, "Fragment returned is valid.");
设置断点会显示LoginFragment
添加了预期的标记。
我的代码中没有任何意义,我在没有设置标签的情况下添加片段。这可能与正在重新创建的活动和Fragment管理器丢失标签有关,即使它自己保留在片段上吗?
答案 0 :(得分: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
方法中对它进行额外的查找。
除此之外,我尝试重现您的错误,但我不能将错误显示在您未显示的代码中。
答案 1 :(得分:3)
这不是您问题的直接答案,而是建议首先避免这个问题。
因为片段和活动生命周期之间的相互作用是如此复杂且难以推理,我发现如果你完全将两者分离,它会更容易。
我没有获得对Fragment的引用并直接调用它上面的方法,而是建立了一个消息总线(例如Otto或GreenRobot),在你的情况下,会让Activity发布一条消息给总线,并且片段订阅该消息。更清洁,如果Fragment没有准备就绪(尚未注册公共汽车),那么没有错误条件。
答案 2 :(得分:0)
好的,这应该是一个评论但是,代码格式化是一个丑陋的评论。也许你的片段被暂停了。 试试这个
@Override
public void onResume() {
super.onResume();
updateFragment();
}