繁体   English   中英

onRestoreInstanceState中的Android DialogPreference NullPointerException

[英]Android DialogPreference NullPointerException in onRestoreInstanceState

我正在尝试使用两个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 Developer主页复制了BaseSavedState内部类以及onSaveInstanceState()和onRestoreInstanceState()函数,但是当我尝试通过该应用恢复方向更改时的值时,手机显示了保留的值,而不是方向更改前的最新值。 当我尝试使用日志消息检查代码时,我发现我在SaveInstanceState isPersisted检查上的电话退出了该功能,并且无法与BaseSavedState对象一起使用。 我注释掉了isPersisted检查,所以现在我可以从BaseSavedState对象保存和检索值,但是之后出现问题nr。 1出现。 所以我的问题是,如果首选项是持久性的,跳过BaseSavedState对象创建的原因是什么。 我是否决定跳过持久检查并强制应用程序创建BaseSavedState对象是错误的选择?

看一下带有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>

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM