commitAllowingStateLoss() 和 commit() 片段

commitAllowingStateLoss() and commit() fragment

我想在网络后台运行后提交一个片段。我在网络操作成功后调用了 commit(),但万一 activity 进入暂停或停止状态,应用程序崩溃并显示 IllegalState 异常。

所以我尝试使用 commitAllowingStateLoss() 并且它现在工作正常。

我浏览了一些博客和文章说 commitAllowingStateLoss() 不好用。

网络操作处理后如何处理提交片段activity暂停和停止状态?

只需检查activity是否完成,然后commit()交易。

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

你得到的异常是在一个场景中,当 activity 正在完成时,你正在尝试提交一个新事务,这显然不会被 FragmentManager 保存,因为 onSavedInstanceState() 已经执行。这就是框架强制您实现它的原因 "correctly".

提交 安排此事务的提交。提交不会立即发生;它将被安排为主线程上的工作,以便在下次该线程准备就绪时完成。

事务只能在包含 activity 保存其状态之前使用此方法提交。如果在该点之后尝试提交,将抛出异常。这是因为如果 activity 需要从其状态恢复,提交后的状态可能会丢失。

commitAllowingStateLoss

类似于 commit() 但允许在保存 activity 的状态后执行提交。这很危险,因为如果 activity 需要稍后从其状态恢复,提交可能会丢失,因此这应该只用于 UI 状态可以意外更改的情况用户。

并且根据您的问题,您正在使用 AsyncTask 进行网络后台操作。所以这可能是您在其回调方法中提交片段的问题。要避免此异常,请不要在 asynctask 回调方法中提交。这不是解决方案,而是预防措施。

我想向 Aritra Roy 添加信息(到目前为止我已经阅读,这是一个非常好的答案)。

我之前遇到过这个问题,我发现主要问题是你试图在另一个线程中进行一些异步操作(HTTP,计算,...),这是一个很好的实践,但你必须在收到答案后通知您的用户。

主要问题是因为它是异步操作,所以无法保证用户仍然在您的 activity/app 上。如果他走了,就没有必要做 UI 的改变。此外,由于 android 可能会因内存问题而杀死你的 app/activity,因此你无法保证能够得到你的答案,并保存它以供恢复。 问题不仅是 "the user can open another app",而且是 "my activity can be recreated from configuration change",您可能会在 activity 娱乐期间尝试进行 UI 更改,这真的非常糟糕。

使用 "commitAllowingStateLoss" 就像说 "i don't care if the UI is not really in the good state"。你可以做一些小事(比如激活一个 gif 表示你的下载已结束)......这不是一个大问题,这个问题真的不值得处理,因为 "in general" 用户将留在你的应用程序上。

但是,用户做了一些事情,你试图在网络上获取信息,信息已经准备好了,你必须在用户恢复应用程序时显示它......主要的词是“简历”。

您必须将您需要的数据收集到一个变量中(如果可以的话,一个可打包或原始变量),然后覆盖您的 "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...*/
    }
}

补充信息: This topic 尤其是@jed 的回答,以及@pjv、@Sufian 对它的评论。 This blog 为了理解错误发生的原因,以及为什么 proposed/accepted 答案有效。

最后一句话: 以防万一你想知道 "why using a service is better than asyncTask"。据我了解,这并不是真的更好。主要区别在于,当您的 activity 为 paused/resumed 时,正确使用服务允许您 register/unregister 处理程序。因此,当您的 activity 处于活动状态时,您总是会得到答案,从而防止错误发生。

请注意,这并不是因为错误没有发生就说明您是安全的。如果您直接在视图上进行更改,则不涉及 fragmentTransactions,因此,无法保证在重新创建、恢复、重新启动或其他任何应用程序时都会保留和重新创建更改。

简单的方法是等待 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 以防使用后退键返回..

好吧,我遇到了同样的问题os,并找到了一个非常简单的解决方法。由于 android os 当时没有任何解决方案(虽然 commitAllowingStateLoss() 是他们的解决方案之一,但仍然没有一个好的解决方案,其余的你知道)。

解决方案是编写一个处理程序 class,当 activity 通过时缓冲消息并再次在 onResume 上播放。

通过使用此 class,确保所有 asynchronously 更改 fragment 状态(提交等)的代码都从该处理程序中的消息中调用。

从句柄class扩展FragmenntPauseHandler

每当您的 activity 接到 onPause() 电话 FragmenntPauseHandler.pause()onResume() 电话时 FragmenntPauseHandler.resume()

将您的处理程序 handleMessage() 的实现替换为 processMessage()

提供 storeMessage() 的简单实现,它始终 returns 为真。

/**
 * 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 class 的简单示例。

单击 Button 后,一条延迟消息将发送到 handler

handler 收到消息时(在 UI 线程上)它显示 DialogFragment.

如果 FragmenntPauseHandler class 未被使用,则在按下测试按钮启动对话框后按下主页按钮时将显示 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;
                }
            }
        }
    }
}

我在 FragmenntPauseHandler class 中添加了一个 storeMessage() 方法,以防即使 activity 暂停时也应立即处理任何消息。如果处理了一条消息,则应返回 false 并且该消息将 discarded.Hope 它有帮助。我使用了四分之一的应用程序,再也没有遇到同样的问题。