片段 A 的 findFragmentByTag 为 null,如果片段 B 上有 setRetain(true)
findFragmentByTag null for Fragment A, if setRetain(true) on Fragment B
我的问题涉及 activity 托管三个支持片段。一个是普通的程序化片段(我们称它为主页片段)。一种是在设备定向时添加在家庭片段之上的肖像片段,一种是 'headless',无论配置更改如何继续异步任务。很简单,我正在工作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);
}
}
在我的 activity 的 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;
}
然后,在 6 秒延迟(用于测试)后,我在方向更改为纵向时添加的纵向片段的 onCreateView() 中启动异步任务(通过我的 startFetching() 函数)。在 activity 的 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);
}
任务完成后,我return到activity并尝试更新活动肖像片段的UI,但片段管理器找不到它,findFragmentByTag() returns null.
要明确:
- 标签正确
- 如果我 不 定位设备,而是在其他地方启动异步任务,在 activity 的 onResume() for例如。
- 如果我不告诉无头片段保留自己——从而失去不重新创建它的好处,肖像片段也能正确找到。
- 调试我可以在管理器中看到所有 3 个片段如果无头片段没有设置为保留自身。如果是,我只能 看到无头片段。
也许保留一个片段会主动杀死其他未保留的片段或类似的东西?
问题的根源在于您如何在无头片段中维护对 activity 的引用。
从提供的代码中不清楚在 AsyncTask 完成后如何更新 UI,让我们假设您使用第一个代码片段中的 mRequest
。当您需要新的 AsyncTask 并在 AsyncTask 完成后使用此引用时,您将 mRequest
提供给构造函数。
没关系,如果在创建 activity 和更新 UI 之间没有屏幕旋转。这是因为您使用了对仍然有效的 activity 的引用。
如果你旋转屏幕是不行的。轮换后每次都有新的 activity。但是当你在 activity 的 onCreate()
的第一次调用中创建无头片段时,mRequest 只被分配一次。因此它包含对 activity 的第一个实例的引用,该实例在旋转后不活动。在您的情况下,轮换后有 2 个 activity 实例:第一个 - 由 mRequest 引用,第二个 - 可见且活动。您可以通过在 onCreate
中记录 activity 的引用来确认这一点:Log.i(TAG, "onCreate: this=" + this);
以及在异步任务后更新 UI 的 activity 方法中:[=15] =]
此外,第一个 activity 处于 Destroyed 状态。所有片段都从此 activity 分离,非保留片段被销毁。这就是为什么 findFragmentByTag
returns null.
如果无头片段未设置为保留自身,则 activity 的 onCreate()
会在每次调用时重新创建它。所以 mRequest
总是引用最后创建的 activity 和所有片段。在这种情况下 findFragmentByTag
returns 不为空。
为了避免这个问题,我建议:
- 使用弱引用存储Activity的引用。像这样:
private WeakReference<RequestCustomerDetails> mRequest;
- 在
HeadlessCustomerDetailFetchFragment
中创建一个方法来更新此引用。
public void updateResultProcessor(RequestCustomerDetails requestCustomerDetails) {
mRequest = new WeakReference(requestCustomerDetails);
// Update ui if there is stored result of AsyncTask (see p.4b)
}
- 每次从 activity 的 onCreate() 调用此方法。
- AsyncTask 完成时:
a) 如果 mRequest.get()
不是 null
则更新 UI.
b) 如果 mRequest.get()
是 null
然后将结果存储在无头片段中并在 p.2.
中使用它
弱引用将允许 GC 处理 destroyed activity 并在弱引用中设置 null。弱引用中的 Null 将表示没有 UI 并且没有要更新的内容。将 AsyncTask 的结果存储在无头片段中将允许在重新创建后使用此结果更新 UI。
希望这会有所帮助。对不起我的英语不好。如果有什么不清楚的地方,我会尽力解释。
我的问题涉及 activity 托管三个支持片段。一个是普通的程序化片段(我们称它为主页片段)。一种是在设备定向时添加在家庭片段之上的肖像片段,一种是 'headless',无论配置更改如何继续异步任务。很简单,我正在工作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);
}
}
在我的 activity 的 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;
}
然后,在 6 秒延迟(用于测试)后,我在方向更改为纵向时添加的纵向片段的 onCreateView() 中启动异步任务(通过我的 startFetching() 函数)。在 activity 的 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);
}
任务完成后,我return到activity并尝试更新活动肖像片段的UI,但片段管理器找不到它,findFragmentByTag() returns null.
要明确:
- 标签正确
- 如果我 不 定位设备,而是在其他地方启动异步任务,在 activity 的 onResume() for例如。
- 如果我不告诉无头片段保留自己——从而失去不重新创建它的好处,肖像片段也能正确找到。
- 调试我可以在管理器中看到所有 3 个片段如果无头片段没有设置为保留自身。如果是,我只能 看到无头片段。
也许保留一个片段会主动杀死其他未保留的片段或类似的东西?
问题的根源在于您如何在无头片段中维护对 activity 的引用。
从提供的代码中不清楚在 AsyncTask 完成后如何更新 UI,让我们假设您使用第一个代码片段中的 mRequest
。当您需要新的 AsyncTask 并在 AsyncTask 完成后使用此引用时,您将 mRequest
提供给构造函数。
没关系,如果在创建 activity 和更新 UI 之间没有屏幕旋转。这是因为您使用了对仍然有效的 activity 的引用。
如果你旋转屏幕是不行的。轮换后每次都有新的 activity。但是当你在 activity 的 onCreate()
的第一次调用中创建无头片段时,mRequest 只被分配一次。因此它包含对 activity 的第一个实例的引用,该实例在旋转后不活动。在您的情况下,轮换后有 2 个 activity 实例:第一个 - 由 mRequest 引用,第二个 - 可见且活动。您可以通过在 onCreate
中记录 activity 的引用来确认这一点:Log.i(TAG, "onCreate: this=" + this);
以及在异步任务后更新 UI 的 activity 方法中:[=15] =]
此外,第一个 activity 处于 Destroyed 状态。所有片段都从此 activity 分离,非保留片段被销毁。这就是为什么 findFragmentByTag
returns null.
如果无头片段未设置为保留自身,则 activity 的 onCreate()
会在每次调用时重新创建它。所以 mRequest
总是引用最后创建的 activity 和所有片段。在这种情况下 findFragmentByTag
returns 不为空。
为了避免这个问题,我建议:
- 使用弱引用存储Activity的引用。像这样:
private WeakReference<RequestCustomerDetails> mRequest;
- 在
HeadlessCustomerDetailFetchFragment
中创建一个方法来更新此引用。
public void updateResultProcessor(RequestCustomerDetails requestCustomerDetails) { mRequest = new WeakReference(requestCustomerDetails); // Update ui if there is stored result of AsyncTask (see p.4b) }
- 每次从 activity 的 onCreate() 调用此方法。
- AsyncTask 完成时:
a) 如果mRequest.get()
不是null
则更新 UI.
b) 如果mRequest.get()
是null
然后将结果存储在无头片段中并在 p.2.
中使用它 弱引用将允许 GC 处理 destroyed activity 并在弱引用中设置 null。弱引用中的 Null 将表示没有 UI 并且没有要更新的内容。将 AsyncTask 的结果存储在无头片段中将允许在重新创建后使用此结果更新 UI。
希望这会有所帮助。对不起我的英语不好。如果有什么不清楚的地方,我会尽力解释。