可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
When a config change happens, my ListView Checkbox states get lost, which I understand why.
I try to implement
public void onSaveInstanceState(final Bundle outState)
in one of my Fragments. So I'm just wondering what's the easiest way to store my SparseBooleanArray in the outState.
Also, I'm a bit confused, as ListView has the method:
getListView().getCheckedItemPositions();
What's this good for?
回答1:
In my case, I ended up doing it by implementing a Parcelable wrapper around the SparseBooleanArray, like this:
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);
}
}
This allows you to save and load your SparseBooleanArray by just doing:
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");
Just my .02€
回答2:
I don't see any answers to the last part of the question:
Also, I'm a bit confused, as ListView has the method:
getListView().getCheckedItemPositions();
What's this good for?
Assuming you're using checkboxes in conjunction with Action Mode, the Action Mode itself will store the state of checked positions. In the normal mode, a press-and-hold will highlight an item, enable action mode, and then allow the user to tap other items to also check them.
What happens on an orientation change? Action mode is resumed and the highlights are kept. Your checkboxes aren't, but clearly the data of what is checked is.
After an orientation change, onCreateActionMode()
is called. In this method (but not before), getListView().getCheckedItemPositions()
will return the SparseBooleanArray
you're probably looking for.
The nifty part about this is if this is your only use case for the SparseBooleanArray
, you need not even worry about storing it yourself. You can retrieve it from the system which is already storing it across the orientation change.
That's what getListView().getCheckedItemPositions()
is good for.
回答3:
You could either invent some serialization scheme for that data structure or switch to a HashSet and store only the list index positions in it which are checked. Since HashSet is serializable you can just put it into the instance state Bundle.
回答4:
Extend SparseArray to implement a 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;
}
}
回答5:
I am trying to do the same thing. Initially i was using a HashMap
How to send hashmap value to another activity using an intent
to pass between activities since it extends the Serializable
interface. However, after doing some research:
"SparseBooleanArrays
are intended to be more efficient than using a HashMap
to map Integers
to Booleans
."
However, I can't find a simple way to store this data structure. I have contemplated grabbing the values of the SparseBooleanArray
and storing it in a HashMap
so that it can be passed between activities. But this seems to add more complexity.
Unfortunately, I think I am going to revert to using HashMaps to store my list of checked boxes
回答6:
Here is my solution. Just a simple wrapper that can be used to serialise a 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);
}
}
}
I then add the object to a bundle like so:
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
SparseBooleanArray sparseBooleanArray = getSparseBooleanArray();
SerializableSparseBooleanArrayContainer sparseBooleanArraySerializable = new SerializableSparseBooleanArrayContainer(sparseBooleanArray);
outState.putSerializable(SOME_BUNDLE_KEY, sparseBooleanArraySerializable);
}
回答7:
I tried the solution creating SerializableSparseArray
extending SparseArray
making it possible to put a SparseArray
into bundle via Bundle.putSerializable
call.
But I found I can't obtain the saved object from bundle in onRestoreInstanceState
. Digging into the issue I found that savedInstanceState.getSerializable(KEY_TO_STRING_SPARSE_ARRAY) == null
.
Then, trying to inspect savedInstanceState.get(KEY_TO_STRING_SPARSE_ARRAY)
and suprisingly got a SparseArray<String>
not SerializableSparseArray<String>
. Finally I'm using savedInstanceState.getSparseParcelableArray(KEY_TO_STRING_SPARSE_ARRAY)
to obtaining SparseArray
back from a bundle.
Then, using java reflection to save a SparseArray<String>
to bundle directly without extending with Serializable
or Parcelable
interface. It's a little bit dirty but I think you can make your own utility function hiding the following detail implementation.
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);
}
I've tested the code and it works on Android 4.4.4. And I'd like to know if it's safe to use the implementation in other SDK implementations.
回答8:
I would create a Parcelable
holder class, using the native methods from Parcel
in order to serialize/deserialize the 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];
}
};
}