代码之家  ›  专栏  ›  技术社区  ›  aurelianr

我的应用程序在后台运行时试图显示AsyncTask的错误消息时崩溃

  •  0
  • aurelianr  · 技术社区  · 5 年前

    我正在显示一条从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();
        }
    }
    

    如您所见,这是一个基本异步任务的简单行为。但是,我无法理解为什么当应用程序在后台时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)***
    

    顺致敬意, 奥勒良

    4 回复  |  直到 5 年前
        1
  •  2
  •   Ahmed.ess    5 年前

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

        2
  •  2
  •   Ben P.    5 年前
    IllegalStateException: Can not perform this action after onSaveInstanceState
    

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

    较新版本的支持库提供了 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();
        }
    }
    
        3
  •  0
  •   aminography    5 年前

    如果你试着展示 FragmentDialog 使用 commitAllowingStateLoss() 而不是 commit() show 方法和设置内部字段时,必须使用反射。您可以覆盖 InfoDialogFragment 分类如下:

    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 ...
    
    }
    
        4
  •  0
  •   Tahir Ferli    5 年前

    试着在你的片段中添加这个

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