commitAllowingStateLoss()和commit()片段

时间:2017-03-09 06:24:33

标签: android android-fragments android-support-library

我想在网络后台操作后提交一个片段。我在成功进行网络操作后调用了commit(),但是如果活动进入暂停或停止状态,则会崩溃应用程序,说明IllegalState异常。

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

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

在网络操作处理活动暂停和停止状态后,处理提交片段的方法是什么?

6 个答案:

答案 0 :(得分:21)

我会尽力向你解释一切。

支持库中的

FragmentTransaction提供了四种提交事务的方法,

1)commit()

2)commitAllowingStateLoss()

3)commitNow()

4)commitNowAllowingStateLoss()

你可能会得到一个IllegalStateException说你在onSaveInstanceState()被调用后无法提交。查看this帖子,其中首先描述了抛出此异常的原因。

commit()commitAllowingStateLoss() 在实施中几乎完全相同有一个区别,commit()检查状态是否已经保存,如果已经保存,那么它抛出一个IllegalStateException。您可以自己查看源代码。

那么,每次拨打commitAllowingStateLoss()时,你都会失去状态。 不,当然不是。如果应用程序被杀,你可能会丢失FragmentManager或onSaveInstanceState()之后添加或删除的任何其他片段的状态。

以下是您使用案例的实际情况 -

  • 您的活动正在显示FragmentA
  • 您的活动转到后台并onSaveInstanceState()获取 称为
  • 您的网络操作已完成,您将FragmentA替换为 FragmentB使用commitAllowingStateLoss()

这些是现在可能发生的两件可能的事情 -

  • 如果系统由于内存不足而导致您的应用被杀,那么您的应用就可以了 将使用在步骤2中创建的已保存状态重新创建。 FragmentB将不可见。 这是你失去状态的时候

  • 如果系统没有杀死你的应用并且该应用仍在内存中, 那么它将被带回前景并且FragmentB会 仍然显示。 在这种情况下,您没有失去状态。

您可以查看this Github项目并亲自尝试此方案。

如果您已打开“不要保持活动”开发人员选项,您将体验到状态真正丢失的第一个场景。

如果你有这个设置,你会遇到没有状态丢失的第二种情况。

答案 1 :(得分:2)

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

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

主要问题是因为它是异步操作,所以无法保证用户仍然在您的活动/应用程序上。如果他离开,则无需进行UI更改。此外,由于Android可能会因内存问题而终止您的应用/活动,因此无法保证能够获得您的答案,并将其保存以便进行恢复。 问题不仅仅是用户可以打开另一个应用程序"但是"我的活动可以从配置更改中重新创建"并且你可能正在尝试在活动娱乐期间进行UI更改,这将非常非常糟糕。

使用" commitAllowingStateLoss"就像说"我不在乎UI是否真的处于良好状态"。你可以做一些小事情(比如激活一个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...*/
    }
}

其他信息: This topic,尤其是@jed回答,以及@pjv,@ Sufian对它的评论。 This blog以了解错误发生的原因,以及为什么建议/接受的答案有效。

最后一句话: 万一你想知道"为什么使用服务比asyncTask"更好。根据我的理解,这并不是更好。主要区别在于,正确使用服务允许您在暂停/恢复活动时注册/取消注册处理程序。因此,您可以在活动处于活动状态时获得答案,从而防止错误发生。

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

答案 2 :(得分:1)

我遇到了同样的问题,发现了一个非常简单的解决方法。因为android os没有那个时间的任何解决方案(虽然commitAllowingStateLoss()仍然没有一个好的解决方案,但是你知道其余的解决方案)。

解决方案是编写一个Handler类,在活动通过时缓冲消息并再次在onResume上播放它们。

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

从句柄类扩展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类的简单示例。

点击Button时,系统会向handler发送延迟消息。

handler收到消息时(在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;
                }
            }
        }
    }
}

我已向storeMessage()类添加了FragmenntPauseHandler方法,以防即使活动暂停也应立即处理任何消息。如果处理了一条消息,那么应该返回false,并且该消息将被丢弃。希望它有所帮助。我在4个应用程序中使用了这个,并且从未遇到过同样的问题。

答案 3 :(得分:0)

只需检查活动是否结束,然后检查commit()交易。

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

您获得的异常是在一个场景中,当活动结束时,您正在尝试提交一个新的交易,这显然不会被FragmentManager保存,因为onSavedInstanceState()已被执行。这就是为什么框架会强迫您正确实施#34;。

答案 4 :(得分:0)

<强>提交 安排提交此交易。提交不会立即发生;它将被安排为主线程上的工作,以便在下一次线程就绪时完成。

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

<强> commitAllowingStateLoss

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

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

答案 5 :(得分:0)

简单的方法就是等待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以便在使用背压回来的情况下重复typedef unsigned char poly8; typedef unsigned long long poly8x64[8]; char* intToBits(unsigned k) { int i; char *nk = malloc(8); for(i=7;i>=0;i--){ nk[i] = (k%2); k = (int)(k/2); printf("nk= %d \n", nk[i]); } return *nk; } void poly8_bitslice(poly8x64 r, const poly8 x[64]) { //TODO int i; for(i=0;i<64;i++) { printf("x= %d \n", x[i]); char* mem = intToBits(x[i]); printf("bufer= %d \n", *mem);; } } int main() { poly8 a[64], b[64], r[64]; poly8x64 va, vb, vt; int i; FILE *urandom = fopen("/dev/urandom","r"); for(i=0;i<64;i++) { a[i] = fgetc(urandom); b[i] = fgetc(urandom); } poly8_bitslice(va, a); poly8_bitslice(vb, b); fclose(urandom); return 0; }