片段创建和检索并发

时间:2017-11-20 22:20:47

标签: java android android-fragments

我目前遇到的问题是如何防止涉及片段的并发问题。

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

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,它们都由相同的片段管理器管理,因此可以访问相同的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例外。

1 个答案:

答案 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();
    }
}
相关问题