Лучший способ хранить SparseBooleanArray в Bundle?

Когда происходит изменение конфигурации, мои состояния флажка ListView теряются, и я понимаю, почему. я пытаюсь реализовать

public void onSaveInstanceState(final Bundle outState)

в одном из моих Фрагментов. Поэтому мне просто интересно, как проще всего сохранить мой SparseBooleanArray в outState.

Кроме того, я немного смущен, так как ListView имеет метод:

getListView().getCheckedItemPositions();

Для чего это нужно?


person Kitesurfer    schedule 29.06.2012    source источник
comment
Возможно, вам вообще не понадобится использовать onSaveInstanceState(). См. stackoverflow.com/questions/24294919/< /а>   -  person faizal    schedule 19.06.2014


Ответы (8)


В моем случае я сделал это, реализовав оболочку Parcelable вокруг SparseBooleanArray, например так:

import android.os.Parcel;
import android.os.Parcelable;
import android.util.SparseBooleanArray;

public class SparseBooleanArrayParcelable extends SparseBooleanArray implements Parcelable {
  public static Parcelable.Creator<SparseBooleanArrayParcelable> CREATOR = new Parcelable.Creator<SparseBooleanArrayParcelable>() {
    @Override
    public SparseBooleanArrayParcelable createFromParcel(Parcel source) {
      SparseBooleanArrayParcelable read = new SparseBooleanArrayParcelable();
      int size = source.readInt();

      int[] keys = new int[size];
      boolean[] values = new boolean[size];

      source.readIntArray(keys);
      source.readBooleanArray(values);

      for (int i = 0; i < size; i++) {
        read.put(keys[i], values[i]);
      }

      return read;
    }

    @Override
    public SparseBooleanArrayParcelable[] newArray(int size) {
      return new SparseBooleanArrayParcelable[size];
    }
  };

  public SparseBooleanArrayParcelable() {

  }

  public SparseBooleanArrayParcelable(SparseBooleanArray sparseBooleanArray) {
    for (int i = 0; i < sparseBooleanArray.size(); i++) {
      this.put(sparseBooleanArray.keyAt(i), sparseBooleanArray.valueAt(i));
    }
  }

  @Override
  public int describeContents() {
    return 0;
  }

  @Override
  public void writeToParcel(Parcel dest, int flags) {
    int[] keys = new int[size()];
    boolean[] values = new boolean[size()];

    for (int i = 0; i < size(); i++) {
      keys[i] = keyAt(i);
      values[i] = valueAt(i);
    }

    dest.writeInt(size());
    dest.writeIntArray(keys);
    dest.writeBooleanArray(values);
  }
}

Это позволяет вам сохранять и загружать SparseBooleanArray, просто выполнив:

Bundle bundle; //For your activity/fragment state, to include in an intent, ...
SparseBooleanArray sbarray; //Probably from your listview.getCheckedItemPositions()

//Write it
bundle.putParcelable("myBooleanArray", new SparseBooleanArrayParcelable(sbarray));

//Read it back
sbarray = (SparseBooleanArray) bundle.getParcelable("myBooleanArray");

Просто мои 0,02 €

person opsidao    schedule 23.05.2013
comment
Вы должны привести bundle.getParcelable(myBooleanArray) к (SparseBooleanArray) при чтении из Bundle. - person localhost; 18.07.2014
comment
Parcel класс имеет writeSparseBooleanArray и readSparseBooleanArray вместо записи чтения и записи вручную - person Mohamed Kamal Kamaly; 07.05.2017
comment
@MohamedKamalKamaly, как вы можете видеть здесь этого метода нет даже в выпущенной версии API, а этому ответу уже четыре года. В любом случае, я бы не стал слишком быстро использовать эти новые методы, если бы я был на вашем месте... - person opsidao; 08.05.2017
comment
упс, только что понял, что смотрю не на тот метод :P - person opsidao; 08.05.2017

Я не вижу ответов на последнюю часть вопроса:

Кроме того, я немного смущен, так как ListView имеет метод:

getListView().getCheckedItemPositions();

Для чего это нужно?

Предполагая, что вы используете флажки в сочетании с режимом действия, сам режим действия будет хранить состояние проверенных позиций. В обычном режиме нажатие и удерживание выделяет элемент, активирует режим действия, а затем позволяет пользователю коснуться других элементов, чтобы также проверить их.

Что происходит при смене ориентации? Режим действия возобновляется, а основные моменты сохраняются. Ваши флажки - нет, но явно данные о том, что проверено.

После изменения ориентации вызывается onCreateActionMode(). В этом методе (но не раньше) getListView().getCheckedItemPositions() вернет SparseBooleanArray, которое вы, вероятно, ищете.

Изящная часть этого заключается в том, что если это ваш единственный вариант использования SparseBooleanArray, вам даже не нужно беспокоиться о его хранении. Вы можете получить его из системы, которая уже сохраняет его при изменении ориентации.

Вот для чего подходит getListView().getCheckedItemPositions().

person lilbyrdie    schedule 28.04.2013

Вы можете либо придумать какую-то схему сериализации для этой структуры данных, либо переключиться на HashSet и сохранить в нем только проверенные позиции индекса списка. Поскольку HashSet является сериализуемым, вы можете просто поместить его в пакет состояния экземпляра.

person tiguchi    schedule 29.06.2012
comment
спасибо, в моем случае это было проще, чем создавать дополнительные классы - person Kirill Karmazin; 29.08.2018
comment
HashMap‹Integer,Boolean› будет лучшей альтернативой SparseBooleanArray, чем HashSet - person shankar_vl; 04.06.2021

Я пытаюсь сделать то же самое. Изначально я использовал HashMap

Как отправить значение hashmap другому действию с помощью намерение

для перехода между действиями, поскольку он расширяет интерфейс Serializable. Однако после некоторых исследований:

«SparseBooleanArrays должны быть более эффективными, чем использование HashMap для сопоставления Integers с Booleans».

Однако я не могу найти простой способ сохранить эту структуру данных. Я подумал о том, чтобы захватить значения SparseBooleanArray и сохранить их в HashMap, чтобы их можно было передавать между действиями. Но это, кажется, добавляет больше сложности.

К сожалению, я думаю, что вернусь к использованию HashMaps для хранения списка отмеченных флажков.

person toobsco42    schedule 16.08.2012

Расширьте SparseArray для реализации Serializable:

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import android.util.SparseArray;



/**
 * @author Asaf Pinhassi www.mobiledev.co.il
 * @param <E>
 *
 */
public class SerializableSparseArray<E> extends SparseArray<E> implements Serializable{

    private static final long serialVersionUID = 824056059663678000L;

    public SerializableSparseArray(int capacity){
        super(capacity);
    }

    public SerializableSparseArray(){
        super();
    }

    /**
     * This method is private but it is called using reflection by java
     * serialization mechanism. It overwrites the default object serialization.
     *
     * <br/><br/><b>IMPORTANT</b>
     * The access modifier for this method MUST be set to <b>private</b> otherwise {@link java.io.StreamCorruptedException}
     * will be thrown.
     *
     * @param oos
     *            the stream the data is stored into
     * @throws IOException
     *             an exception that might occur during data storing
     */
    private void writeObject(ObjectOutputStream oos) throws IOException {
        Object[] data = new  Object[size()];

        for (int i=data.length-1;i>=0;i--){
            Object[] pair = {keyAt(i),valueAt(i)}; 
            data[i] = pair;
        }
        oos.writeObject(data);
    }

    /**
     * This method is private but it is called using reflection by java
     * serialization mechanism. It overwrites the default object serialization.
     *
     * <br/><br/><b>IMPORTANT</b>
     * The access modifier for this method MUST be set to <b>private</b> otherwise {@link java.io.StreamCorruptedException}
     * will be thrown.
     *
     * @param oos
     *            the stream the data is read from
     * @throws IOException
     *             an exception that might occur during data reading
     * @throws ClassNotFoundException
     *             this exception will be raised when a class is read that is
     *             not known to the current ClassLoader
     */
    private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
        Object[] data = (Object[]) ois.readObject();
        for (int i=data.length-1;i>=0;i--){
            Object[] pair = (Object[]) data[i]; 
            this.append((Integer)pair[0],(E)pair[1]);
        }
        return;
    }


}
person Asaf Pinhassi    schedule 05.02.2014

Я бы создал класс держателя Parcelable, используя собственные методы из Parcel, чтобы сериализовать/десериализовать SparseBooleanArray:

import android.os.Parcel;
import android.os.Parcelable;
import android.util.SparseBooleanArray;

public class SparseBooleanArrayHolder implements Parcelable {

    private final SparseBooleanArray source;

    public SparseBooleanArrayHolder(SparseBooleanArray source) {
        this.source = source;
    }

    public SparseBooleanArray get() {
        return source;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeSparseBooleanArray(source);
    }

    @Override
    public int describeContents() {
        return 0;
    }

    public static final Creator<SparseBooleanArrayHolder> CREATOR = new Creator<SparseBooleanArrayHolder>() {
        @Override
        public SparseBooleanArrayHolder createFromParcel(Parcel in) {
            return new SparseBooleanArrayHolder(in.readSparseBooleanArray());
        }

        @Override
        public SparseBooleanArrayHolder[] newArray(int size) {
            return new SparseBooleanArrayHolder[size];
        }
    };
}
person BladeCoder    schedule 16.04.2018

Вот мое решение. Просто простая оболочка, которую можно использовать для сериализации SparseBooleanArray.

public class SerializableSparseBooleanArrayContainer implements Serializable {

    private static final long serialVersionUID = 393662066105575556L;
    private SparseBooleanArray mSparseArray;

    public SerializableSparseBooleanArrayContainer(SparseBooleanArray mDataArray) {
        this.mSparseArray = mDataArray;
    }

    public SparseBooleanArray getSparseArray() {
        return mSparseArray;
    }

    public void setSparseArray(SparseBooleanArray sparseArray) {
        this.mSparseArray = sparseArray;
    }

    private void writeObject(java.io.ObjectOutputStream out) throws IOException {
        out.writeLong(serialVersionUID);
        int sparseArraySize = mSparseArray.size();
        out.write(sparseArraySize);
        for (int i = 0 ; i < sparseArraySize; i++){
            int key = mSparseArray.keyAt(i);
            out.writeInt(key);
            boolean value = mSparseArray.get(key);
            out.writeBoolean(value);
        }
    }

    private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException {
        long readSerialVersion = in.readLong();
        if (readSerialVersion != serialVersionUID) {
            throw new IOException("serial version mismatch");
        }
        int sparseArraySize = in.read();
        mSparseArray = new SparseBooleanArray(sparseArraySize);
        for (int i = 0 ; i < sparseArraySize; i++) {
            int key = in.readInt();
            boolean value = in.readBoolean();
            mSparseArray.put(key, value);
        }
    }

}

Затем я добавляю объект в пакет следующим образом:

@Override
public void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    SparseBooleanArray sparseBooleanArray = getSparseBooleanArray();
    SerializableSparseBooleanArrayContainer sparseBooleanArraySerializable = new SerializableSparseBooleanArrayContainer(sparseBooleanArray);
    outState.putSerializable(SOME_BUNDLE_KEY, sparseBooleanArraySerializable);
}
person Diederik    schedule 25.01.2013

Я попробовал решение, создающее SerializableSparseArray расширение SparseArray, позволяющее поместить SparseArray в пакет через вызов Bundle.putSerializable.

Но я обнаружил, что не могу получить сохраненный объект из пакета в onRestoreInstanceState. Копаясь в вопросе, я обнаружил, что savedInstanceState.getSerializable(KEY_TO_STRING_SPARSE_ARRAY) == null.

Затем, пытаясь осмотреть savedInstanceState.get(KEY_TO_STRING_SPARSE_ARRAY), получил SparseArray<String>, а не SerializableSparseArray<String>. Наконец, я использую savedInstanceState.getSparseParcelableArray(KEY_TO_STRING_SPARSE_ARRAY) для получения SparseArray обратно из пакета.

Затем, используя отражение java, сохраните SparseArray<String> в пакет напрямую, не расширяя его с помощью интерфейса Serializable или Parcelable. Это немного грязно, но я думаю, вы можете сделать свою собственную служебную функцию, скрывающую следующую детальную реализацию.

try {
    // FIXME WTF
    Method m = Bundle.class.getMethod("putSparseParcelableArray", String.class, SparseArray.class);
    m.invoke(savedInstanceState, KEY_TO_STRING_SPARSE_ARRAY, mSparseStringArray);
} catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
    Log.e(TAG, "exception", e);
}

Я протестировал код, и он работает на Android 4.4.4. И я хотел бы знать, безопасно ли использовать реализацию в других реализациях SDK.

person kirisetsz    schedule 08.08.2014