commitAllowingStateLoss()和commit()片段

19

我希望在网络后台操作完成后提交一个fragment。 我在成功的网络操作后调用commit(),但如果活动进入暂停或停止状态,它将导致应用程序崩溃并显示IllegalStateException。

因此,我尝试使用commitAllowingStateLoss()现在可以正常工作。

我阅读了一些博客和文章,发现commitAllowingStateLoss()不适合使用。

如何处理在网络操作处理活动暂停和停止状态后提交碎片的方式?


1
请阅读此博客文章以获取有关您的问题的更多信息。 - android_griezmann
1
https://dev59.com/i2Qn5IYBdhLWcg3wiXqd - IntelliJ Amiya
如果您想在异步回调中加载片段,例如在工作线程中下载数据并在主线程中获取响应,则应使用commitAllowingStateLoss或commitNowAllowingStateLoss。 - Keyur Thumar
5个回答

5
我想要为Aritra Roy添加信息(据我所读,这是一个非常好的答案)。
我之前也遇到过这个问题,我发现主要问题在于您正在另一个线程中进行一些异步操作(HTTP,计算等),这是一个好的做法,但必须在收到回答后通知用户。
主要问题是由于这些是异步操作,因此不能保证用户仍然在您的活动/应用程序上。如果用户离开了,那么没有必要进行UI更改。此外,由于Android可能会因内存问题杀死您的应用程序/活动,您无法保证能够获取并保存答案以便恢复。问题不仅是“用户可以打开另一个应用程序”,而且“我的活动可能会从配置更改中重新创建”,并且您可能会尝试在活动重新创建期间进行UI更改,这将非常糟糕。
使用“commitAllowingStateLoss”就像说“我不关心UI是否处于良好状态”。您可以为小事情(例如激活gif以表示下载已结束)这样做......这不是一个大问题,而且这个问题不值得花费时间去解决,因为“通常情况下”,用户会留在您的应用程序上。
但是,如果用户执行了某些操作,您正在尝试从Web上获取信息,信息已准备好,您必须在用户恢复应用程序时显示它……那么关键词是“恢复”。
您必须将所需数据收集到变量中(如果可能,是可包裹或基本变量),然后按以下方式覆盖您的“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的回答以及@pjv、@Sufian对此的评论。 这篇博客可以帮助您了解为什么会出现此错误以及为什么提出/接受的答案能够解决问题。

最后: 如果您想知道“为什么使用服务比asyncTask更好”,我所理解的并不是真正的更好。主要区别在于,适当地使用服务允许您在activity暂停/恢复时注册/注销处理程序。因此,在您的活动处于活动状态时,您始终可以获得您的答案,从而防止出现错误。

请注意,这并不意味着没有错误发生,如果您直接更改视图,则不涉及片段事务,因此不能保证更改将在应用程序重新创建、恢复、重新启动或其他任何情况下得到保留和重建。


1

简单的方法是等待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也已恢复),则您可以提交您的操作,但如果没有恢复,则需要等待Activity启动后才能提交您的操作:

@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();
    }
}

注意:moveToNext = false;它的存在是为了确保在使用后退按钮返回时,commit不会重复执行。


1
我遇到了同样的问题,并找到了一个非常简单的解决方法。由于Android操作系统当时没有任何解决方案(尽管现在还没有一个好的解决方案,commitAllowingStateLoss()是他们的其中一个解决方案,你知道其余的)。解决方法是编写一个处理程序类,在活动传递时缓冲消息并在onResume上播放它们。通过使用这个类,请确保所有异步更改片段状态(提交等)的代码都是从此处理程序中的消息调用的。从处理程序类继承FragmenntPauseHandler。每当您的活动接收到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类的简单示例。
点击一个按钮后,会向处理程序发送延迟消息。
当处理程序(在UI线程上)接收到消息时,它会显示一个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;
                }
            }
        }
    }
}

我已经在FragmentPauseHandler类中添加了一个storeMessage()方法,以便在活动暂停时立即处理任何消息。如果处理了消息,则应返回false并且该消息将被丢弃。希望能对您有所帮助。我在4个应用程序中使用了这个方法,再也没有遇到同样的问题了。

0

提交 安排提交此事务。提交不会立即发生;它将被安排为主线程上的工作,在下一次该线程准备好进行时执行。

只能在包含活动保存其状态之前使用此方法提交事务。如果在此之后尝试提交,则会抛出异常。这是因为提交后的状态可以在需要从其状态还原活动时丢失。

commitAllowingStateLoss

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

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


0

只需检查活动是否正在结束,然后commit()事务。

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

你得到的异常是在一个场景中,当活动正在结束,并且你尝试提交一个新的事务,显然,由于FragmentManager已经执行了onSavedInstanceState(),所以它不会被保存。这就是为什么框架强制要求你"正确"实现它。


这样做行不通,对于commit()方法来说不行... 这个方法是异步的,所以它会稍后执行,在那个时候Activity可能正在结束(在onSaveInstanceState之后的状态)- 我从生产应用程序中的崩溃日志可以证实这一点。检查isFinishing()就足够了,并且将与commitNow()一起使用,这个方法是同步的。 - snachmsm
无法理解您所概述的情况可能发生的原因,因为当您执行 commit() 时,会在 MessageQueue 上发布一条 Message,该消息将在 Looper 的一个即将到来的循环中处理。现在,当活动即将完成时,也会有一条 Message,它将被发布到 MessageQueue 上,因此可以保证在稍早发布的 commit() 消息之后处理。这是我对此案例的理解。 - azizbekian

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接