问题
I am trying to switch my app to use dialog fragments but I get an application crash when rotating the screen while the dialog is visible. I can reproduce this in a very simple app described below. Create a new project in Android studio and add a DialogFragment like so:
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.
getMenuInflater().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;
}
}
}
Now run the app, and after the dialog shows up (1 second after loading), rotate the screen. Note that I only create the dialog on the initial onCreate above.
Here is the exception Im getting:
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)
My gradle script looks like this:
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:+'
}
which is just the standard build script when you create a new project and uses the latest android support jar.
I can do setRetainInstance in the DialogFragment's onCreate method and then it doesn't crash, but the dialog gets dismissed on rotation. This is better than a crash obviously but not what I'm looking for.
I'm not really sure what the preferred way of using DialogFragments is but I got this idea from some sample code straight from google (for the google service sdk). I figured they knew what they were doing so I'd use the same concept.
回答1:
I hate to answer my own questions but I figured out the reason for the crash. I need to actually do the dialog creation in onCreateDialog of the DialogFragment rather than setting it from my Activity. Not exactly sure why this is the case. Perhaps on rotation the android system erases references tied to the old instance of the Activity. This isn't totally ideal but I can work with it by perhaps passing in the data used to create the dialog in
Here is the update code which works on rotation without crashing:
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.
getMenuInflater().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();
}
}
}
回答2:
I solved such a problem via subclassing DialogFragment class and overriding next two lifecycle callback methods:
@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();
}
回答3:
When the activity is first created, the savedInstanceState
is null because it had no previous state to be saved. When the screen is rotated the activity is destroyed and the onCreate(savedInstanceState)
is called again with some states saved (like your DialogFragment
state, which is visible).
So, firstly your savedInstaceState
is null, when the screen rotates, instances are saved and then restored on the onCreate(savedInstanceState)
so it is not null anymore and anything inside if(savedInstanceState == null)
will be called, and then you get the NullPointerException
.
To fix the problem, remove this validation:
if(savedInstanceState == null){
///blablalba
}
来源:https://stackoverflow.com/questions/21465932/android-support-dialogfragment-crashes-on-screen-rotation