在设置Fragment B的setRetain(true)后,针对Fragment A执行findFragmentByTag操作返回null。

8

我的问题涉及一个包含三个支持片段的活动。其中一个是普通编程片段(我们称之为主页片段)。当设备方向发生变化时,另一个是添加在主页片段上面的纵向片段,还有一个是“无头”的片段,可以继续异步任务而不受配置更改的影响。非常简单,我是根据这个很好的示例进行工作的。

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

在我的活动的onCreate()方法中,如果必要的话,我会创建并添加无界面碎片。

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

然后我会在创建视图的时候,通过我的startFetching()函数,在延迟6秒后(用于测试)启动一个异步任务。这个视图碎片被添加到当屏幕方向变为纵向时。屏幕方向的改变是在活动的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);
    }

任务完成后,我返回到活动并尝试更新活动肖像片段的UI,但是片段管理器找不到它,findFragmentByTag() 返回 null
明确一下:
  • 标记是正确的。
  • 如果我在设备未发生方向变化时(比如在活动的 onResume() 中)在其他地方启动异步任务,则可以找到该片段。
  • 如果我不要求无头片段保留自己-从而失去不重新创建它的好处,则可以正确找到纵向片段。
  • 调试时,如果无头片段没有被保留,我可以在管理器中看到所有3个片段。如果保留它,则我只能看到无头片段。
也许过于积极地保留一个片段会杀死其他没有被保留的片段或其他类似的影响?

为什么你不能在一个服务中执行异步任务呢?我相信这可以解决你的旋转问题,同时也是更整洁的做法。 - Smashing
我也这么认为 - 我对服务了解不多,似乎为一个任务而言有些过度,但这可能是我的唯一选择 - 谢谢,我会研究一下的。 - Daniel Wilson
1
很酷。如果需要帮助,请告诉我。 - Smashing
也许 setRetained 会改变 popBackStackImmediate(name, flags) 的工作方式?我可以尝试删除或修改 getSupportFragmentManager().popBackStackImmediate(null, FragmentManager.POP_BACK_STACK_INCLUSIVE); - Leo Landau
尝试使用https://dev59.com/g5Dea4cB1Zd3GeqPbGeH#33342499。 - Mohammad Hossein Gerami
1个回答

4
问题的根源在于如何在无界面(fragment)中保留对活动(activity)的引用。提供的代码中不清楚如何在AsyncTask完成后更新UI,假设您从第一个代码段中使用mRequest。当需要新的AsyncTask时,将mRequest传递给构造函数,并在AsyncTask完成后使用此引用。如果在创建活动和更新UI之间没有屏幕旋转,那么这是可以的。这是因为您使用仍然活动的活动引用。如果旋转屏幕,则情况就不同了。每次旋转后都会有新的活动。但是,当您在第一次调用活动的onCreate()时创建无头片段时,mRequest仅分配一次。因此,它包含对第一个实例的活动的引用,在旋转后不再活动。在旋转后,您的情况下有两个活动实例:第一个是由mRequest引用的活动,第二个是可见且活动的。可以通过在onCreate()中记录活动引用以及更新异步任务后更新UI的活动方法内部记录来确认这一点。
此外,第一个活动处于销毁状态。所有片段都已从此活动中分离,而未保留的片段已被销毁。这就是findFragmentByTag返回null的原因。如果无头片段没有设置为保留它自己,则活动的onCreate()在每次调用时都会重新创建它。因此,mRequest始终引用具有所有片段的最后一个创建的活动。在这种情况下,findFragmentByTag返回不为null。
为避免此问题,我建议:
1.使用弱引用来存储活动引用。例如:private WeakReference<RequestCustomerDetails> mRequest; 2.在HeadlessCustomerDetailFetchFragment中创建一个方法来更新此引用。 public void updateResultProcessor(RequestCustomerDetails requestCustomerDetails) { mRequest = new WeakReference(requestCustomerDetails); // 如果存在AsyncTask的存储结果,则更新ui (请参见p.4b) } 3.每次从活动的onCreate()调用此方法。
4.当AsyncTask完成时: a)如果mRequest.get()不为null,则更新UI。 b)如果mRequest.get()为null,则将结果存储在无头片段中,并在p.2中使用它。
弱引用将允许GC处理销毁的活动并在弱引用内设置null。弱引用内的空值将表示没有UI可供更新。在无头片段中存储AsyncTask的结果将允许在重建之后使用此结果来更新UI。
希望这可以帮助解决问题。如果有什么不清楚的地方,我会尽力解释。

稍后会研究这个问题,但是这是赏金,看起来非常像一个正确的答案,所以谢谢。 - Daniel Wilson

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接