片段创建和检索并发



我目前在弄清楚如何防止涉及碎片的并发问题方面遇到了一个问题。

为了在活动重新创建(配置更改等)之间存储几段数据,我使用了一个保留的片段(没有视图)。

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());
}

对于这个例子,让我们将这些片段称为Fragment1Fragment2,它们都由同一个片段管理器管理,因此可以通过findOrCreateRetainFragment方法访问同一个ListRetainFragment。在大多数情况下,这很好。碎片被保留下来,数据会随之正确地转移

当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

然而,在一些设备配置中,片段1和片段2是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}

由于ViewPager创建的Fragment1和Fragment2时间很短,所以当Fragment2尝试检索时,Fragment1创建的保留片段(214bdd7)还没有正确添加到FragmentManager中。因此,会创建一个新片段(887e4db),覆盖Fragment1所创建的片段。

在重新创建活动之后,最初由Fragment1(214bdd7)检索和引用的片段及其数据将丢失,因为重新创建的Fragment1现在检索由Fragment2(887e4db)创建的保留片段。

有没有一种方法可以确保在我尝试检索之前完成上一个片段提交?我已经尝试过commitNow()executePendingTransactions()方法,但它们会导致java.lang.IllegalStateException: FragmentManager is already executing transactions异常。

我认为解决问题的唯一方法是重新考虑每个UI片段如何获得对保留片段的引用。与其让每个UI片段尝试从FragmentManager中检索保留的片段,我认为您应该让活动执行此操作,然后让片段从活动中检索保留片段。

问题的根源在于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中,当您的活动在循环后重新创建时,它知道CCD_ 15已经有了它的一个实例,所以它只使用它。

您的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();
}
}

最新更新