Po wyświetleniu okna dialogowego otrzymuję komunikat „Nie można wykonać tej czynności po onSaveInstanceState”


121

Niektórzy użytkownicy zgłaszają, że jeśli używają szybkiej akcji na pasku powiadomień, zbliżają się do siły.

W powiadomieniu pokazuję szybką akcję, która wywołuje klasę „TestDialog” . W klasie TestDialog po naciśnięciu przycisku „drzemka” pokażę SnoozeDialog.

private View.OnClickListener btnSnoozeOnClick() {
    return new View.OnClickListener() {

        public void onClick(View v) {
            showSnoozeDialog();
        }
    };
}

private void showSnoozeDialog() {
    FragmentManager fm = getSupportFragmentManager();
    SnoozeDialog snoozeDialog = new SnoozeDialog();
    snoozeDialog.show(fm, "snooze_dialog");
}

Błąd jest *IllegalStateException: Can not perform this action after onSaveInstanceState*.

Wiersz kodu, w którym jest uruchamiany wyjątek IllegarStateException, to:

snoozeDialog.show(fm, "snooze_dialog");

Klasa rozszerza „FragmentActivity”, a klasa „SnoozeDialog” rozszerza „DialogFragment”.

Oto pełny ślad stosu błędu:

java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
at android.support.v4.app.FragmentManagerImpl.checkStateLoss(FragmentManager.java:1327)
at android.support.v4.app.FragmentManagerImpl.enqueueAction(FragmentManager.java:1338)
at android.support.v4.app.BackStackRecord.commitInternal(BackStackRecord.java:595)
at android.support.v4.app.BackStackRecord.commit(BackStackRecord.java:574)
at android.support.v4.app.DialogFragment.show(DialogFragment.java:127)
at com.test.testing.TestDialog.f(TestDialog.java:538)
at com.test.testing.TestDialog.e(TestDialog.java:524)
at com.test.testing.TestDialog.d(TestDialog.java:519)
at com.test.testing.g.onClick(TestDialog.java:648)
at android.view.View.performClick(View.java:3620)
at android.view.View$PerformClick.run(View.java:14292)
at android.os.Handler.handleCallback(Handler.java:605)
at android.os.Handler.dispatchMessage(Handler.java:92)
at android.os.Looper.loop(Looper.java:137)
at android.app.ActivityThread.main(ActivityThread.java:4507)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:511)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:790)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:557)
at dalvik.system.NativeStart.main(Native Method)

Nie mogę odtworzyć tego błędu, ale otrzymuję wiele raportów o błędach.

Czy ktoś może pomóc, jak mogę naprawić ten błąd?


2
Znalazłeś rozwiązanie? Mam ten sam problem co Ty. Zadałem tutaj: stackoverflow.com/questions/15730878/ ... Sprawdź moje pytanie i zobacz możliwe rozwiązanie, które nie działa w moim przypadku. Może to zadziała dla Ciebie.
rootpanthera

Jeszcze nie ma rozwiązania :-( A twoja sugestia została już dodana do moich zajęć.
chrisonline

Sprawdź zaakceptowaną odpowiedź tutaj. To rozwiązało mój problem: stackoverflow.com/questions/14177781/ ...
bogdan

4
Czy Twoja aktywność jest widoczna po uruchomieniu tego okna dialogowego? Wygląda na to, że przyczyną może być próba wyświetlenia przez aplikację okna dialogowego dołączonego do działania, które zostało wstrzymane / zatrzymane.
Kai

stackoverflow.com/questions/7575921/ ... próbowałeś tego. Jestem pewien.
Orion

Odpowiedzi:


66

To częsty problem . Rozwiązaliśmy ten problem, zastępując metodę show () i obsługując wyjątek w rozszerzonej klasie DialogFragment

public class CustomDialogFragment extends DialogFragment {

    @Override
    public void show(FragmentManager manager, String tag) {
        try {
            FragmentTransaction ft = manager.beginTransaction();
            ft.add(this, tag);
            ft.commit();
        } catch (IllegalStateException e) {
            Log.d("ABSDIALOGFRAG", "Exception", e);
        }
    }
}

Należy pamiętać, że zastosowanie tej metody nie zmieni wewnętrznych pól pliku DialogFragment.class:

boolean mDismissed;
boolean mShownByMe;

W niektórych przypadkach może to prowadzić do nieoczekiwanych wyników. Lepiej użyj commitAllowingStateLoss () zamiast commit ()


3
Ale dlaczego pojawia się ten problem? Czy można zignorować błąd? Co się stanie, kiedy to zrobisz? Przecież po kliknięciu oznacza to, że aktywność jest aktywna i ma się dobrze ... W każdym razie pisałem o tym tutaj, ponieważ uważam to za błąd: code.google.com/p/android/issues/detail?id= 207269
programista Androida

1
Czy możesz w takim razie oznaczyć i / lub skomentować to miejsce?
programista Androida

2
lepiej zadzwonić do super.show (manager, tag) wewnątrz klauzuli try-catch. W ten sposób flagi należące do DialogFragment mogą pozostać bezpieczne
Shayan_Aryan

20
W tym momencie możesz wywołać commitAllowingStateLoss () zamiast commit (). Wyjątek nie zostałby zgłoszony.
ARLabs

1
@ARLabs Wyobrażam sobie, że w tym przypadku byłoby to również lepsze dla osoby odpowiadającej, ponieważ jeśli po prostu złapiesz wyjątek, jak pokazano tutaj, okno dialogowe z pewnością nie zostanie pokazane. Lepiej pokazać to okno dialogowe, jeśli możesz, i może po prostu zniknąć, jeśli trzeba będzie przywrócić stan. Utrzymuj również niskie zużycie pamięci aplikacji w tle, aby nie zostało zniszczone.
androidguy

27

To znaczy, że commit()( show()w przypadku DialogFragment) fragment po onSaveInstanceState().

Android zapisze stan fragmentu pod adresem onSaveInstanceState(). Tak więc, jeśli commit()fragmentujesz po onSaveInstanceState()fragmencie, stan fragmentu zostanie utracony.

W rezultacie, jeśli aktywność zostanie zabita i odtworzona później, fragment nie zwiększy aktywności, co jest złym doświadczeniem użytkownika. Dlatego Android nie pozwala na utratę stanu za wszelką cenę.

Prostym rozwiązaniem jest sprawdzenie, czy stan został już zapisany.

boolean mIsStateAlreadySaved = false;
boolean mPendingShowDialog = false;

@Override
public void onResumeFragments(){
    super.onResumeFragments();
    mIsStateAlreadySaved = false;
    if(mPendingShowDialog){
        mPendingShowDialog = false;
        showSnoozeDialog();
    }
}

@Override
public void onPause() {
    super.onPause();
    mIsStateAlreadySaved = true;
}

private void showSnoozeDialog() {
    if(mIsStateAlreadySaved){
        mPendingShowDialog = true;
    }else{
        FragmentManager fm = getSupportFragmentManager();
        SnoozeDialog snoozeDialog = new SnoozeDialog();
        snoozeDialog.show(fm, "snooze_dialog");
    }
}

Uwaga: onResumeFragments () będzie wywoływać po wznowieniu fragmentów.


1
A co jeśli chcę pokazać DialogFragment w innym fragmencie?
programista Androida

Nasze rozwiązanie polega na utworzeniu aktywności i fragmentacji klasy bazowej oraz delegowaniu onResumeFragments do fragmentacji (tworzymy onResumeFragments w klasie bazowej fragmentu). Nie jest to fajne rozwiązanie, ale działa. Jeśli masz jakieś lepsze rozwiązanie, daj mi znać :)
Pongpat

Cóż, pomyślałem, że pokazanie okna dialogowego w "onStart" powinno działać dobrze, ponieważ fragment na pewno jest wyświetlany, ale wciąż widzę raporty o awariach na ten temat. Poinstruowano mnie, abym zamiast tego spróbował umieścić go na „onResume”. Jeśli chodzi o alternatywy, widziałem to: twigstechtips.blogspot.co.il/2014/01/… , ale to dość dziwne.
programista Androida

Myślę, że powód, dla którego twigstechtips.blogspot.co.il/2014/01/ ... działa, ponieważ uruchamia nowy wątek, a tym samym cały kod cyklu życia, tj. OnStart, onResume itp. Wywołany przed uruchomieniem kodu runOnUiThread. Oznacza to, że stan został już przywrócony przed wywołaniem runOnUiThread.
Pongpat

2
Używam pojedynczego połączenia do wysyłania postów (uruchamialne). Jeśli chodzi o getFragmentManager, to zależy. Jeśli chcesz udostępnić to okno dialogowe innej aktywności, powinieneś użyć getFragmentManager, jednak jeśli to okno dialogowe istnieje tylko z fragmentem getChildFragmentManager, lepszym wyborem wydaje się.
Pongpat

16
private void showSnoozeDialog() {
    FragmentManager fm = getSupportFragmentManager();
    SnoozeDialog snoozeDialog = new SnoozeDialog();
    // snoozeDialog.show(fm, "snooze_dialog");
    FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
    ft.add(snoozeDialog, "snooze_dialog");
    ft.commitAllowingStateLoss();
}

ref: link


11

Po kilku dniach chcę podzielić się moją rozwiązanie jak Naprawiłem go, aby pokazać DialogFragment Państwo powinno przesłonić show()metodę to i zadzwonić commitAllowingStateLoss()na Transactionobiekcie. Oto przykład w Kotlinie:

override fun show(manager: FragmentManager?, tag: String?) {
        try {
            val ft = manager?.beginTransaction()
            ft?.add(this, tag)
            ft?.commitAllowingStateLoss()
        } catch (ignored: IllegalStateException) {

        }

    }

1
Aby programiści nie musieli dziedziczyć po DialogFragmenttobie, mogliby to zmienić na funkcję rozszerzenia Kotlin z następującą sygnaturą:fun DialogFragment.showAllowingStateLoss(fragmentManager: FragmentManager, tag: String) . Również try-catch nie jest konieczny, ponieważ wywołujesz commitAllowingStateLoss()metodę, a nie commit()metodę.
Adil Hussain

10

Jeśli okno dialogowe nie jest naprawdę ważne (można go nie pokazywać, gdy aplikacja jest zamknięta / nie jest już widoczna), użyj:

boolean running = false;

@Override
public void onStart() {
    running = true;
    super.onStart();
}

@Override
public void onStop() {
    running = false;
    super.onStop();
}

I otwórz swoje okno dialogowe (fragment) tylko wtedy, gdy uruchamiamy:

if (running) {
    yourDialog.show(...);
}

EDYTUJ, PRAWDOPODOBNIE LEPSZE ROZWIĄZANIE:

Tam, gdzie onSaveInstanceState jest wywoływana w cyklu życia jest nieprzewidywalna, myślę, że lepszym rozwiązaniem jest sprawdzenie isSavedInstanceStateDone () w następujący sposób:

/**
 * True if SavedInstanceState was done, and activity was not restarted or resumed yet.
 */
private boolean savedInstanceStateDone;

@Override
protected void onResume() {
    super.onResume();

    savedInstanceStateDone = false;
}

@Override
protected void onStart() {
    super.onStart();

    savedInstanceStateDone = false;
}

protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    savedInstanceStateDone = true;
}


/**
 * Returns true if SavedInstanceState was done, and activity was not restarted or resumed yet.
 */
public boolean isSavedInstanceStateDone() {
    return savedInstanceStateDone;
}

Wydaje się, że to nie działa, ponieważ otrzymuję ten wyjątek w wywołaniu metody „onStart” (próbuję pokazać tam DialogFragment).
programista Androida

Uratowałeś mi dzień. Dziękuję Frank.
Cüneyt,

7

Od lat spotykam się z tym problemem.
Internet jest zaśmiecony dziesiątkami (setkami? Tysiącami?) Dyskusji na ten temat, a zamieszania i dezinformacji w nich wydaje się mnóstwo.
Aby pogorszyć sytuację, w duchu komiksu xkcd „14 standardów”, wrzucam swoją odpowiedź na ring.
xkcd 14 standardów

The cancelPendingInputEvents(),commitAllowingStateLoss() , catch (IllegalStateException e), I podobne rozwiązania wszystkie wydają okropne.

Mamy nadzieję, że poniższe informacje z łatwością pokazują, jak odtworzyć i rozwiązać problem:

private static final Handler sHandler = new Handler();
private boolean mIsAfterOnSaveInstanceState = true;

@Override
protected void onSaveInstanceState(Bundle outState)
{
    super.onSaveInstanceState(outState);
    mIsAfterOnSaveInstanceState = true; // <- To repro, comment out this line
}

@Override
protected void onPostResume()
{
    super.onPostResume();
    mIsAfterOnSaveInstanceState = false;
}

@Override
protected void onResume()
{
    super.onResume();
    sHandler.removeCallbacks(test);
}

@Override
protected void onPause()
{
    super.onPause();
    sHandler.postDelayed(test, 5000);
}

Runnable test = new Runnable()
{
    @Override
    public void run()
    {
        if (mIsAfterOnSaveInstanceState)
        {
            // TODO: Consider saving state so that during or after onPostResume a dialog can be shown with the latest text
            return;
        }

        FragmentManager fm = getSupportFragmentManager();
        DialogFragment dialogFragment = (DialogFragment) fm.findFragmentByTag("foo");
        if (dialogFragment != null)
        {
            dialogFragment.dismiss();
        }

        dialogFragment = GenericPromptSingleButtonDialogFragment.newInstance("title", "message", "button");
        dialogFragment.show(fm, "foo");

        sHandler.postDelayed(test, 5000);
    }
};

2
Uwielbiam ludzi, którzy głosują negatywnie bez wyjaśnienia. Zamiast głosować tylko w dół, może lepiej by było, gdyby wyjaśnili, dlaczego moje rozwiązanie jest wadliwe? Czy mogę zagłosować przeciw wyborcy przeciw?
swooby

1
Tak, to jest problem SO, piszę ten problem za każdym razem w sugestiach, ale nie chcą rozwiązać.
CoolMind

2
Myślę, że głosy przeciwne mogą wynikać z osadzonego XKCD, odpowiedzi naprawdę nie są miejscem na komentarze społecznościowe (bez względu na to, jak zabawne i / lub prawdziwe).
RestingRobot

6

spróbuj użyć FragmentTransaction zamiast FragmentManager. Myślę, że poniższy kod rozwiąże Twój problem. Jeśli nie, daj mi znać.

FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
SnoozeDialog snoozeDialog = new SnoozeDialog();
snoozeDialog.show(ft, "snooze_dialog");

EDYTOWAĆ:

Transakcja fragmentaryczna

Proszę sprawdzić ten link. Myślę, że rozwiąże Twoje pytania.


4
Jakiekolwiek wyjaśnienie, dlaczego użycie FragmentTransaction rozwiązuje ten problem, byłoby świetne.
Hemanshu,

3
Dialog # show (FragmentManager, tag) robi to samo. To nie jest rozwiązanie.
William

3
Ta odpowiedź nie jest rozwiązaniem. DialogFragment # show (ft) i show (fm) robią dokładnie to samo.
danijoo

@danijoo Masz rację, że oba te zadania wykonują tę samą pracę. Jednak w przypadku kilku telefonów występuje problem podobny do tego, jeśli używasz menedżera fragmentów zamiast fragmenttransaction. Więc w moim przypadku to rozwiązało mój problem.
RIJO RV

6

Korzystanie z nowych zakresów cyklu życia Activity-KTX jest tak proste, jak poniższy przykład kodu:

lifecycleScope.launchWhenResumed {
   showErrorDialog(...)
}

Tę metodę można wywołać bezpośrednio po onStop () i pomyślnie pokaże okno dialogowe po wywołaniu metody onResume () po powrocie.


3

Wiele widoków publikuje zdarzenia wysokiego poziomu, takie jak programy obsługi kliknięć, w kolejce zdarzeń w celu wykonania odroczonego. Problem polega więc na tym, że „onSaveInstanceState” zostało już wywołane dla działania, ale kolejka zdarzeń zawiera odroczone „zdarzenie kliknięcia”. Stąd, kiedy to zdarzenie jest wysyłane do twojego programu obsługi

at android.os.Handler.handleCallback(Handler.java:605)
at android.os.Handler.dispatchMessage(Handler.java:92)
at android.os.Looper.loop(Looper.java:137)

a Twój kod powoduje zgłoszenie showwyjątku IllegalStateException.

Najprostszym rozwiązaniem jest wyczyszczenie kolejki zdarzeń w formacie onSaveInstanceState

protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        // ..... do some work
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            findViewById(android.R.id.content).cancelPendingInputEvents();
        }
}

Czy rzeczywiście potwierdziłeś, że to rozwiązuje problem?
mhsmith

Firma Google dodała to do następnego wydania bibliotek androidx, obecnie w wersji beta ( activityi fragment).
mhsmith

1
@mhsmith Pamiętam, że to rozwiązanie rozwiązało problem w moim kodzie z IllegalStateException
sim

2

Ustaw obiekt fragmentu okna dialogowego na globalny i wywołaj discissAllowingStateLoss () w metodzie onPause ()

@Override
protected void onPause() {
    super.onPause();

    if (dialogFragment != null) {
        dialogFragment.dismissAllowingStateLoss();
    }
}

Nie zapomnij przypisać wartości we fragmencie i wywołać funkcję show () po kliknięciu przycisku lub gdziekolwiek.


1

Chociaż nigdzie o tym oficjalnie nie wspomniano, kilka razy miałem do czynienia z tym problemem. Z mojego doświadczenia wynika, że ​​coś jest nie tak w bibliotece kompatybilności obsługującej fragmenty na starszych platformach, co powoduje ten problem. Możesz to przetestować, używając normalnego interfejsu API menedżera fragmentów. Jeśli nic nie działa, możesz użyć normalnego okna dialogowego zamiast fragmentu okna dialogowego.


1
  1. Dodaj tę klasę do swojego projektu: (musi znajdować się w pakiecie android.support.v4.app )
pakiet android.support.v4.app;


/ **
 * Utworzone przez Gil 16.08.2017.
 * /

public class StatelessDialogFragment rozszerza DialogFragment {
    / **
     * Wyświetl okno dialogowe, dodając fragment za pomocą istniejącej transakcji, a następnie zatwierdzając plik
     * transakcja przy jednoczesnej utracie stanu.
* * Zalecam używanie {@link #show (FragmentTransaction, String)} przez większość czasu, ale * to dotyczy okien dialogowych, na których naprawdę nie zależy. (Debugowanie / śledzenie / reklamy itp.) * * Transakcja @param * Istniejąca transakcja, w której należy dodać fragment. * Tag @param * Znacznik tego fragmentu, zgodnie z * {@link FragmentTransaction # add (Fragment, String) FragmentTransaction.add}. * @return Zwraca identyfikator zatwierdzonej transakcji, zgodnie z * {@link FragmentTransaction # commit () FragmentTransaction.commit ()}. * @see StatelessDialogFragment # showAllowingStateLoss (FragmentManager, String) * / public int showAllowingStateLoss (transakcja FragmentTransaction, tag String) { mDismissed = false; mShownByMe = true; transaction.add (this, tag); mViewDestroyed = false; mBackStackId = transaction.commitAllowingStateLoss (); return mBackStackId; } / ** * Wyświetl okno dialogowe, dodając fragment do podanego FragmentManagera. To wygoda * za jawne utworzenie transakcji, dodanie do niej fragmentu z podanym tagiem, oraz * popełnienie tego bez dbania o stan. Tak nie jest dodania transakcji do pliku * tylny stos. Kiedy fragment zostanie odrzucony, zostanie wykonana nowa transakcja, aby go usunąć * z zajęć.
* * Zalecam używanie {@link #show (FragmentManager, String)} przez większość czasu, ale tak jest * w przypadku dialogów, na których naprawdę nie zależy. (Debugowanie / śledzenie / reklamy itp.) * * * Menedżer @param * FragmentManager, do którego zostanie dodany ten fragment. * Tag @param * Znacznik tego fragmentu, zgodnie z * {@link FragmentTransaction # add (Fragment, String) FragmentTransaction.add}. * @see StatelessDialogFragment # showAllowingStateLoss (FragmentTransaction, String) * / public void showAllowingStateLoss (menedżer FragmentManager, tag String) { mDismissed = false; mShownByMe = true; FragmentTransaction ft = manager.beginTransaction (); ft.add (this, tag); ft.commitAllowingStateLoss (); } }
  1. Rozszerz StatelessDialogFragment zamiast DialogFragment
  2. Użyj metody showAllowingStateLoss zamiast show

  3. Cieszyć się ;)


Po co są te wszystkie pola boolowskie? Dlaczego nie są zadeklarowane jako członkowie klasy?
niezdefiniowany

1
Pola boolowskie są chronionymi członkami DialogFragment, ich nazwy oczywiście sugerują, do czego służą i musimy je zaktualizować, aby nie kolidować z logiką DialogFragment. Zauważ, że w oryginalnej klasie DialogFragment te funkcje istnieją, ale bez dostępu publicznego
Gil SH

Członkowie ci nie są chronieni, są wewnętrzni. StatelessDialogFragmentOtrzymałem błędy kompilacji podczas umieszczania w jednym z moich pakietów. Dzięki stary. Niedługo przetestuję to na produkcji.
nieokreślony

1

użyj tego kodu

FragmentTransaction ft = fm.beginTransaction();
        ft.add(yourFragment, "fragment_tag");
        ft.commitAllowingStateLoss();

zamiast

yourFragment.show(fm, "fragment_tag");

1

Znalazłem eleganckie rozwiązanie tego problemu za pomocą refleksji. Problem wszystkich powyższych rozwiązań polega na tym, że pola mDismissed i mShownByMe nie zmieniają swojego stanu.

Po prostu nadpisz metodę „show” we własnym niestandardowym fragmencie okna dialogowego dolnego arkusza, jak przykład poniżej (Kotlin)

override fun show(manager: FragmentManager, tag: String?) {
        val mDismissedField = DialogFragment::class.java.getDeclaredField("mDismissed")
        mDismissedField.isAccessible = true
        mDismissedField.setBoolean(this, false)

        val mShownByMeField = DialogFragment::class.java.getDeclaredField("mShownByMe")
        mShownByMeField.isAccessible = true
        mShownByMeField.setBoolean(this, true)

        manager.beginTransaction()
                .add(this, tag)
                .commitAllowingStateLoss()
    }

4
„Znalazłem eleganckie rozwiązanie tego problemu za pomocą refleksji”. jak to jest eleganckie?
Mark Buikema

elegancki, stylowy, szykowny, elegancki, miły, pełen wdzięku
Рома Богдан

1
to jedyne rozwiązanie, które u mnie zadziałało. Myślę, że jest elegancki
MBH

0

Do rozwiązania problemu bezpiecznej zmiany stanu w trakcie Activitycyklu życia, w szczególności w przypadku wyświetlania okien dialogowych, można zastosować następującą implementację : jeśli stan instancji został już zapisany (np. W wyniku zmiany konfiguracji), to odkłada je do czasu przywrócenia stanu zostało wykonane.

public abstract class XAppCompatActivity extends AppCompatActivity {

    private String TAG = this.getClass().getSimpleName();

    /** The retained fragment for this activity */
    private ActivityRetainFragment retainFragment;

    /** If true the instance state has been saved and we are going to die... */
    private boolean instanceStateSaved;

    @Override
    protected void onPostCreate(Bundle savedInstanceState) {
        super.onPostCreate(savedInstanceState);

        // get hold of retain Fragment we'll be using
        retainFragment = ActivityRetainFragment.get(this, "Fragment-" + this.getClass().getName());
    }

    @Override
    protected void onPostResume() {
        super.onPostResume();

        // reset instance saved state
        instanceStateSaved = false;

        // execute all the posted tasks
        for (ActivityTask task : retainFragment.tasks) task.exec(this);
        retainFragment.tasks.clear();
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        instanceStateSaved = true;
    }

    /**
     * Checks if the activity state has been already saved.
     * After that event we are no longer allowed to commit fragment transactions.
     * @return true if the instance state has been saved
     */
    public boolean isInstanceStateSaved() {
        return instanceStateSaved;
    }

    /**
     * Posts a task to be executed when the activity state has not yet been saved
     * @param task The task to be executed
     * @return true if the task executed immediately, false if it has been queued
     */
    public final boolean post(ActivityTask task)
    {
        // execute it immediately if we have not been saved
        if (!isInstanceStateSaved()) {
            task.exec(this);
            return true;
        }

        // save it for better times
        retainFragment.tasks.add(task);
        return false;
    }

    /** Fragment used to retain activity data among re-instantiations */
    public static class ActivityRetainFragment extends Fragment {

        /**
         * Returns the single instance of this fragment, creating it if necessary
         * @param activity The Activity performing the request
         * @param name The name to be given to the Fragment
         * @return The Fragment
         */
        public static ActivityRetainFragment get(XAppCompatActivity activity, String name) {

            // find the retained fragment on activity restarts
            FragmentManager fm = activity.getSupportFragmentManager();
            ActivityRetainFragment fragment = (ActivityRetainFragment) fm.findFragmentByTag(name);

            // create the fragment and data the first time
            if (fragment == null) {
                // add the fragment
                fragment = new ActivityRetainFragment();
                fm.beginTransaction().add(fragment, name).commit();
            }

            return fragment;
        }

        /** The queued tasks */
        private LinkedList<ActivityTask> tasks = new LinkedList<>();

        @Override
        public void onCreate(Bundle savedInstanceState)
        {
            super.onCreate(savedInstanceState);

            // retain this fragment
            setRetainInstance(true);
        }

    }

    /** A task which needs to be performed by the activity when it is "fully operational" */
    public interface ActivityTask {

        /**
         * Executed this task on the specified activity
         * @param activity The activity
         */
        void exec(XAppCompatActivity activity);
    }
}

Następnie używając takiej klasy:

/** AppCompatDialogFragment implementing additional compatibility checks */
public abstract class XAppCompatDialogFragment extends AppCompatDialogFragment {

    /**
     * Shows this dialog as soon as possible
     * @param activity The activity to which this dialog belongs to
     * @param tag The dialog fragment tag
     * @return true if the dialog has been shown immediately, false if the activity state has been saved
     *         and it is not possible to show it immediately
     */
    public boolean showRequest(XAppCompatActivity activity, final String tag) {
        return showRequest(activity, tag, null);
    }

    /**
     * Shows this dialog as soon as possible
     * @param activity The activity to which this dialog belongs to
     * @param tag The dialog fragment tag
     * @param args The dialog arguments
     * @return true if the dialog has been shown immediately, false if the activity state has been saved
     *         and it is not possible to show it immediately
     */
    public boolean showRequest(XAppCompatActivity activity, final String tag, final Bundle args)
    {
        return activity.post(new XAppCompatActivity.ActivityTask() {
            @Override
            public void exec(XAppCompatActivity activity) {
                if (args!= null) setArguments(args);
                show(activity.getSupportFragmentManager(), tag);
            }
        });
    }

    /**
     * Dismiss this dialog as soon as possible
     * @return true if the dialog has been dismissed immediately, false if the activity state has been saved
     *         and it is not possible to dismissed it immediately
     */
    public boolean dismissRequest()
    {
        return dismissRequest(null);
    }

    /**
     * Dismiss this dialog as soon as possible
     * @param runnable Actions to be performed before dialog dismissal
     * @return true if the dialog has been dismissed immediately, false if the activity state has been saved
     *         and it is not possible to dismissed it immediately
     */
    public boolean dismissRequest(final Runnable runnable)
    {
        // workaround as in rare cases the activity could be null
        XAppCompatActivity activity = (XAppCompatActivity)getActivity();
        if (activity == null) return false;

        // post the dialog dismissal
        return activity.post(new XAppCompatActivity.ActivityTask() {
            @Override
            public void exec(XAppCompatActivity activity) {
                if (runnable != null) runnable.run();
                dismiss();
            }
        });
    }
}

Możesz bezpiecznie wyświetlać okna dialogowe, nie martwiąc się o stan aplikacji:

public class TestDialog extends XAppCompatDialogFragment {

    private final static String TEST_DIALOG = "TEST_DIALOG";

    public static void show(XAppCompatActivity activity) {
        new TestDialog().showRequest(activity, TEST_DIALOG);
    }

    public TestDialog() {}

    @NonNull
    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState)
    {
        return new AlertDialog.Builder(getActivity(), R.style.DialogFragmentTheme /* or null as you prefer */)
                .setTitle(R.string.title)
                // set all the other parameters you need, e.g. Message, Icon, etc.
                ).create();
    }
}

a następnie zadzwoń TestDialog.show(this)ze swojego XAppCompatActivity.

Jeśli chcesz utworzyć bardziej ogólną klasę okna dialogowego z parametrami, możesz zapisać je w a Bundlez argumentami w show()metodzie i pobrać je za pomocą getArguments()in onCreateDialog().

Całe podejście może wydawać się nieco skomplikowane, ale po utworzeniu dwóch klas podstawowych dla działań i dialogów jest dość łatwe w użyciu i doskonale działa. Może być używany do innych Fragmentoperacji bazowych, na które może mieć wpływ ten sam problem.


0

Wydaje się, że ten błąd występuje, ponieważ zdarzenia wejściowe (takie jak naciśnięcie klawisza lub zdarzenia onclick) są dostarczane po onSaveInstanceStatewywołaniu.

Rozwiązaniem jest zastąpienie onSaveInstanceStateaktywności i anulowanie wszelkich oczekujących wydarzeń.

@Override
protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
        final View rootView = findViewById(android.R.id.content);
        if (rootView != null) {
            rootView.cancelPendingInputEvents();
        }
    }
}
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.