5

我试图找出原因:

getSupportFragmentManager().beginTransaction().commit();

失败,与

java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState

在一个非常基本的 FragmentActivity 类中。

所以这是我的用例(这将是一些伪代码,而不是一个完整的示例,抱歉):我有一个带有内部 AsyncTask 类的 FragmentActivity。大致是这样的:

public class HelloWorld extends FragmentActivity {
    showFragment(Fragment fragment, String name) {
        getSupportFragmentManager().beginTransaction().replace(R.id.fragmentContainer, fragment, name).commit();
    }

    private class SlowFragmentShow extends AsyncTask<Context, String, Void> {
        protected Void doInBackground(Context... contexts) {
            try {
                Thread.sleep(10000);
            } catch (InterruptedException e) {
                /* meh */
            }
        }

        protected void onPostExecute(Void nothing) {
            showFragment(new MyFragment(), "myFragment");
        }
    }
}

或者基本上,在启动应用程序 10 秒后,它会显示另一个片段。听起来很简单,对吧?这似乎也很有效,直到我决定旋转手机。当我这样做时,应用程序会在调用“getSupportFragmentManager()...”时崩溃,并显示“无法执行此操作...”。

4

2 回答 2

10

经过大量调试,结果发现当SlowFragmentShow.onPostExecute()被调用时,调用showFragment()了,又调用getSupportFragmentManager()了,我收到了一个FragmentManager在 an 中的IllegalState(可以说我得到的异常是正确的)。我仍然不确定为什么getSupportFragmentManager()会在这种不确定状态下返回一个对象,但确实如此,我需要以某种方式访问​​“正确的” FragmentManager。所以切入正题,我将 存储FragmentManager为静态变量,我在调用HelloWorld FragmentActivity时对其进行了更新HelloWorld.onStart()

public class HelloWorld extends FragmentActivity {
    private static FragmentManager fragmentManager;

    public void onStart() {
        fragmentManager = getSupportFragmentManager();
        /* more code here */
    }

    showFragment(Fragment fragment, String name) {
        fragmentManager.beginTransaction().replace(R.id.fragmentContainer, fragment, name).commit();
    }

    private class SlowFragmentShow extends AsyncTask<Context, String, Void> {
        protected Void doInBackground(Context... contexts) {
            try {
                Thread.sleep(10000);
            } catch (InterruptedException e) {
                /* meh */
            }
        }

        protected void onPostExecute(Void nothing) {
            showFragment(new MyFragment(), "myFragment");
        }
    }
}

好吧,这几乎解决了它。现在我可以随心所欲地旋转手机,当 AsyncTask 完成时,片段仍然会显示。

回想起来,它确实看起来有点“哦,当然!”,但 Android 背后的设计决策却让人觉得很“陌生”和不寻常。我似乎最终得到了try-catch(Exception)大约一半的代码,只是为了防止它因非致命错误而崩溃(例如未能更新文本字段),以及许多需要更新的静态变量,onStart()因为这似乎就像您可以引用 Android 对象而不将它们放在IllegalState.

于 2013-06-14T22:49:21.470 回答
0

@VidarWahlberg您的答案很好,但是假设您FragmentActivity在运行任务的地方打开了新FragmentActivity的,在异步结束或您的应用程序在后台运行之前打开(例如:您单击了主页)。我建议你创建BaseActivity你扩展的地方,你FragmentActivity的所有活动都应该扩展它。

什么是重要的?

正如@VidarWahlberg 在他的回答中提到的那样,您需要拥有 ,并在子活动中使用它来BaseActivity显示.static FragmentManager fm;DialogFragment

有什么区别?

fm = getSupportFragmentManager();而不是在方法中初始化,onStart()您需要在onResume(). 这是必需的,因为当您启动线程运行活动时,它正在创建它的新实例,并且在启动下一个活动时,它会创建将被初始化的新实例,BaseActivity并且FragmenManager当您回到线程启动活动fm对象时,将使用第二个活动(暂停)实例。应用程序将崩溃,因为您将尝试在已暂停的活动视图中显示对话框。您无法更改暂停活动的视图,因此您需要在onResume()调用时进行初始化。onResume()在活动开始时也会调用,因此在FragmentManager那里初始化是安全的!

我们还需要担心什么?

你可能认为一切都很好,但事实并非如此。您可能第一次没有意识到,但是如果您的应用程序不在前台(而是在后台),您的所有活动都将暂停,但线程已经启动并且正在运行。这是一个问题,因为线程将继续并尝试显示DialogFragmernt(或者假设更改未聚焦的活动视图)。所以解决方案是跟踪应用程序是否在前台。这可以完成,PackageManager但它需要清单文件中的新权限(用户不喜欢,他们可能不想安装您的应用程序)。解决方案是static boolean isOnBackground = false;在每个活动中onResume()使用isOnBackground = false;和 in更改字段。这不是很大的变化,因为onPause()isOnBackground = true;BaseActivity是我们可以做到的地方。这是安全的,因为每次新活动的开始都会创建BaseActivity类的新实例,但由于该字段static只创建​​一次并且对于所有实例都是相同的。我们需要isOnBackground在启动线程 post 方法的主体代码之前检查状态。

您可能会说由于 和 之间的延迟可能会出现问题onPause()onResume()但是您的应用程序需要以快速启动活动的方式进行设计,而不是在 ui 线程上做长时间的工作(需要在后台线程上完成)。因此,如果您的应用程序设计良好,您将不会遇到问题 :)

只要应用程序不在前台,您就可以使用线程运行方法的递归调用。这是安全的,不会导致循环,因为当应用程序的状态被重置(需要内存)或应用程序被用户停止或 android 本身循环将停止,因为所有应用程序的实例都将从内存中释放!

注意:使用 android 支持库!

代码如下:

BaseActivity.java

.......................
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentManager;
.......................

public class BaseActivity extends FragmentActivity {

    public static FragmentManager fm;
    public static boolean isOnBackground;

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

    @Override
    protected void onResume() {
        super.onResume();
        BaseActivity.isOnBackground = false;
        BaseActivity.fm = getSupportFragmentManager();
    }

    @Override
    protected void onPause() {
        super.onPause();
        BaseActivity.isOnBackground = true;
    }
}

MainActivity.java

.....................
import android.support.v4.app.DialogFragment;
.....................

public class MainActivity extends BaseActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //This is just to call some second activity
        Button startNewActivity = (Button) findViewById(R.id.helloWorld);

        startNewActivity.setOnClickListener( new OnClickListener() {

            @Override
            public void onClick(View v) {
                Intent intent = new Intent(MainActivity.this,
                            SecondActivity.class);
                startActivity(intent);
            }
        });

        showEditDialog();
    }

    private void showEditDialog() {
        (new Handler()).postDelayed(new Runnable() {
            @Override
            public void run() {
                if(!BaseActivity.isOnBackground) {
                    DialogFragment myFragmentDialog = new DialogFragment();
                    myFragmentDialog.show(BaseActivity.fm,
                            "fragment_my_dialog");
                } else {
                    //this is optional
                    showEditDialog();
                }
            } 

        }, 15000);
    }
}
于 2013-10-09T14:48:59.173 回答