CommitAllowingStateLoss() и commit()

Я хочу зафиксировать фрагмент после работы в фоновом режиме. Я был вызван commit() после успешной работы в сети, но в случае, если активность переходит в состояние паузы или остановки, это вызывало сбой приложение, в котором упоминалось исключение IllegalState.

SO Я попытался использовать commitAllowingStateLoss() и теперь он отлично работает.

Я просмотрел несколько блогов и статей, в которых говорится, что commitAllowingStateLoss() не подходит для использования.

Каким образом обрабатывать фрагмент фиксации после операции работы с сетью приостанавливает и останавливает работу?

Ответы

Ответ 1

Я хотел бы добавить информацию в Aritra Roy (пока я читал, это действительно хороший ответ).

Я столкнулся с проблемой раньше, и я обнаружил, что основная проблема заключается в том, что вы пытаетесь сделать некоторые операции async (HTTP, вычисления,...) в другом потоке, который является хорошей оценкой, но вы должны сообщить об этом пользователь ПОСЛЕ получения ответов.

Основная проблема заключается в том, что, поскольку это асинхронные операции, нет никакой гарантии, что пользователь все еще находится в вашей активности/приложении. И если он ушел, нет необходимости делать изменения пользовательского интерфейса. Более того, поскольку андроид может убить ваше приложение/деятельность по проблемам памяти, у вас нет гарантий, чтобы иметь возможность получить ответ и сохранить его для восстановления. Проблема заключается не только в том, что пользователь может открыть другое приложение, но "моя деятельность может быть воссоздана из конфигурации", и вы, возможно, пытаетесь сделать изменения пользовательского интерфейса во время активного отдыха, что было бы действительно, очень плохо.

Использование "commitAllowingStateLoss" похоже на высказывание "Мне все равно, если пользовательский интерфейс не находится в хорошем состоянии". Вы можете сделать это для мелочей (например, активировать gif, говорящий, что ваша загрузка закончилась)... Это не большая проблема, и эта проблема не стоит иметь в виду, так как "в общем" пользователь останется в вашем приложении.

Но пользователь что-то сделал, вы пытаетесь получить информацию в Интернете, информация готова, и вы должны показать ее, когда пользователь возобновит приложение... основное слово - "возобновить".

Вы должны собрать необходимые данные в переменную (и, если возможно, какую-либо детальную или примитивную переменную), а затем переопределить функции onResume или onPostResume (для действий) следующим образом.

public void onResume/onPostResume() {
    super.onResume/onPostResume();
    if(someTreatmentIsPending) {
        /*do what you need to do with your variable here : fragment 
        transactions, dialog showing...*/
    }
}

Дополнительная информация: Этот раздел и особенно @jed answer, и @pjv, @Sufian комментирует это. Этот блог, чтобы понять, почему возникает ошибка, и почему предлагаемые/принятые ответы работают.

Последнее слово: На всякий случай вы задаетесь вопросом: "Почему использование сервиса лучше, чем asyncTask". Для того, что я понял, это не совсем лучше. Основное различие заключается в том, что при правильном использовании службы вы можете регистрировать/отменять регистрацию обработчиков, когда ваша деятельность приостановлена ​​/возобновлена. Поэтому вы всегда получаете свои ответы, когда ваша активность активна, предотвращая появление ошибки.

Обратите внимание, что не потому, что ошибка не в том, что вы в безопасности. Если вы внесли изменения непосредственно в свои представления, нет связанных с фрагментами транзакций, поэтому не гарантируйте, что изменения будут сохранены и воссозданы, когда приложение будет воссоздано, возобновлено, перезапущено или что-либо еще.

Ответ 2

Я попытаюсь подробно объяснить вам все.

FragmentTransaction в библиотеке поддержки предоставляет четыре способа совершения транзакции,

1) commit()

2) commitAllowingStateLoss()

3) commitNow()

4) commitNowAllowingStateLoss()

Вероятно, вы получаете сообщение IllegalStateException, которое вы не можете зафиксировать после вызова onSaveInstanceState(). Отметьте этот пост, в котором описывается, почему это исключение выбрасывается в первую очередь.

commit() и commitAllowingStateLoss() с одним отличием почти идентичны в своей реализации, commit() проверяет, было ли состояние уже сохранено, и если да, то он выдает IllegalStateException, Вы можете проверить исходный код самостоятельно.

Итак, вы теряете состояние каждый раз, когда вызываете commitAllowingStateLoss(). Нет, конечно, не. Вы MAY теряете состояние FragmentManager или любой другой фрагмент, добавленный или удаленный после onSaveInstanceState(), если приложение убито.

Ниже приведен пример практического сценария для использования case -

  • В вашей работе отображается FragmentA
  • Ваша деятельность переходит на задний план и onSaveInstanceState() получает называется
  • Ваша сетевая операция завершается, и вы заменяете FragmentA на FragmentB с использованием commitAllowingStateLoss()

Это две возможные вещи, которые могут произойти сейчас -

  • Если система убила ваше приложение из-за нехватки памяти, тогда ваше приложение снова будет воссоздан с сохраненным состоянием, выполненным на шаге 2. FragmentB не будет виден. Это когда вы теряете состояние.

  • Если система не убила ваше приложение, и приложение все еще находится в памяти, то он будет возвращен на передний план, а FragmentB будет все еще отображается. В этом случае вы не потеряли состояние.

Вы можете проверить этот проект Github и попробовать этот сценарий самостоятельно.

Если вы включили опцию разработчика "Dont Keep Activities", вы испытаете первый сценарий, в котором состояние действительно потеряно.

Если у вас есть это значение, вы получите второй сценарий, в котором не будет потеряно состояние.

Ответ 3

Ну, я столкнулся с той же проблемой и нашел очень простой способ обхода. Поскольку у android os нет решения в это время (все еще нет хорошего, хотя commitAllowingStateLoss() является одним из их решений, и вы знаете все остальное).

Решение заключалось в том, чтобы написать класс Handler, который буферизует сообщения, когда активность проходит, и снова воспроизводит их на onResume.

Используя этот класс, убедитесь, что из этого обработчика вызывается весь код, из которого asynchronously изменение состояния fragment (commit и т.д.) вызывается.

Расширить FragmenntPauseHandler из класса handle.

Всякий раз, когда ваша активность получает вызов onPause() FragmenntPauseHandler.pause() и для onResume() вызов FragmenntPauseHandler.resume().

Замените реализацию обработчика handleMessage() на processMessage().

Обеспечьте простую реализацию storeMessage(), которая всегда возвращает true.

/**
 * Message Handler class that supports buffering up of messages when the
 * activity is paused i.e. in the background.
 */
public abstract class FragmenntPauseHandler extends Handler {

    /**
     * Message Queue Buffer
     */
    final Vector<Message> messageQueueBuffer = new Vector<Message>();

    /**
     * Flag indicating the pause state
     */
    private boolean paused;

    /**
     * Resume the handler
     */
    final public void resume() {
        paused = false;

        while (messageQueueBuffer.size() > 0) {
            final Message msg = messageQueueBuffer.elementAt(0);
            messageQueueBuffer.removeElementAt(0);
            sendMessage(msg);
        }
    }

    /**
     * Pause the handler
     */
    final public void pause() {
        paused = true;
    }

    /**
     * Notification that the message is about to be stored as the activity is
     * paused. If not handled the message will be saved and replayed when the
     * activity resumes.
     * 
     * @param message
     *            the message which optional can be handled
     * @return true if the message is to be stored
     */
    protected abstract boolean storeMessage(Message message);

    /**
     * Notification message to be processed. This will either be directly from
     * handleMessage or played back from a saved message when the activity was
     * paused.
     * 
     * @param message
     *            the message to be handled
     */
    protected abstract void processMessage(Message message);

    /** {@inheritDoc} */
    @Override
    final public void handleMessage(Message msg) {
        if (paused) {
            if (storeMessage(msg)) {
                Message msgCopy = new Message();
                msgCopy.copyFrom(msg);
                messageQueueBuffer.add(msgCopy);
            }
        } else {
            processMessage(msg);
        }
    }
}

Ниже приведен простой пример того, как можно использовать класс PausedHandler.

При щелчке Button сообщение с задержкой отправляется в handler.

Когда handler получает сообщение (в потоке пользовательского интерфейса), он отображает DialogFragment.

Если класс FragmenntPauseHandler не использовался, IllegalStateException будет отображаться, если кнопка дома была нажата после нажатия кнопки тестирования, чтобы запустить диалог.

public class FragmentTestActivity extends Activity {

    /**
     * Used for "what" parameter to handler messages
     */
    final static int MSG_WHAT = ('F' << 16) + ('T' << 8) + 'A';
    final static int MSG_SHOW_DIALOG = 1;

    int value = 1;

    final static class State extends Fragment {

        static final String TAG = "State";
        /**
         * Handler for this activity
         */
        public ConcreteTestHandler handler = new ConcreteTestHandler();

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

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

            handler.setActivity(getActivity());
            handler.resume();
        }

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

            handler.pause();
        }

        public void onDestroy() {
            super.onDestroy();
            handler.setActivity(null);
        }
    }

    /**
     * 2 second delay
     */
    final static int DELAY = 2000;

    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        if (savedInstanceState == null) {
            final Fragment state = new State();
            final FragmentManager fm = getFragmentManager();
            final FragmentTransaction ft = fm.beginTransaction();
            ft.add(state, State.TAG);
            ft.commit();
        }

        final Button button = (Button) findViewById(R.id.popup);

        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                final FragmentManager fm = getFragmentManager();
                State fragment = (State) fm.findFragmentByTag(State.TAG);
                if (fragment != null) {
                    // Send a message with a delay onto the message looper
                    fragment.handler.sendMessageDelayed(
                            fragment.handler.obtainMessage(MSG_WHAT, MSG_SHOW_DIALOG, value++),
                            DELAY);
                }
            }
        });
    }

    public void onSaveInstanceState(Bundle bundle) {
        super.onSaveInstanceState(bundle);
    }

    /**
     * Simple test dialog fragment
     */
    public static class TestDialog extends DialogFragment {

        int value;

        /**
         * Fragment Tag
         */
        final static String TAG = "TestDialog";

        public TestDialog() {
        }

        public TestDialog(int value) {
            this.value = value;
        }

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

        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container,
                Bundle savedInstanceState) {
            final View inflatedView = inflater.inflate(R.layout.dialog, container, false);
            TextView text = (TextView) inflatedView.findViewById(R.id.count);
            text.setText(getString(R.string.count, value));
            return inflatedView;
        }
    }

    /**
     * Message Handler class that supports buffering up of messages when the
     * activity is paused i.e. in the background.
     */
    static class ConcreteTestHandler extends FragmenntPauseHandler {

        /**
         * Activity instance
         */
        protected Activity activity;

        /**
         * Set the activity associated with the handler
         * 
         * @param activity
         *            the activity to set
         */
        final void setActivity(Activity activity) {
            this.activity = activity;
        }

        @Override
        final protected boolean storeMessage(Message message) {
            // All messages are stored by default
            return true;
        };

        @Override
        final protected void processMessage(Message msg) {

            final Activity activity = this.activity;
            if (activity != null) {
                switch (msg.what) {

                case MSG_WHAT:
                    switch (msg.arg1) {
                    case MSG_SHOW_DIALOG:
                        final FragmentManager fm = activity.getFragmentManager();
                        final TestDialog dialog = new TestDialog(msg.arg2);

                        // We are on the UI thread so display the dialog
                        // fragment
                        dialog.show(fm, TestDialog.TAG);
                        break;
                    }
                    break;
                }
            }
        }
    }
}

Я добавил метод storeMessage() в класс FragmenntPauseHandler, если любые сообщения должны быть обработаны немедленно, даже когда действие приостановлено. Если сообщение обрабатывается, то false должно быть возвращено, и сообщение будет отброшено. Надеюсь, это поможет. Я использовал это в 4 приложениях и никогда не имел такой же проблемы снова.

Ответ 4

Просто проверьте, завершена ли работа или нет, а затем commit() транзакция.

if (!isFinishing()) {
    // commit transaction
}

Исключением, которое вы получаете, является сценарий, когда действие завершается, и вы пытаетесь совершить новую транзакцию, которая, очевидно, не будет сохранена FragmentManager, потому что onSavedInstanceState() уже выполнен. Вот почему структура заставляет вас реализовать его "правильно".

Ответ 5

фиксации Планирует фиксацию транзакции. Конец не происходит немедленно; он будет запланирован, поскольку работа над основным потоком будет выполнена в следующий раз, когда поток будет готов.

Транзакция может быть совершена только с помощью этого метода до его сохранения, сохраняя его состояние. Если попытка совершения совершится после этой точки, будет выбрано исключение. Это связано с тем, что состояние после фиксации может быть потеряно, если действие необходимо восстановить из его состояния.

commitAllowingStateLoss

Подобно commit(), но позволяет выполнить фиксацию после сохранения состояния активности. Это опасно, потому что фиксация может быть потеряна, если действие необходимо восстановить позже из состояния, поэтому это должно использоваться только в тех случаях, когда пользовательское пользовательское состояние нормально изменяется на пользователя.

И В соответствии с вашим вопросом вы используете AsyncTask для работы в фоновом режиме. Таким образом, это может быть проблемой, заключающейся в том, что вы выполняете фрагмент внутри метода обратного вызова. Чтобы избежать этого исключения, просто не выполняйте внутри метода асинхронных обратных вызовов. Это не решение, а предосторожность.

Ответ 6

Простой способ ждать Activity для возобновления, так что вы можете commit выполнить свое действие, простое обходное решение будет выглядеть примерно так:

@Override
public void onNetworkResponse(){
       //Move to next fragmnt if Activity is Started or Resumed
       shouldMove = true;
       if (isResumed()){
            moveToNext = false;

            //Move to Next Page
            getActivity().getSupportFragmentManager()
                    .beginTransaction()
                    .replace(R.id.fragment_container, new NextFragment())
                    .addToBackStack(null)
                    .commit();
       }
}

Итак, если Fragment возобновлено (Следовательно, Activity) вы можете commit выполнить свое действие, но если вы не подождете Activity, чтобы начать с commit ваше действие:

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

    if(moveToNext){
        moveToNext = false;

        //Move to Next Page
        getActivity().getSupportFragmentManager()
                .beginTransaction()
                .replace(R.id.fragment_container, new NextFragment())
                .addToBackStack(null)
                .commit();
    }
}

P.S: Обратите внимание на moveToNext = false; Он должен гарантировать, что после commit вы не будете повторять commit в случае возврата с помощью обратного нажатия.