Поддержка Android DialogFragment падает при повороте экрана

Я пытаюсь переключить свое приложение на использование фрагментов диалогового окна, но при сбое экрана при появлении диалогового окна появляется крах приложения. Я могу воспроизвести это в очень простом приложении, описанном ниже. Создайте новый проект в студии Android и добавьте DialogFragment следующим образом:

public class MainActivity extends ActionBarActivity {
 @Override
 protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 setContentView(R.layout.activity_main);
 if (savedInstanceState == null) {
 getSupportFragmentManager().beginTransaction()
 .add(R.id.container, new PlaceholderFragment())
 .commit();
 }
 if (savedInstanceState == null) {
 (new Handler()).postDelayed(new Runnable() {
 @Override
 public void run() {
 AlertDialog dialog = new AlertDialog.Builder(MainActivity.this)
 .setMessage("Alert")
 .setTitle("My Alert")
 .create();
 MyDialogFragment dialogFragment = new MyDialogFragment();
 dialogFragment.setDialog(dialog);
 dialogFragment.show(getSupportFragmentManager(), "dialog");
 }
 }, 1000);
 }
 }
 @Override
 public boolean onCreateOptionsMenu(Menu menu) {
 // Inflate the menu; this adds items to the action bar if it is present.
 ***************().inflate(R.menu.main, menu);
 return true;
 }
 @Override
 public boolean onOptionsItemSelected(MenuItem item) {
 // Handle action bar item clicks here. The action bar will
 // automatically handle clicks on the Home/Up button, so long
 // as you specify a parent activity in AndroidManifest.xml.
 int id = item.getItemId();
 if (id == R.id.action_settings) {
 return true;
 }
 return super.onOptionsItemSelected(item);
 }
 /**
 * A placeholder fragment containing a simple view.
 */
 public static class PlaceholderFragment extends Fragment {
 public PlaceholderFragment() {
 }
 @Override
 public View onCreateView(LayoutInflater inflater, ViewGroup container,
 Bundle savedInstanceState) {
 View rootView = inflater.inflate(R.layout.fragment_main, container, false);
 return rootView;
 }
 @Override
 public void onActivityCreated(Bundle savedState) {
 super.onActivityCreated(savedState);
 }
 }
 public static class MyDialogFragment extends DialogFragment {
 private Dialog mDialog;
 public MyDialogFragment() {
 super();
 mDialog = null;
 }
 @Override
 public void onCreate(Bundle state) {
 super.onCreate(state);
 }
 // Set the dialog to display
 public void setDialog(Dialog dialog) {
 mDialog = dialog;
 }
 // Return a Dialog to the DialogFragment.
 @Override
 public Dialog onCreateDialog(Bundle savedInstanceState) {
 return mDialog;
 }
 }
}

Теперь запустите приложение, и после появления диалогового окна (через 1 секунду после загрузки) поверните экран. Обратите внимание, что я только создаю диалог на начальном onCreate выше.

Вот исключение Im получение:

01-30 11:19:40.199 31986-31986/? E/AndroidRuntime﹕ FATAL EXCEPTION: main
 Process: com.example.testdialogs, PID: 31986
 java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.testdialogs/com.example.testdialogs.MainActivity}: java.lang.NullPointerException
 at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2215)
 at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2265)
 at android.app.ActivityThread.handleRelaunchActivity(ActivityThread.java:3758)
 at android.app.ActivityThread.access$900(ActivityThread.java:145)
 at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1212)
 at android.os.Handler.dispatchMessage(Handler.java:102)
 at android.os.Looper.loop(Looper.java:136)
 at android.app.ActivityThread.main(ActivityThread.java:5081)
 at java.lang.reflect.Method.invokeNative(Native Method)
 at java.lang.reflect.Method.invoke(Method.java:515)
 at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:781)
 at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:597)
 at dalvik.system.NativeStart.main(Native Method)
 Caused by: java.lang.NullPointerException
 at android.support.v4.app.DialogFragment.onActivityCreated(DialogFragment.java:368)
 at android.support.v4.app.Fragment.performActivityCreated(Fragment.java:1508)
 at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:947)
 at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1104)
 at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1086)
 at android.support.v4.app.FragmentManagerImpl.dispatchActivityCreated(FragmentManager.java:1884)
 at android.support.v4.app.FragmentActivity.onStart(FragmentActivity.java:566)
 at android.app.Instrumentation.callActivityOnStart(Instrumentation.java:1171)
 at android.app.Activity.performStart(Activity.java:5241)
 at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2178)
            at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2265)
            at android.app.ActivityThread.handleRelaunchActivity(ActivityThread.java:3758)
            at android.app.ActivityThread.access$900(ActivityThread.java:145)
            at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1212)
            at android.os.Handler.dispatchMessage(Handler.java:102)
            at android.os.Looper.loop(Looper.java:136)
            at android.app.ActivityThread.main(ActivityThread.java:5081)
            at java.lang.reflect.Method.invokeNative(Native Method)
            at java.lang.reflect.Method.invoke(Method.java:515)
            at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:781)
            at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:597)
            at dalvik.system.NativeStart.main(Native Method)
            
            
            
            

Мой gradle script выглядит следующим образом:

apply plugin: 'android'
android {
 compileSdkVersion 19
 buildToolsVersion "19.0.1"
 defaultConfig {
 minSdkVersion 9
 targetSdkVersion 19
 versionCode 1
 versionName "1.0"
 }
 compileOptions {
 sourceCompatibility JavaVersion.VERSION_1_7
 targetCompatibility JavaVersion.VERSION_1_7
 }
 buildTypes {
 release {
 runProguard false
 proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
 }
 }
}
dependencies {
 compile 'com.android.support:appcompat-v7:+'
}

который является стандартной конструкцией script при создании нового проекта и использует последнюю банку поддержки Android.

Я могу сделать setRetainInstance в методе DialogFragment onCreate, а затем он не сбой, но диалог отбрасывается при вращении. Это лучше, чем крушение, но не то, что я ищу.

Я не совсем уверен, какой предпочтительный способ использования DialogFragments, но я получил эту идею из некоторого примера кода прямо из google (для службы google service sdk). Я подумал, что они знают, что они делают, поэтому я бы использовал ту же концепцию.

3 ответа

Я ненавижу отвечать на свои вопросы, но я выяснил причину крушения. Мне нужно на самом деле создать диалог в onCreateDialog диалога DialogFragment, а не устанавливать его из моей Activity. Не совсем понятно, почему это так. Возможно, при ротации система андроида стирает ссылки, привязанные к старому экземпляру Activity. Это не совсем идеально, но я могу работать с ним, возможно, передавая данные, используемые для создания диалога в

Вот код обновления, который работает при вращении без сбоев:

public class MainActivity extends ActionBarActivity {
 @Override
 protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 setContentView(R.layout.activity_main);
 if (savedInstanceState == null) {
 getSupportFragmentManager().beginTransaction()
 .add(R.id.container, new PlaceholderFragment())
 .commit();
 }
 if (savedInstanceState == null) {
 (new Handler()).postDelayed(new Runnable() {
 @Override
 public void run() {
 MyDialogFragment dialogFragment = new MyDialogFragment();
 dialogFragment.show(getSupportFragmentManager(), "dialog");
 }
 }, 1000);
 }
 }
 @Override
 public boolean onCreateOptionsMenu(Menu menu) {
 // Inflate the menu; this adds items to the action bar if it is present.
 ***************().inflate(R.menu.main, menu);
 return true;
 }
 @Override
 public boolean onOptionsItemSelected(MenuItem item) {
 // Handle action bar item clicks here. The action bar will
 // automatically handle clicks on the Home/Up button, so long
 // as you specify a parent activity in AndroidManifest.xml.
 int id = item.getItemId();
 if (id == R.id.action_settings) {
 return true;
 }
 return super.onOptionsItemSelected(item);
 }
 /**
 * A placeholder fragment containing a simple view.
 */
 public static class PlaceholderFragment extends Fragment {
 public PlaceholderFragment() {
 }
 @Override
 public View onCreateView(LayoutInflater inflater, ViewGroup container,
 Bundle savedInstanceState) {
 View rootView = inflater.inflate(R.layout.fragment_main, container, false);
 return rootView;
 }
 @Override
 public void onActivityCreated(Bundle savedState) {
 super.onActivityCreated(savedState);
 }
 }
 public static class MyDialogFragment extends DialogFragment {
 public MyDialogFragment() {
 super();
 }
 @Override
 public void onCreate(Bundle state) {
 super.onCreate(state);
 }
 // Return a Dialog to the DialogFragment.
 @Override
 public Dialog onCreateDialog(Bundle savedInstanceState) {
 return AlertDialog dialog = new AlertDialog.Builder(MainActivity.this)
 .setMessage("Alert")
 .setTitle("My Alert")
 .create();
 }
 } 
}


Я решил эту проблему с помощью подкласса класса DialogFragment и переопределения следующих двух методов обратного вызова жизненного цикла:

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


Когда действие сначала создается, savedInstanceState имеет значение null, поскольку он не сохранил предыдущее состояние. Когда экран вращается, активность уничтожается, а onCreate(savedInstanceState) вызывается снова с сохраненными состояниями (например, ваше состояние DialogFragment, которое видно).

Итак, во-первых, ваш savedInstaceState равен нулю, когда экран вращается, экземпляры сохраняются и затем восстанавливаются на onCreate(savedInstanceState), поэтому он больше не равен нулю, и что-либо внутри if(savedInstanceState == null) будет вызвано, а затем вы получите NullPointerException.

Чтобы устранить проблему, удалите эту проверку:

if(savedInstanceState == null){
 ///blablalba
}

licensed under cc by-sa 3.0 with attribution.