当我的应用程序在后台运行时试图显示来自AsyncTask的错误消息时,它会崩溃



我正在向我的Fragment显示一条来自AsyncTask的错误消息,但当我的应用程序处于后台时,该应用程序将崩溃。

代码如下:

import android.support.v4.app.FragmentManager;
public class AppUtil {
public static void showErrorDialog(FragmentManager fm, int messageResourceId) {
if (fm != null && !fm.isDestroyed()) {
InfoDialogFragment.newInstance("", messageResourceId).show(fm, "ERROR");
}
}
public static boolean isEmpty(String s) {
return s == null || s.trim().length() == 0;
}
}

import android.app.ProgressDialog;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
public class BlankFragment extends Fragment  implements ILoginable, 
View.OnClickListener {
private ProgressDialog pd ;
public BlankFragment() {
// Required empty public constructor
}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View rootView =  inflater.inflate(R.layout.fragment_blank, 
container, false);
rootView.findViewById(R.id.pressBT).setOnClickListener(this);
return rootView;
}
@Override
public void starProgress(int messageId) {
if (pd == null) {
pd = new ProgressDialog(getContext(), R.string.loading);
pd.show();
}
}
@Override
public void endProgress() {
if(pd != null && isAdded()) {
pd.dismiss();
pd = null;
}
}
@Override
public void displayError(int messageId) {
if (isAdded()) {
AppUtil.showErrorDialog(getFragmentManager(), R.string.app_name);
}
}
@Override
public void loginResult(boolean success) {
}
@Override
public void onClick(View v) {
if (R.id.pressBT == v.getId()) {
new LoginAsyncTask(this).execute("x");
}
}
}

public interface ILoginable {
void starProgress(int messageId);
void endProgress();
void displayError(int messageId);
void loginResult(boolean success);
}

import android.os.AsyncTask;
import java.lang.ref.WeakReference;
public class LoginAsyncTask extends AsyncTask<String, Void, String> {
private WeakReference<ILoginable> reference;
public LoginAsyncTask(ILoginable iLoginable) {
reference = new WeakReference<>(iLoginable);
}
@Override
protected void onPreExecute() {
reference.get().starProgress(R.string.loading);
}
@Override
protected String doInBackground(String... strings) {
return "xx";
}
@Override
protected void onPostExecute(String s) {
if (reference.get() !=  null) {
reference.get().endProgress();
reference.get().displayError(R.string.hello_blank_fragment);
}
reference.clear();
}
}

正如您所看到的,这是基本asyncTask的一个简单行为。然而,我不明白为什么当应用程序在后台时,isAdded方法返回true,也不明白为什么应用程序崩溃并显示消息:

*** Process: com.roscasend.android.testadd, PID: 2891
java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
at android.support.v4.app.FragmentManagerImpl.checkStateLoss(FragmentManager.java:2053)
at android.support.v4.app.FragmentManagerImpl.enqueueAction(FragmentManager.java:2079)
at android.support.v4.app.BackStackRecord.commitInternal(BackStackRecord.java:678)
at android.support.v4.app.BackStackRecord.commit(BackStackRecord.java:632)
at android.support.v4.app.DialogFragment.show(DialogFragment.java:143)
at com.roscasend.android.testadd.AppUtil.showErrorDialog(AppUtil.java:17)
at com.roscasend.android.testadd.BlankFragment.displayError(BlankFragment.java:51)
at com.roscasend.android.testadd.LoginAsyncTask.onPostExecute(LoginAsyncTask.java:37)
at com.roscasend.android.testadd.LoginAsyncTask.onPostExecute(LoginAsyncTask.java:15)***

谨致问候,Aurelian

IllegalStateException: Can not perform this action after onSaveInstanceState

Android FragmentManager使用savedInstanceState捆绑包来跟踪当前向用户显示的片段。如果保存了状态,并且尝试执行片段事务,则系统将崩溃,因为用户永远看不到该事务。

支持库的较新版本在FragmentManager上提供了一个isStateSaved()方法,您可以使用该方法来检查是否处于显示对话框的安全窗口中。

@Override
public void displayError(int messageId) {
FragmentManager fm = getFragmentManager();
if (!fm.isStateSaved()) {
AppUtil.showErrorDialog(fm, R.string.app_name);
}
}

然而,这仍然给您留下了一个问题。如果AsyncTask完成并试图在状态已保存时显示错误,则用户将永远不会看到该错误。

你可以让应用程序记录它试图显示错误,然后在应用程序恢复时进行检查,看看它现在是否应该显示错误。我们需要一个地方来保存我们试图显示对话框的信息,它不能只是活动/片段上的boolean(因为这会遇到相同的状态保存问题)。我推荐SharedPreferences来存储信息。

将此代码添加到您的片段:

private static final String SHOW_DIALOG = "SHOW_DIALOG";
private SharedPreferences getSharedPreferences() {
return getActivity().getPreferences(Context.MODE_PRIVATE);
}

然后使用此代码显示对话框:

@Override
public void displayError(int messageId) {
FragmentManager fm = getFragmentManager();
if (!fm.isStateSaved()) {
AppUtil.showErrorDialog(getFragmentManager(), R.string.app_name);
} else {
getSharedPreferences()
.edit()
.putBoolean(SHOW_DIALOG, true)
.apply();
}
}

当应用程序恢复时,显示对话框的代码如下:

@Override
public void onResume() {
super.onResume();
SharedPreferences prefs = getSharedPreferences();
boolean triedToShowInBackground = prefs.getBoolean(SHOW_DIALOG, false);
if (triedToShowInBackground) {
AppUtil.showErrorDialog(getFragmentManager(), R.string.app_name);
prefs.edit().remove(SHOW_DIALOG).apply();
}
}

即使你的应用程序在后台,片段也总是被附加的,我认为你可以使用isVisible(),如果片段当前对用户可见,则返回true。因此,如果碎片可见,则显示对话框

如果您尝试使用commitAllowingStateLoss()而不是commit()来显示FragmentDialog,问题就会得到解决。要模仿父show方法并设置内部字段,必须使用反射。您可以在InfoDialogFragment类中重写show方法,如下所示:

import android.support.v4.app.DialogFragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
import java.lang.reflect.Field;
public class InfoDialogFragment extends DialogFragment {
@Override
public void show(FragmentManager manager, String tag) {
try {
Field mDismissedField = DialogFragment.class.getField("mDismissed");
mDismissedField.setAccessible(true);
mDismissedField.setBoolean(this, false);
Field mShownByMeField = DialogFragment.class.getField("mShownByMe");
mShownByMeField.setAccessible(true);
mShownByMeField.setBoolean(this, true);
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
FragmentTransaction ft = manager.beginTransaction();
ft.add(this, tag);
ft.commitAllowingStateLoss();
}
// Other parts of code ...
}

尝试将其添加到片段中

@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if(isVisibleToUser && isResumed())
onResume();
}
@Override
public void onResume() {
super.onResume();
if(!getUserVisibleHint()){
return;
}
// Populate UI
}

最新更新