findFragmentByTag null for Fragment A, if setRetain(true) on Fragment B

时间:2015-10-30 22:25:31

标签: android android-fragments android-configchanges fragmentmanager

My problem involves an activity hosting three support fragments. One is a normal programmatic fragment (let's call it a home fragment). One is a portrait fragment added on top of the home fragment when the device is orientated, and one is 'headless', to continue an async task regardless of configuration changes. Very simple, I was working off this nice example.

public class HeadlessCustomerDetailFetchFragment extends Fragment{
private RequestCustomerDetails mRequest;
private AsyncFetchCustomerDetails mAsyncFetchCustomerDetails;

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setRetainInstance(true);

    mRequest = (RequestCustomerDetails)getActivity();
}

public void startFetching(String scannedBarcode) {
    if(mAsyncFetchCustomerDetails != null && mAsyncFetchCustomerDetails.getStatus() == AsyncTask.Status.RUNNING) return;

    if(mAsyncFetchCustomerDetails == null || mAsyncFetchCustomerDetails.getStatus() == AsyncTask.Status.FINISHED)
        mAsyncFetchCustomerDetails = new AsyncFetchCustomerDetails(getActivity(), mRequest, mPartner, scannedBarcode);
}

public void stopFetching() {
    if(mAsyncFetchCustomerDetails != null && mAsyncFetchCustomerDetails.getStatus() != AsyncTask.Status.RUNNING) return;
    mAsyncFetchCustomerDetails.cancel(true);
}

}

In my activity's onCreate() I create and add the headless fragment if necessary.

 mHeadlessCustomerDetailFetchFragment = (HeadlessCustomerDetailFetchFragment)getSupportFragmentManager()
            .findFragmentByTag(HeadlessCustomerDetailFetchFragment.class.getSimpleName());

if(mHeadlessCustomerDetailFetchFragment == null) {
         mHeadlessCustomerDetailFetchFragment = HeadlessCustomerDetailFetchFragment.instantiate(this, HeadlessCustomerDetailFetchFragment.class.getName());
    getSupportFragmentManager().beginTransaction()
            .add(mHeadlessCustomerDetailFetchFragment, mHeadlessCustomerDetailFetchFragment.getClass().getSimpleName())
            .commit();
    getSupportFragmentManager().executePendingTransactions();
        id = null;
    }

I then launch an async task (via my startFetching() function) after a 6 second delay (for testing) kicked off in the onCreateView() of the portrait fragment that is added when the orientation changes to portrait. The orientation change is detected in the activity's onCreate():

if (savedInstanceState == null) { 
   // Do some initial stuff for the home fragment
} 
else {
    getSupportFragmentManager().popBackStackImmediate(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
    if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {
        //Launch portrait fragment
        FragmentLauncher.launchPortraitFragment(this);
    }

When the task is finished, I return to the activity and attempt to update the UI of the active portrait fragment, but the fragment manager cannot find it, findFragmentByTag() returns null.

To be clear:

  • The tag is correct
  • The fragment is found if I do not orientate the device, and instead kick off the async task somewhere else, during the activity's onResume() for example.
  • If I do not tell the headless fragment to retain itself - thereby losing the benefit of not recreating it, the portrait fragment is also correctly found.
  • Debugging I can see all 3 fragments in the manager if the headless one is not set to retain itself. If it is, I can only see the headless fragment.

Maybe retaining a fragment aggressively kills other fragments that are not retained or something to that effect?

1 个答案:

答案 0 :(得分:4)

问题的根源在于如何维护无头片段内活动的引用 从提供的代码中可以看出,在完成AsyncTask之后如何更新UI,我们假设您使用了第一个代码段中的mRequest。当您需要新的AsyncTask时,将mRequest赋予构造函数,并在AsyncTask完成后使用此引用。
当创建活动和更新UI之间没有屏幕旋转时,没关系。这是因为您使用了对仍处于活动状态的活动的引用 旋转屏幕是不行的。每次轮换后你都有新的活动。但是,当您在活动的onCreate()的第一次调用中创建无头片段时,mRequest仅分配一次。因此它包含对旋转后无效的第一个活动实例的引用。在您的情况下,旋转后有2个活动实例:第一个 - 由mRequest引用,第二个 - 是可见且活动的。您可以通过记录onCreate内的活动参考:Log.i(TAG, "onCreate: this=" + this);和内部活动的方法来确认这一点,该方法在异步任务后更新UI:Log.i(TAG, "updating UI: this=" + this);
除了第一个活动处于毁灭状态。所有片段都与此活动分离,并且未保留的片段被破坏。这就是findFragmentByTag返回null的原因 如果无头片段未设置为保留自身,则活动的onCreate()会在每次调用中重新创建它。所以mRequest总是引用最后创建的活动与所有片段。在这种情况下,findFragmentByTag不返回空。

为了避免这个问题,我建议:

  1. 使用弱引用来存储Activity的引用。这样的事情:
    private WeakReference<RequestCustomerDetails> mRequest;
  2. HeadlessCustomerDetailFetchFragment中创建一个方法来更新此引用。
    public void updateResultProcessor(RequestCustomerDetails requestCustomerDetails) { mRequest = new WeakReference(requestCustomerDetails); // Update ui if there is stored result of AsyncTask (see p.4b) }
  3. 每次都从活动的onCreate()调用此方法。
  4. 当AsyncTask结束时:
    a)如果mRequest.get()不是null,则更新用户界面 b)如果mRequest.get()null,则将结果存储在无头碎片内并在第2页中使用。

    弱引用将允许GC处理被破坏的活动并在弱引用内设置null。弱引用中的空值将表示没有UI且无需更新。在无头片段中存储AsyncTask的结果将允许使用此结果在娱乐后更新UI。

    希望这会有所帮助。对不起我的英语不好。如果事情不清楚,我会尝试解释。