【问题标题】:Fragments, DialogFragment, and Screen Rotation片段、DialogFragment 和屏幕旋转
【发布时间】:2012-01-04 07:48:36
【问题描述】:

我有一个使用这个 XML 调用 setContentView 的 Activity:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="horizontal"
    >
    <fragment android:name="org.vt.indiatab.GroupFragment"
        android:id="@+id/home_groups"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:layout_weight="1" />
            <..some other fragments ...>
</LinearLayout>

GroupFragment 扩展了 Fragment,一切正常。但是,我从 GroupFragment 中显示了一个 DialogFragment。这显示正确,但是当屏幕旋转时,我得到一个强制关闭。

在 DialogFragment.show(FragmentManager, String) 之外的另一个 Fragment 中显示 DialogFragment 的正确方法是什么?

【问题讨论】:

  • 你能提供强制关闭的堆栈跟踪吗?
  • 您可以发布您实例化 DialogFragment 的代码行吗?

标签: android android-fragments


【解决方案1】:

在onCreate() 中调用setRetainInstance(true),然后包含以下内容:

@Override
public void onDestroyView() {
    if (getDialog() != null && getRetainInstance()) {
        getDialog().setOnDismissMessage(null);
    }
    super.onDestroyView();
}

当您在 onCreate() 中调用 setRetainInstance(true) 时,onCreate() 将不再在方向更改时被调用,但仍会调用 onCreateView()。

所以您仍然可以将状态保存到 onSaveInstanceState() 中的包中,然后在 onCreateView() 中检索它:

@Override
public void onSaveInstanceState(Bundle outState) {

    super.onSaveInstanceState(outState);

    outState.putInt("myInt", myInt);
}

@Override
public View onCreateView(LayoutInflater inflater, 
                         ViewGroup container, Bundle savedInstanceState) {

    View view = inflater.inflate(R.layout.my_layout, container);

    if (savedInstanceState != null) {

        myInt = savedInstanceState.getInt("myInt");
    }

    ...

    return view;
}

【讨论】:

    【解决方案2】:

    我通过@ZsomborErdődy-Nagy 和@AndyDennie 的回答解决了这个问题。你必须继承这个类,并在你的父片段中调用setRetainInstance(true)和dialogFragment.show(getFragmentManager(), "Dialog");

     public class AbstractDialogFragment extends DialogFragment {
    
            @Override
            public void onCreate(Bundle savedInstanceState) {
                super.onCreate(savedInstanceState);
                setRetainInstance(true);
            }
    
            @Override
            public void onDestroyView() {
                if (getDialog() != null && getRetainInstance())
                    getDialog().setDismissMessage(null);
                super.onDestroyView();
            }
        }
    

    【讨论】:

    • 我观察到如果应用显示另一个对话框片段,如 googleplayservices,应用在方向更改时也会崩溃。
    【解决方案3】:

    我遇到了这个问题,onDestroyView() 技巧不起作用。原来是因为我在onCreate() 中创建了一些相当密集的对话框。这包括保存对AlertDialog 的引用,然后我将在onCreateDialog() 中返回它。

    当我将所有这些代码移至onCreateDialog() 并停止保留对对话框的引用时,它又开始工作了。我希望我违反了DialogFragment 关于管理其对话的不变量之一。

    【讨论】:

      【解决方案4】:

      我在我的项目中遇到了这个问题,但上述解决方案都没有帮助。

      如果异常看起来像

      
      java.lang.RuntimeException: Unable to start activity ComponentInfo{ 
      
      ...
      
              Caused by: java.lang.IllegalStateException: Fragment.... 
              did not create a view.
      
      

      这是由轮换后使用的备用容器 ID 的问题引起的。请参阅此票以获取更多详细信息:

      https://code.google.com/p/android/issues/detail?id=18529

      基本上,您可以通过确保所有 xml 片段在布局中定义一个标签来防止崩溃。如果您在片段可见时旋转,这可以防止发生回退情况。

      在我的情况下,我能够应用此修复而无需不必重写 onDestroyView() 或 setRetainInstance(true),这是针对这种情况的常见建议。

      【讨论】:

        【解决方案5】:

        我混合使用了所提供的解决方案并添加了一个东西。 这是我的最终解决方案:

        我在 onCreateDialog 中使用了 setRetainInstance(true); 我用这个:

        public void onDestroyView() {
            if (getDialog() != null && getRetainInstance())
                getDialog().setDismissMessage(null);
            super.onDestroyView();
        }
        

        作为 savedInstanceState 不起作用的解决方法,我创建了一个名为 StateHolder 的私有类(与为 listView 创建持有者的方式相同):

        private class StateHolder {
            String name;
            String quantity;
        }
        

        我这样保存状态:

        @Override
        public void onSaveInstanceState(Bundle savedInstanceState) {
            super.onSaveInstanceState(savedInstanceState);
            stateHolder = new StateHolder();
            stateHolder.name = actvProductName.getText().toString();
            stateHolder.quantity = etProductQuantity.getText().toString();
        }
        

        在 onDismiss 方法中,我将 stateHolder 设置回 null。创建对话框时,它会验证 stateHolder 是否为 null 以恢复状态或只是正常初始化所有内容。

        【讨论】:

          【解决方案6】:

          为了克服Bundle 始终为空的问题,我将其保存到onSaveInstanceState 的静态字段中。这是一种代码异味,但我找到的唯一解决方案是恢复对话框和保存状态。

          Bundle 引用应该在onDestroy 中为空。

          @Override
          public void onCreate(Bundle savedInstanceState)
          {
              if (savedInstanceState == null)
                  savedInstanceState = HackishSavedState.savedInstanceState;
          
              setRetainInstance(true);
          }
          
          @Override
          public Dialog onCreateDialog(Bundle savedInstanceState)
          {
              if (savedInstanceState == null)
                  savedInstanceState = HackishSavedState.savedInstanceState;
          
              ...
          }
          
          @Override
          public void onDestroyView() // necessary for restoring the dialog
          {
              if (getDialog() != null && getRetainInstance())
                  getDialog().setOnDismissListener(null);
          
              super.onDestroyView();
          }
          
          @Override
          public void onSaveInstanceState(Bundle outState)
          {
              ...
          
              HackishSavedState.savedInstanceState = outState;
              super.onSaveInstanceState(outState);
          }
          
          @Override
          public void onDestroy()
          {
              HackishSavedState.savedInstanceState = null;
              super.onDestroy();
          }
          
          private static class HackishSavedState
          {
              static Bundle savedInstanceState;
          }
          

          【讨论】:

          • 这个具有静态成员的类只要该类的类加载器还活着,就会一直留在内存中,这需要一些时间...
          【解决方案7】:

          我遇到了类似的问题,但是以上方法都不适合我。最后我需要在代码中而不是在 XML 布局中创建片段。

          见:Replacing fragments and orientation change

          【讨论】:

            【解决方案8】:

            好的,虽然 Zsombor 的方法有效,但这是由于我对 Fragments 缺乏经验,他的解决方案导致 saveInstanceState Bundle 出现问题。

            显然(至少对于 DialogFragment),它应该是 public static class。您还必须编写自己的 static DialogFragment newInstance() 方法。这是因为 Fragment 类在其instantiate() 方法中调用了newInstance 方法。

            因此,总而言之,您必须像这样编写 DialogFragments:

            public static class MyDialogFragment extends DialogFragment {
            
                static MyDialogFragment newInstance() {
                    MyDialogFragment d = new MyDialogFragment();
                    return d;
                }
            
                @Override
                public Dialog onCreateDialog(Bundle savedInstanceState) {
                    ...
                }
            }
            

            并向他们展示:

            private void showMyDialog() {
                MyDialogFragment d = MyDialogFragment.newInstance();
                d.show(getFragmentManager(), "dialog");
            }
            

            这可能是 ActionBarSherlock 库独有的,但 SDK 文档中的官方示例也使用此范例。

            【讨论】:

            • 我尝试过使用相同的方法,但仍然出现错误。源代码为github.com/gauntface/Android-Boiler-Plate-Kitchen-Sink[.activity.DialogActivity]
            • 您不必编写“您自己的”newInstance() 方法,因为 Fragment.instantiate() 正在调用 Class.newInstance()。但是因为您的片段实例可能使用 Class.newInstance() 进行实例化,所以您必须为您编写的每个片段提供显式的默认构造函数。
            • 我同意@Szabolcs Berecz 的观点,即 DialogFragment 不需要静态构造函数,但它需要一个公共的空(无参数)构造函数,因此可以通过 Class.newInstance() 方法正确初始化它。
            • @Weston 为什么说 DialogFragment 需要是公共静态类?我一直在使用 DialogFragments,并且不需要将它们设为静态...
            • @flock.dux:所有类都有一个默认的、无参数的构造函数(隐式)。需要默认构造函数的原因是肯定的,因此它可以由框架实例化,但是初始化 DialogFragment 的正确方法显示在您在此页面上看到的第一个代码 developer.android.com/reference/android/app/DialogFragment.html 中,使用工厂方法创建一个新的实例并为片段设置参数。您将始终是第一个实例化片段的人(使用工厂方法)。然后在 onCreate 中,您可以访问参数,即使在屏幕旋转之后(例如)。
            【解决方案9】:

            兼容性库中存在可能导致此问题的错误。试着把它放在你的对话片段中:

            @Override
            public void onDestroyView() {
              if (getDialog() != null && getRetainInstance())
                getDialog().setOnDismissListener(null);
              super.onDestroyView();
            }
            

            我还建议将您的对话片段设置为保留,这样它就不会在旋转后被关闭。放“setRetainInstance(true);”例如在 onCreate() 方法中。

            【讨论】:

            • setRetainInstance(true) 修复了崩溃,但对话框在方向更改时被简单地关闭。同时拥有 setRetainInstance(true) 和上面的代码 sn-p 将导致它重新显示。唯一的问题是,savedInstanceState Bundle 不知何故搞砸了。方向改变后,我无法恢复我的价值观。调用 onCreateDialog() 时,Bundle 始终为 null。有什么想法吗?
            • 这解决了我创建持久对话框的任务。使用 V4 兼容性库。但是您确定使用本机 API 11 DialogFragment 不需要这种解决方法吗?
            • 我没有研究它,因为我只使用 backport 库。如果这在 api 级别 11 中仍然存在问题,我不会感到惊讶。我建议对 ICS 以下的任何内容使用 backport lib,这样您就可以确保片段框架始终如一地工作。
            • 请注意,有报道称getDialog().setOnDismissListener(null); 会导致某些设备崩溃。解决方法是改为调用getDialog().setDismissMessage(null);。详情请见this issue。
            • 是的,这对我来说会导致IllegalStateException:OnDismissListener is already taken by DialogFragment and cannot be replaced. 但setDismissMessage 效果很好。
            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2012-02-20
            • 1970-01-01
            • 1970-01-01
            • 2012-03-11
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多