旋转后 onPostExecute 中的 IllegalStateException 片段未附加到 activity 但未调用 onDetach
IllegalStateException fragment not attached to activity in onPostExecute after rotation but onDetach not called
我有一个使用 NavigationDrawer 模式的 AppCompatActivity,管理一些片段。其中一个没有 setRetainInstance(true)
,我展示了一个带有 ProgressDialog 的 DialogFragment 和一个带有以下代码的 AsyncTask:
SavingLoader savingLoader = SavingLoader.newInstance(savingLoaderMaxValue);
savingLoader.show(getChildFragmentManager(), SAVING_LOADER_TAG);
new MyAsyncTask().execute();
其中 SavingLoader
class 是这个:
public class SavingLoader extends DialogFragment {
private static final String MAX_VALUE_TAG = "MAX_VALUE_TAG";
private static final String PROGRESS_VALUE_TAG = "PROGRESS_VALUE_TAG";
public static SavingLoader newInstance(int max_value){
SavingLoader s = new SavingLoader();
Bundle args = new Bundle();
args.putInt(MAX_VALUE_TAG, max_value);
s.setArguments(args);
return s;
}
private ProgressDialog dialog;
public SavingLoader(){}
@Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setCancelable(false);
}
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState){
dialog = new ProgressDialog(getActivity(), getTheme());
dialog.setTitle(getString(R.string.dialog_title_saving));
dialog.setMessage(getString(R.string.dialog_message_saving));
dialog.setIndeterminate(false);
int max = (savedInstanceState == null ?
getArguments().getInt(MAX_VALUE_TAG) : savedInstanceState.getInt(MAX_VALUE_TAG));
if (max >= 1){
dialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
dialog.setProgress((savedInstanceState == null ?
0 : savedInstanceState.getInt(PROGRESS_VALUE_TAG)));
dialog.setMax(max);
} else dialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
return dialog;
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt(MAX_VALUE_TAG, dialog.getMax());
outState.putInt(PROGRESS_VALUE_TAG, dialog.getProgress());
}
public int getProgress(){
return dialog.getProgress();
}
public int getMax(){
return dialog.getMax();
}
public void incrementProgressBy(int value){
if (dialog.getProgress() + value <= dialog.getMax())
dialog.incrementProgressBy(value);
}
}
在 onPostExecute()
方法中,我需要执行一些 UI 更新,所以这是我的问题:如果我启动对话框和 AsyncTask(如上)并且我不旋转我的 phone,一切都按预期工作。如果我在 onPostExecute()
方法之后旋转 phone 也是一样的。但是,如果我在 AsyncTask 仍为 运行 时旋转我的 phone,当它完成并到达 onPostExecute()
方法时,它会给我 IllegalStateException
说托管 AsyncTask 的片段和Dialogfragment 不再附加到 activity。因此,我尝试覆盖片段的 onAttach()
和 onDetach()
方法(使用简单的 System.out.println
),以查看 onPostExecute()
何时被调用。结果是当我旋转 phone 时,我总是得到这个输出:
onDetach
onAttach
... (if I rotate more my phone)
onPostExecute
那么当 AsyncTask 完成时不应该附加片段吗?谢谢大家的时间和关注。
我终于设法通过停止使用 AsyncTask
并使用 LoaderManager + AsyncTaskLoader following this article. In short, your fragment must implement the LoaderCallbacks 界面并管理 AsyncTaskLoader
来解决这个问题。骨骼片段可能是这样的:
public class MyFragment extends Fragment implements LoaderManager.LoaderCallbacks {
@Override
public View onCreateView(LayoutInflater inflater, final ViewGroup container,
Bundle savedInstanceState) {
// Inflate here your view as you usually do and find your components
// For example imagine to have a button tha will fire the task
Button b = (Button) view.findViewById(R.id.my_button);
b.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// Use this to start task for the first time
getLoaderManager().initLoader(0, null, this);
// .. or this for restart the task, details in
// the provided article
// getLoaderManager().restartLoader(0, null, this);
}
});
// Get fragments load manager
LoaderManager lm = getLoaderManager();
if (lm.getLoader(0) != null) {
// Reconnect to an existing loader
lm.initLoader(0, null, this);
}
// Return your view here
return view;
}
// LoaderCallbacks methods to override
@Override
public Loader onCreateLoader(int id, Bundle args) {
// Create an instance of the loader passing the context
MyTaskLoader loader = new MyTaskLoader(getActivity());
loader.forceLoad();
return loader;
}
@Override
public void onLoadFinished(Loader loader, Object data) {
// Use this callback as you would use the AsyncTask "onPostExecute"
}
@Override
public void onLoaderReset(Loader loader) {}
// Now define the loader class
private static class MyTaskLoader extends AsyncTaskLoader {
public MyTaskLoader(Context context){
super(context);
}
@Override
public Object loadInBackground() {
// Do here your async work
}
}
}
我有一个使用 NavigationDrawer 模式的 AppCompatActivity,管理一些片段。其中一个没有 setRetainInstance(true)
,我展示了一个带有 ProgressDialog 的 DialogFragment 和一个带有以下代码的 AsyncTask:
SavingLoader savingLoader = SavingLoader.newInstance(savingLoaderMaxValue);
savingLoader.show(getChildFragmentManager(), SAVING_LOADER_TAG);
new MyAsyncTask().execute();
其中 SavingLoader
class 是这个:
public class SavingLoader extends DialogFragment {
private static final String MAX_VALUE_TAG = "MAX_VALUE_TAG";
private static final String PROGRESS_VALUE_TAG = "PROGRESS_VALUE_TAG";
public static SavingLoader newInstance(int max_value){
SavingLoader s = new SavingLoader();
Bundle args = new Bundle();
args.putInt(MAX_VALUE_TAG, max_value);
s.setArguments(args);
return s;
}
private ProgressDialog dialog;
public SavingLoader(){}
@Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setCancelable(false);
}
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState){
dialog = new ProgressDialog(getActivity(), getTheme());
dialog.setTitle(getString(R.string.dialog_title_saving));
dialog.setMessage(getString(R.string.dialog_message_saving));
dialog.setIndeterminate(false);
int max = (savedInstanceState == null ?
getArguments().getInt(MAX_VALUE_TAG) : savedInstanceState.getInt(MAX_VALUE_TAG));
if (max >= 1){
dialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
dialog.setProgress((savedInstanceState == null ?
0 : savedInstanceState.getInt(PROGRESS_VALUE_TAG)));
dialog.setMax(max);
} else dialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
return dialog;
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt(MAX_VALUE_TAG, dialog.getMax());
outState.putInt(PROGRESS_VALUE_TAG, dialog.getProgress());
}
public int getProgress(){
return dialog.getProgress();
}
public int getMax(){
return dialog.getMax();
}
public void incrementProgressBy(int value){
if (dialog.getProgress() + value <= dialog.getMax())
dialog.incrementProgressBy(value);
}
}
在 onPostExecute()
方法中,我需要执行一些 UI 更新,所以这是我的问题:如果我启动对话框和 AsyncTask(如上)并且我不旋转我的 phone,一切都按预期工作。如果我在 onPostExecute()
方法之后旋转 phone 也是一样的。但是,如果我在 AsyncTask 仍为 运行 时旋转我的 phone,当它完成并到达 onPostExecute()
方法时,它会给我 IllegalStateException
说托管 AsyncTask 的片段和Dialogfragment 不再附加到 activity。因此,我尝试覆盖片段的 onAttach()
和 onDetach()
方法(使用简单的 System.out.println
),以查看 onPostExecute()
何时被调用。结果是当我旋转 phone 时,我总是得到这个输出:
onDetach
onAttach
... (if I rotate more my phone)
onPostExecute
那么当 AsyncTask 完成时不应该附加片段吗?谢谢大家的时间和关注。
我终于设法通过停止使用 AsyncTask
并使用 LoaderManager + AsyncTaskLoader following this article. In short, your fragment must implement the LoaderCallbacks 界面并管理 AsyncTaskLoader
来解决这个问题。骨骼片段可能是这样的:
public class MyFragment extends Fragment implements LoaderManager.LoaderCallbacks {
@Override
public View onCreateView(LayoutInflater inflater, final ViewGroup container,
Bundle savedInstanceState) {
// Inflate here your view as you usually do and find your components
// For example imagine to have a button tha will fire the task
Button b = (Button) view.findViewById(R.id.my_button);
b.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// Use this to start task for the first time
getLoaderManager().initLoader(0, null, this);
// .. or this for restart the task, details in
// the provided article
// getLoaderManager().restartLoader(0, null, this);
}
});
// Get fragments load manager
LoaderManager lm = getLoaderManager();
if (lm.getLoader(0) != null) {
// Reconnect to an existing loader
lm.initLoader(0, null, this);
}
// Return your view here
return view;
}
// LoaderCallbacks methods to override
@Override
public Loader onCreateLoader(int id, Bundle args) {
// Create an instance of the loader passing the context
MyTaskLoader loader = new MyTaskLoader(getActivity());
loader.forceLoad();
return loader;
}
@Override
public void onLoadFinished(Loader loader, Object data) {
// Use this callback as you would use the AsyncTask "onPostExecute"
}
@Override
public void onLoaderReset(Loader loader) {}
// Now define the loader class
private static class MyTaskLoader extends AsyncTaskLoader {
public MyTaskLoader(Context context){
super(context);
}
@Override
public Object loadInBackground() {
// Do here your async work
}
}
}