【问题标题】:Android DialogPreference NullPointerException in onRestoreInstanceStateAndroid DialogPreference NullPointerException 在 onRestoreInstanceState
【发布时间】:2013-06-02 17:47:41
【问题描述】:

我正在尝试用两个NumberPicker 对象实现DialogPreference,它在方向更改后恢复最后更改的NumberPicker 值:

public class CustomTimePreference extends DialogPreference {

public NumberPicker firstPicker, secondPicker;

private int lastHour = 0;
private int lastMinute = 15;
private int firstMaxValue;
private int tempHour;
private int tempMinute;
private int rotatedHour;
private int rotatedMinute;
private int firstMinValue = 0;
private int secondMinValue=0;
private int secondMaxValue=59;
private String headerText;
private boolean usedForApprox;




public static int getHour(String time){
    String[] pieces = time.split(":");
    return (Integer.parseInt(pieces[0]));
}

public static int getMinute(String time){
    String[] pieces = time.split(":");
    return (Integer.parseInt(pieces[1]));
}

public CustomTimePreference(Context context){
    this(context, null);
}

public CustomTimePreference(Context context, AttributeSet attrs){
    super(context, attrs);
    init(attrs);
    setDialogLayoutResource(R.layout.custom_time_preference);
    setPositiveButtonText(context.getString(R.string.time_preference_set_text));
    setNegativeButtonText(context.getString(R.string.time_preference_cancel_text));
}

private void init(AttributeSet attrs){
    TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.CustomTimePreference);
    firstMaxValue = a.getInteger(R.styleable.CustomTimePreference_firstMaxValue,10);
    usedForApprox = a.getBoolean(R.styleable.CustomTimePreference_usedForApproximate, false);
    headerText = a.getString(R.styleable.CustomTimePreference_customTimeDialogTopText);
    a.recycle();
}

public void setFirstPickerValue(int value){
    firstPicker.setValue(value);
}

public void setSecondPickerValue(int value){
    secondPicker.setValue(value);
}



@Override
protected View onCreateDialogView(){
    Log.d("OnCreateDialogView","nanana");
    View root = super.onCreateDialogView();
    TextView tv = (TextView)root.findViewById(R.id.custom_time_preference_title);
    tv.setText(headerText);
    firstPicker = (NumberPicker)root.findViewById(R.id.time_preference_first_picker);
    firstPicker.setOnValueChangedListener(new NumberPicker.OnValueChangeListener() {

        @Override
        public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
            // TODO Auto-generated method stub
            tempHour = newVal;
        }
    });

    secondPicker = (NumberPicker)root.findViewById(R.id.time_preference_second_picker);
    secondPicker.setOnValueChangedListener(new NumberPicker.OnValueChangeListener() {

        @Override
        public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
            // TODO Auto-generated method stub
            tempMinute = newVal;
        }
    });
    if(usedForApprox){
        int smallestValue = MainActivity.getShortestPeriodLength(getContext());
        int second = smallestValue % 60;
        second-=1;
        firstPicker.setMaxValue(second);
        secondPicker.setMaxValue(59);

    } else {
        firstPicker.setMaxValue(firstMaxValue);
        secondPicker.setMaxValue(secondMaxValue);
    }
    firstPicker.setMinValue(firstMinValue);
    secondPicker.setMinValue(secondMinValue);
    return root;
}

@Override
protected void onBindDialogView(View v){
    super.onBindDialogView(v);
        firstPicker.setValue(lastHour);
        secondPicker.setValue(lastMinute);
}

@Override
protected void onDialogClosed(boolean positiveResult){
    super.onDialogClosed(positiveResult);
    if(positiveResult){
        lastHour = firstPicker.getValue();
        lastMinute = secondPicker.getValue();

        if (lastHour ==0 && lastMinute == 0){
            lastMinute =1;
        }

        String time = String.valueOf(lastHour) + ":" + String.valueOf(lastMinute);

        if(callChangeListener(time)){
            persistString(time);
        }
    }
}

@Override
protected Object onGetDefaultValue(TypedArray a, int index){
    return a.getString(index);
}

@Override
protected void onSetInitialValue(boolean restoreValue, Object defaultValue){
    String time = null;

    if(restoreValue){
        if (defaultValue == null){
            time = getPersistedString("00:00");
        } else {
            time = getPersistedString(defaultValue.toString());
        }
    } else {
        time = defaultValue.toString();
    }

    lastHour = tempHour =  getHour(time);
    lastMinute = tempMinute = getMinute(time);
}

private static class SavedState extends BaseSavedState {
    // Member that holds the setting's value
    // Change this data type to match the type saved by your Preference
    String value;

    public SavedState(Parcelable superState) {
        super(superState);
    }

    public SavedState(Parcel source) {
        super(source);
        // Get the current preference's value
        value = source.readString();  // Change this to read the appropriate data type
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        super.writeToParcel(dest, flags);
        // Write the preference's value
        dest.writeString(value);  // Change this to write the appropriate data type
    }

    // Standard creator object using an instance of this class
    public static final Parcelable.Creator<SavedState> CREATOR =
            new Parcelable.Creator<SavedState>() {

        public SavedState createFromParcel(Parcel in) {
            return new SavedState(in);
        }

        public SavedState[] newArray(int size) {
            return new SavedState[size];
        }
    };
}

@Override
protected Parcelable onSaveInstanceState() {
    final Parcelable superState = super.onSaveInstanceState();
    // Check whether this Preference is persistent (continually saved)
    /*
    if (isPersistent()) {
        // No need to save instance state since it's persistent, use superclass state
        return superState;
    }
    */
    // Create instance of custom BaseSavedState
    final SavedState myState = new SavedState(superState);
    // Set the state's value with the class member that holds current setting value
    myState.value = String.valueOf(tempHour) + ":" + String.valueOf(tempMinute);
    return myState;
}

@Override
protected void onRestoreInstanceState(Parcelable state) {
    // Check whether we saved the state in onSaveInstanceState
    if (state == null || !state.getClass().equals(SavedState.class)) {
        // Didn't save the state, so call superclass
        super.onRestoreInstanceState(state);
        return;
    }
    // Cast state to custom BaseSavedState and pass to superclass
    SavedState myState = (SavedState) state;
    super.onRestoreInstanceState(myState.getSuperState());
    // Set this Preference's widget to reflect the restored state
    rotatedHour = getHour(myState.value);
    rotatedMinute = getMinute(myState.value);
    firstPicker.setValue(rotatedHour);
    secondPicker.setValue(rotatedMinute);
}

}

有两个问题:

  1. 应用程序崩溃,当我打开我的自定义首选项,更改其中一个选择器的值,然后将手机从纵向旋转到横向时。错误是NuLLpointerException,它指向我尝试将恢复的值分配给我的NumberPicker 对象之一的行。
  2. 这更像是一个问题而不是一个问题。我从 Android 开发者主页复制了 BaseSavedState 内部类和 onSaveInstanceState()、onRestoreInstanceState() 函数,但是当我尝试应用程序在方向更改时恢复值时,手机显示的是持久值,而不是方向更改前的最新值。当我尝试使用日志消息检查代码时,我发现我的手机在 SaveInstanceState isPersisted 检查退出了该功能并且根本不使用 BaseSavedState 对象。我注释掉了 isPersisted 检查,所以现在我可以从 BaseSavedState 对象中保存和检索值,但在那之后问题就出现了。 1 出现。所以我的问题是,如果偏好是持久的,那么跳过 BaseSavedState 对象创建的原因是什么。我是否决定跳过持久性检查并强制应用创建 BaseSavedState 对象?

【问题讨论】:

    标签: android preference numberpicker


    【解决方案1】:

    看看这个包含 3 个NumberPicker 对象的实现:

    package com.bom.dom;
    
    import android.content.Context;
    import android.content.res.TypedArray;
    import android.os.Parcel;
    import android.os.Parcelable;
    import android.preference.DialogPreference;
    import android.util.AttributeSet;
    import android.view.View;
    import android.widget.NumberPicker;
    import android.widget.NumberPicker.OnValueChangeListener;
    
    public class TimePreference extends DialogPreference {
    
    NumberPicker hoursNumberPicker;
    NumberPicker minutesNumberPicker;
    NumberPicker secondsNumberPicker;
    int time;
    int currentTime;
    
    public TimePreference(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
    
    @Override
    protected void onBindDialogView(View view) {
        hoursNumberPicker = (NumberPicker) view.findViewById(R.id.numberpicker_hours);
        hoursNumberPicker.setMaxValue(24);
        hoursNumberPicker.setMinValue(0);
        hoursNumberPicker.setOnValueChangedListener(new OnValueChangeListener() {
            @Override
            public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
                updateCurrentTimeFromUI();
            }
        });
        minutesNumberPicker = (NumberPicker) view.findViewById(R.id.numberpicker_minutes);
        minutesNumberPicker.setMaxValue(59);
        minutesNumberPicker.setMinValue(0);
        minutesNumberPicker.setOnValueChangedListener(new OnValueChangeListener() {
            @Override
            public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
                updateCurrentTimeFromUI();
            }
        });
        secondsNumberPicker = (NumberPicker) view.findViewById(R.id.numberpicker_seconds);
        secondsNumberPicker.setMaxValue(59);
        secondsNumberPicker.setMinValue(0);
        secondsNumberPicker.setOnValueChangedListener(new OnValueChangeListener() {
            @Override
            public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
                updateCurrentTimeFromUI();
            }
        });
        updateUI();
        super.onBindDialogView(view);
    }
    
    @Override
    protected void onDialogClosed(boolean positiveResult) {
        if (positiveResult) {
                time = currentTime;
                persistInt(time);
                return;
        }
        currentTime = time;
    }
    
    private void updateCurrentTimeFromUI() {
        int hours = hoursNumberPicker.getValue();
        int minutes = minutesNumberPicker.getValue();
        int seconds = secondsNumberPicker.getValue();
        currentTime = hours * 3600 + minutes * 60 + seconds;
    }
    
    @Override
    protected void onSetInitialValue(boolean restorePersistedValue, Object defaultValue) {
        if (restorePersistedValue) {
            time = getPersistedInt(1);
        } else {
            time = (Integer) defaultValue;
            persistInt(time);
        }
        currentTime = time;
    }
    
    @Override
    protected Object onGetDefaultValue(TypedArray a, int index) {
        Integer defaultValue = a.getInteger(index, 1);
        return defaultValue;
    }
    
    private void updateUI() {
        int hours = (int) (currentTime / 3600);
        int minutes = ((int) (currentTime / 60)) % 60;
        int seconds = currentTime % 60;
        hoursNumberPicker.setValue(hours);
        minutesNumberPicker.setValue(minutes);
        secondsNumberPicker.setValue(seconds);
    }
    
    @Override
    protected Parcelable onSaveInstanceState() {
        final Parcelable superState = super.onSaveInstanceState();
        final SavedState myState = new SavedState(superState);
        myState.value = currentTime;
        return myState;
    }
    
    @Override
    protected void onRestoreInstanceState(Parcelable state) {
        if (state == null || !state.getClass().equals(SavedState.class)) {
            super.onRestoreInstanceState(state);
            return;
        }
        SavedState myState = (SavedState) state;
        currentTime = myState.value;
        super.onRestoreInstanceState(myState.getSuperState());
    }
    
    private static class SavedState extends BaseSavedState {
        int value;
    
        public SavedState(Parcelable superState) {
            super(superState);
        }
    
        public SavedState(Parcel source) {
            super(source);
            value = source.readInt();
        }
    
        @Override
        public void writeToParcel(Parcel dest, int flags) {
            super.writeToParcel(dest, flags);
            dest.writeInt(value);
        }
    
        @SuppressWarnings("unused")
        public static final Parcelable.Creator<SavedState> CREATOR = new Parcelable.Creator<SavedState>() {
    
            public SavedState createFromParcel(Parcel in) {
                return new SavedState(in);
            }
    
            public SavedState[] newArray(int size) {
                return new SavedState[size];
            }
        };
    }
    }
    

    布局文件为:

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal" >
    
    <NumberPicker
        android:id="@+id/numberpicker_hours"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
    
    <NumberPicker
        android:id="@+id/numberpicker_minutes"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
    
    <NumberPicker
        android:id="@+id/numberpicker_seconds"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
    
    </LinearLayout>
    

    【讨论】:

      【解决方案2】:

      我找到了第一个问题的解决方案: 为了避免 NullPointerException,我必须将访问 NumberPicker 对象的函数附上,并进行检查,以确定这些选择器是否已启动。我遇到了这个问题,因为在我的应用程序上我有多个自定义首选项实例,当我尝试按照数据路径保存/恢复功能时,我发现在 logcat 中我的消息量是我自己的两倍(不仅对于我的屏幕偏好,但也适用于使用我的自定义 DialogPreference 类的其他偏好)。因为,我只打开了一个首选项,其他首选项中的 NumberPicker 对象的初始化没有发生,所以访问这些选择器会导致(如果我理解正确的话)NullPointerException。 但我仍然希望听到更有经验的人来解释保存/恢复功能的默认行为。

      【讨论】:

        猜你喜欢
        • 2012-04-29
        • 1970-01-01
        • 2011-07-21
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多