What is the proper way to handle orientation chang

2020-03-02 13:46发布

In my app I have a custom AlertDialog (handled by the system using showDialog()) which contains a tabhost with 2 tabs. In one of the tabs is a spinner. I can rotate my screen without problems as long as the spinner isn't open (spinner dialog displayed). If the spinner is open during rotation, I get this:

FATAL EXCEPTION: main
java.lang.IllegalArgumentException: View not attached to window manager
    at android.view.WindowManagerImpl.findViewLocked(WindowManagerImpl.java:355)
    at android.view.WindowManagerImpl.removeView(WindowManagerImpl.java:200)
    at android.view.Window$LocalWindowManager.removeView(Window.java:432)
    at android.app.Dialog.dismissDialog(Dialog.java:278)
    at android.app.Dialog.access$000(Dialog.java:71)
    at android.app.Dialog$1.run(Dialog.java:111)
    at android.app.Dialog.dismiss(Dialog.java:268)
    at android.widget.Spinner.onDetachedFromWindow(Spinner.java:89)
    at android.view.View.dispatchDetachedFromWindow(View.java:6173)
    at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:1164)
    at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:1162)
    at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:1162)
    at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:1162)
    at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:1162)
    at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:1162)
    at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:1162)
    at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:1162)
    at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:1162)
    at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:1162)
    at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:1162)
    at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:1162)
    at android.view.ViewRoot.dispatchDetachedFromWindow(ViewRoot.java:1746)
    at android.view.ViewRoot.doDie(ViewRoot.java:2757)
    at android.view.ViewRoot.handleMessage(ViewRoot.java:1995)
    at android.os.Handler.dispatchMessage(Handler.java:99)
    at android.os.Looper.loop(Looper.java:130)
    at android.app.ActivityThread.main(ActivityThread.java:3683)
    at java.lang.reflect.Method.invokeNative(Native Method)
    at java.lang.reflect.Method.invoke(Method.java:507)
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:839)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:597)
    at dalvik.system.NativeStart.main(Native Method)

So...

1 - Is it possible to programmatically close the spinner during onPause()?

2 - Is there a different method I should be using?

3 - If there isn't a proper solution, how do I catch this particular Exception? (Bad practice I know, but the crashing needs to be stopped, and since the activity reconstructs itself properly after being destroyed, it wouldn't matter anyway.

... And please for the love of all that is holy, do not suggest that I add android:configChanges="orientation" to my activity declaration. It amazes me how often that is accepted as the official fix-all for orientation issues.

Edit for additional info

Here is my dialog creation code for reference:

static final int ID_DIALOG_CHOOSER = 1;
int pref_location;
private Dialog dialog;

...

protected Dialog onCreateDialog(int id)
{
    switch(id)
    {
    case ID_DIALOG_CHOOSER:
        dialog = show(ID_DIALOG_CHOOSER);
        break;
    }
    return dialog;
}

...

showDialog(DialogView.ID_DIALOG_CHOOSER);

...

Dialog show(final int dialogType) 
{
    if (dialogType == ID_DIALOG_CHOOSER)
    {
        AlertDialog.Builder builder = new AlertDialog.Builder(this);

        // inflate tabhost layout
        View tabHostLayout = (View) inflater.inflate(R.layout.tabhost_layout, null);
        FrameLayout tabContent = (FrameLayout) tabHostLayout.findViewById(android.R.id.tabcontent);

        // inflate tab content layouts and add to tabhost layout
        LinearLayout tab1 = (LinearLayout) inflater.inflate(R.layout.dialog_tab1, null);
        tabContent.addView(tab1);
        LinearLayout tab2 = (LinearLayout) inflater.inflate(R.layout.dialog_tab2, null);
        tabContent.addView(tab2);

        // tab setup
        TabHost tabHost = (TabHost) tabHostLayout.findViewById(R.id.TabHost_Dialog_Tabs);
        tabHost.setup();

        TabHost.TabSpec tab_1 = tabHost.newTabSpec("Category 1");
        tab_1.setContent(R.id.LinearLayout_Dialog_Tab1_Content);
        tab_1.setIndicator(this.getResources().getString(R.string.dialog_tab1), this.getResources().getDrawable(R.drawable.tabhost_icon_selector));
        tabHost.addTab(tab_1);

        TabHost.TabSpec tab_2 = tabHost.newTabSpec("Category 2");
        tab_2.setContent(R.id.LinearLayout_Dialog_Tab2_Content);
        tab_2.setIndicator(this.getResources().getString(R.string.dialog_tab2), this.getResources().getDrawable(R.drawable.tabhost_icon_selector));
        tabHost.addTab(tab_2);

        // spinner setup
        final Spinner spinner_location = (Spinner) tab1.findViewById(R.id.Spinner_Dialog_Location);
        String[] locationArrayVals = null;
        ArrayAdapter<CharSequence> adapter_location = null;
        locationArrayVals = this.getResources().getStringArray(R.array.location_array_vals);
        adapter_location = ArrayAdapter.createFromResource(this, R.array.location_array_listdisplay, android.R.layout.simple_spinner_item);
        adapter_location.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
        spinner_location.setAdapter(adapter_location);

        // ok button
        Button button_ok = (Button) tab1.findViewById(R.id.Button_Dialog_OK);
        button_ok.setOnClickListener(new View.OnClickListener() 
        {
            public void onClick(View view) 
            {
                pref_location = spinner_location.getSelectedItemPosition();
                dialog.dismiss();
            }
        });

        // create dialog
        builder.setTitle("Location")
        .setView(tabHost)
        .setCancelable(true);
        dialog = builder.create();
    }
    return dialog;
}

Edit with temporary workaround

For anybody interested I managed to at least stop the crashing by subclassing spinner and overriding onDetachedFromWindow() like so:

public static class CatchingSpinner extends Spinner
{
    public CatchingSpinner(Context context, AttributeSet attrs) 
    {
        super(context, attrs);
    }

    @Override
    protected void onDetachedFromWindow()
    {
        try
        {
            super.onDetachedFromWindow();
        }
        catch (IllegalArgumentException e)
        {
            // do whatever
        }
    } 
}

Like I said, a workaround. Still working on a solution. :/

标签: java android
3条回答
叛逆
2楼-- · 2020-03-02 14:27

I found a better solution. It is actually not so hard to replace Spinner with a button that looks and behaves like Spinner (except the crash, fortunately).

<Button 
     android:background="@android:drawable/btn_dropdown" 
     android:gravity="left|center_vertical"
     android:layout_width="wrap_content" 
     android:layout_height="wrap_content" />

public void showSpinner() {
    AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
    builder.setTitle(_spinnerPrompt);
    builder.setSingleChoiceItems(_spinnerOptions, _spinnerSelection,
      new DialogInterface.OnClickListener() {
        @Override
        public void onClick(DialogInterface dialog, int item) {
            _spinnerSelection = item;
            _pseudoSpinner.setText(_spinnerOptions[item]);
            _restoreSpinnerOnRestart = false;
            dialog.dismiss();
        }
    });
    builder.setOnCancelListener(new DialogInterface.OnCancelListener() {
        @Override
        public void onCancel(DialogInterface dialog) {
            _restoreSpinnerOnRestart = false;
        }
    });
    AlertDialog alert = builder.create();
    _restoreSpinnerOnRestart = true;
    alert.show();
    }

@Override
public Bundle onSaveInstanceState() {
    Bundle state = super.onSaveInstanceState();
    state.putBoolean(STATE_SPINNER_RESTORE, _restoreSpinnerOnRestart);
    state.putInt(STATE_SPINNER_SELECTION, _spinnerSelection);
    return state;
};

@Override
public void onRestoreInstanceState(Bundle savedInstanceState) {
    super.onRestoreInstanceState(savedInstanceState);
    _spinnerSelection = savedInstanceState.getInt(STATE_SPINNER_SELECTION, -1);
    _restoreSpinnerOnRestart = savedInstanceState.getBoolean(STATE_SPINNER_RESTORE);
    _pseudoSpinner.setText(_spinnerOptions[_spinnerSelection]);

    if (_restoreSpinnerOnRestart) {
        showSpinner();
    }
};
查看更多
趁早两清
3楼-- · 2020-03-02 14:28

Ok, I think I've found your problem: dialog.dismiss();
You are doing really strange thing - you are creating dialog through activity but say dismiss to it not to activity to dismiss it - those are two different approaches which you can't mix. You should choose one method: dialog.show and dialog.dismiss or activity.showDialog() and activity.dismissDialog() <- this is better because handles orientation change.
What you should do is just remove dialog from class variables, use activity.showDialog everywhere. Probably your problem was in dismissing dialog in onClick. Just use YourActivityName.this.dismissDialog(DIALOG_NUMBER) and it should work.
Read more about dialogs form developers site - I had also such problems and taken long time to learn how it works but after all - dialogs are not so complicated ;)

查看更多
Animai°情兽
4楼-- · 2020-03-02 14:36

I had a similar a crash. I worked around by disabling orientation changes when dialog is displayed.

@Override
public void onDismiss(DialogInterface dialog) {
    setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
}

@Override
public void onShow(DialogInterface dialog) {
    int loadedOrientation = getResources().getConfiguration().orientation;
    int requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
    if (loadedOrientation == Configuration.ORIENTATION_LANDSCAPE) {
        requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
    } else if (loadedOrientation == Configuration.ORIENTATION_PORTRAIT) {
        requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
    }
    setRequestedOrientation(requestedOrientation);
}

NOTE: I actually found that there is no reliable way to lock screen orientation.

查看更多
登录 后发表回答