在Bundle中存储SparseBooleanArray的最佳方法是什么?

15

当出现配置更改时,我的ListView复选框状态会丢失,我可以理解为什么会这样。

我尝试实现

public void onSaveInstanceState(final Bundle outState)

我有一个Fragment,想知道最简单的方法是将SparseBooleanArray存储在outState中。

另外,我有点困惑,因为ListView有这个方法:

getListView().getCheckedItemPositions();

这有什么用处?


你可能根本不需要使用onSaveInstanceState()。请参见http://stackoverflow.com/questions/24294919/maintain-item-selection-on-orientation-change - faizal
8个回答

40

在我的情况下,我最终通过在SparseBooleanArray周围实现一个Parcelable包装器来完成它,就像这样:

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欧元。


当从Bundle中读取时,您应该将bundle.getParcelable("myBooleanArray")转换为(SparseBooleanArray)。 - localhost
1
Parcel类拥有writeSparseBooleanArrayreadSparseBooleanArray函数,可以自动处理读写操作,无需手动编写。 - Mohamed Kamaly
@MohamedKamalKamaly,正如您在这里所看到的,该方法甚至还没有发布到API的版本中,而这个答案已经有四年了。无论如何,如果我是您,我不会太快地使用这些新方法... - opsidao
糟糕,我刚意识到我看错了方法 :P - opsidao

4

我没有看到对问题最后一部分的回答:

另外,我有点困惑,因为ListView有这个方法:

getListView().getCheckedItemPositions();

这有什么用?

假设您正在与Action Mode结合使用复选框,则Action Mode本身将存储所选位置的状态。在正常模式下,按住并保持按住会突出显示一个项目,启用操作模式,然后允许用户点击其他项目以同时选中它们。

发生方向更改时会发生什么? Action Mode将被恢复,并保留高亮显示。但是您的复选框不会,但显然已经检查了哪些数据。

方向更改后,将调用onCreateActionMode()。在此方法中(但不是之前),getListView().getCheckedItemPositions()将返回您可能正在寻找的SparseBooleanArray

这个巧妙的部分是,如果这是您对SparseBooleanArray的唯一用例,则甚至不需要担心自己存储它。您可以从系统中检索它,该系统已经在方向更改期间存储它。

这就是getListView().getCheckedItemPositions()的用处。


2
你可以为该数据结构发明一些序列化方案,或者切换到 HashSet 并仅存储已检查的列表索引位置。由于 HashSet 是可序列化的,因此你可以将其放入实例状态 Bundle 中。

谢谢,对我来说比创建额外的类更容易。 - Kirill Karmazin
HashMap<Integer,Boolean> 比 HashSet 更适合作为 SparseBooleanArray 的替代方案。 - shankar_vl

1
我会创建一个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];
        }
    };
}

1

我正在尝试做同样的事情。最初我使用了一个HashMap来传递活动之间的数据,因为它扩展了Serializable接口。

如何使用意图将哈希映射值发送到另一个活动

然而,在进行一些研究后:

"SparseBooleanArrays旨在比使用HashMapIntegers映射到Booleans更有效。"

但是,我找不到一种简单的方法来存储这个数据结构。我已经考虑过获取SparseBooleanArray的值并将其存储在HashMap中,以便可以在活动之间传递。但这似乎增加了更多的复杂性。

不幸的是,我认为我将回归使用HashMap来存储我的选中框列表。


1
将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;
    }


}

0
这是我的解决方案。只需一个简单的包装器即可用于序列化 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);
        }
    }

}

我接着将对象添加到一个 bundle 中,方法如下:
@Override
public void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    SparseBooleanArray sparseBooleanArray = getSparseBooleanArray();
    SerializableSparseBooleanArrayContainer sparseBooleanArraySerializable = new SerializableSparseBooleanArrayContainer(sparseBooleanArray);
    outState.putSerializable(SOME_BUNDLE_KEY, sparseBooleanArraySerializable);
}

0

我尝试了创建一个SerializableSparseArray,继承自SparseArray的解决方案,使得可以通过Bundle.putSerializable调用将SparseArray放入bundle中。

但是我发现在onRestoreInstanceState中无法从bundle中获取保存的对象。经过深入研究,我发现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)从bundle中获取SparseArray

接着,我使用Java反射直接将SparseArray<String>保存到bundle中,而不需要扩展SerializableParcelable接口。虽然有点不太规范,但我认为你可以编写自己的实用函数来隐藏以下详细实现。

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 实现中使用这个实现是否安全。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接