我目前遇到的问题是如何防止涉及片段的并发问题。
要在活动重新创建(配置更改等)之间存储多个数据,我使用保留的片段(没有视图)。
public class ListRetainFragment extends Fragment {
private static final String TAG = "ListRetainFragment";
public ListRetainFragment() {}
public static ListRetainFragment findOrCreateRetainFragment(FragmentManager fm) {
ListRetainFragment fragment = (ListRetainFragment) fm.findFragmentByTag(TAG);
if (fragment == null) {
fragment = new ListRetainFragment();
fm.beginTransaction().add(fragment, TAG).commit();
Log.e("FRAGMENT_INIT", "New retain fragment created with reference: "+fragment.toString());
}
else{
Log.e("FRAGMENT_INIT", "Existing fragment found with reference: "+fragment.toString());
}
return fragment;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
}
//Storage code omitted.....
}
然后在希望在其中存储一些数据的片段的onCreate中检索此片段。
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
listRetainFragment = ListRetainFragment.findOrCreateRetainFragment(getFragmentManager());
}
对于此示例,我们将这些片段称为Fragment1
和Fragment2
,它们都由相同的片段管理器管理,因此可以访问相同的ListRetainFragment
findOrCreateRetainFragment
方法。
在大多数情况下,这很好。片段被保留,数据也随之携带。
当logcat按预期工作时查看:
D/FRAGMENT_INIT: New retain fragment created with reference: ListRetainFragment{671e2da ListRetainFragment} //Fragment 1 is the first one trying to retrieve a retain fragment. A new instance is created and added to the fragment manager.
D/FRAGMENT_INIT: Existing fragment found with reference: ListRetainFragment{671e2da ListRetainFragment} //Fragment 2 is the second one trying to retrieve a retain fragment. The originally created fragment is found and returned
//ACTIVITY + FRAGMENT1 + FRAGMENT2 ARE RECREATED (Ex. when changing device orientation)
D/FRAGMENT_INIT: Existing fragment found with reference: ListRetainFragment{671e2da ListRetainFragment} //After recreation Fragment1 finds the orignal fragment and uses it to retrieve/store persistent data
D/FRAGMENT_INIT: Existing fragment found with reference: ListRetainFragment{671e2da ListRetainFragment} //After recreation Fragment2 finds the orignal fragment and uses it to retrieve/store persistent data
但是,在某些设备配置中,Fragment1和Fragment2是2片段ViewPager的一部分,因此很快就会相互创建。
这种情况会导致以下问题:
D/FRAGMENT_INIT: New retain fragment created with reference: ListRetainFragment{214bdd7 ListRetainFragment}
D/FRAGMENT_INIT: New retain fragment created with reference: ListRetainFragment{887e4db ListRetainFragment}
//ACTIVITY + FRAGMENT1 + FRAGMENT2 ARE RECREATED (Ex. when changing device orientation)
D/FRAGMENT_INIT: Existing fragment found with reference: ListRetainFragment{887e4db ListRetainFragment}
D/FRAGMENT_INIT: Existing fragment found with reference: ListRetainFragment{887e4db ListRetainFragment}
由于Fragment1和Fragment2由ViewPager创建得如此之短,因此Fragment1(214bdd7)创建的retain片段在Fragment2
尝试检索它时尚未正确添加到FragmentManager中。因此,会创建一个新片段(887e4db),它会覆盖Fragment1创建的片段。
在Activity重新创建后,Fragment1(214bdd7)最初检索和引用的片段及其数据将丢失,因为重新创建的Fragment1现在正在检索Fragment2(887e4db)创建的保留片段。
在我尝试检索之前,有没有办法确保先前的片段提交已完成?
我已经尝试了commitNow()
或executePendingTransactions()
方法,但它们会导致java.lang.IllegalStateException: FragmentManager is already executing transactions
例外。
答案 0 :(得分:1)
我认为解决问题的唯一方法是重新考虑每个UI片段如何获取对保留片段的引用。我不是让每个UI片段都尝试从FragmentManager
中检索保留的片段,而是认为你应该让你的活动这样做,然后让你的片段从你的活动中检索保留的片段。 / p>
问题的根源是FragmentTransaction.commit()
是异步的。因此,FragmentManager
不是“我的保留片段是否已创建?”这一问题的可靠答案来源。相反,您应该让Activity
成为保留片段的真相来源。
public class MainActivity extends AppCompatActivity {
private RetainedFragment retainedFragment;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (savedInstanceState != null) {
retainedFragment = (RetainedFragment) getSupportFragmentManager().findFragmentByTag(RetainedFragment.TAG);
}
else {
retainedFragment = new RetainedFragment();
getSupportFragmentManager()
.beginTransaction()
.add(retainedFragment, RetainedFragment.TAG)
.commit();
}
}
public RetainedFragment getRetainedFragment() {
return retainedFragment;
}
}
通过利用savedInstanceState
参数创建和检索保留的片段,可确保您只创建保留片段的单个实例:当活动首次启动时,它会创建一个新实例并将其添加到FragmentManager
,当您的活动在轮播后重新创建时,知道 FragmentManager
已经有了它的实例,所以它只是使用它。
您的UI片段现在可以像这样访问保留的片段:
public class UiFragment extends Fragment {
private RetainedFragment retainedFragment;
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
MainActivity main = (MainActivity) getActivity();
retainedFragment = main.getRetainedFragment();
}
}