Читання та запис масивів об’єктів, що підлягають парцеляції


79

У мене є такий клас, який читає та записує масив об’єктів з / на посилку:

class ClassABC extends Parcelable {
    MyClass[] mObjList;

    private void readFromParcel(Parcel in) {
        mObjList = (MyClass[]) in.readParcelableArray(
                com.myApp.MyClass.class.getClassLoader()));
    }

    public void writeToParcel(Parcel out, int arg1) {
        out.writeParcelableArray(mObjList, 0);
    }

    private ClassABC(Parcel in) {
        readFromParcel(in);
    }

    public int describeContents() {
        return 0;
    }

    public static final Parcelable.Creator<ClassABC> CREATOR =
            new Parcelable.Creator<ClassABC>() {

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

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

У наведеному вище коді я отримую ClassCastExceptionпри читанні readParcelableArray:

ПОМИЛКА / AndroidRuntime (5880): Причина: java.lang.ClassCastException: [Landroid.os.Parcelable;

Що не так у наведеному вище коді? Під час запису масиву об’єкта, чи слід спочатку перетворити масив на an ArrayList?

ОНОВЛЕННЯ:

Чи нормально перетворити масив Object на an ArrayListта додати його на посилку? Наприклад, під час написання:

    ArrayList<MyClass> tmpArrya = new ArrayList<MyClass>(mObjList.length);
    for (int loopIndex=0;loopIndex != mObjList.length;loopIndex++) {
        tmpArrya.add(mObjList[loopIndex]);
    }
    out.writeArray(tmpArrya.toArray());

Під час читання:

    final ArrayList<MyClass> tmpList = 
            in.readArrayList(com.myApp.MyClass.class.getClassLoader());
    mObjList= new MyClass[tmpList.size()];
    for (int loopIndex=0;loopIndex != tmpList.size();loopIndex++) {
        mObjList[loopIndex] = tmpList.get(loopIndex);
    }

Але зараз я отримую NullPointerException. Чи правильний наведений вище підхід? Чому це кидання NPE?


Який тип mConversationMemberList?
Четан

Я оновив запитання, воно типу MyClass [].
Користувач7723337

Відповіді:


163

Вам потрібно записати масив, використовуючи Parcel.writeTypedArray()метод, і прочитати його разом із Parcel.createTypedArray()методом, приблизно так:

MyClass[] mObjList;

public void writeToParcel(Parcel out) {
    out.writeTypedArray(mObjList, 0);
}

private void readFromParcel(Parcel in) {
    mObjList = in.createTypedArray(MyClass.CREATOR);
}

Причина, по якій вам не слід використовувати readParcelableArray()/ writeParcelableArray()методи, полягає в тому, що readParcelableArray()насправді створюється Parcelable[]результат. Це означає, що ви не можете передати результат методу MyClass[]. Натомість вам потрібно створити MyClassмасив тієї ж довжини, що і результат, і скопіювати кожен елемент із масиву результатів у MyClassмасив.

Parcelable[] parcelableArray =
        parcel.readParcelableArray(MyClass.class.getClassLoader());
MyClass[] resultArray = null;
if (parcelableArray != null) {
    resultArray = Arrays.copyOf(parcelableArray, parcelableArray.length, MyClass[].class);
}

1
Дякую, це зробило фокус! Це слід пояснити в документації, оскільки при іменуванні методу звучить як парцельований масив - це те, що слід писати, коли це явно більше коду.
Том,

Так, назва методу трохи заплутала. Коли я зіткнувся з цією проблемою, єдиним способом її вирішення було читання джерел Android, тому я вирішив написати цю відповідь.
Michael

3
До речі, висловлювання в межах if (parcelableArray != null) {...}можна спроститиresultArray = Arrays.copyOf(parcelableArray, parcelableArray.length, MyClass[].class);
CrimsonX

1
@Michael: Я думаю, це має бути "mObjList = new MyClass [розмір]; in.readTypedArray (mObjList, MyClass.CREATOR);" замість "mObjList = in.readTypedArray (новий MyClass [розмір], MyClass.CREATOR);", оскільки in.readTypedArray має тип повернення void, але в іншому випадку ваша публікація мені дуже допомогла
Linard Arquint

1
@LinardArquint, насправді зразок коду був не дуже хорошим. Вам слід використовувати in.createTypedArray()замість цього. Будь ласка, перевірте оновлений приклад.
Майкл

37

ПОМИЛКА / AndroidRuntime (5880): Причина: java.lang.ClassCastException: [Landroid.os.Parcelable;

Відповідно до API , метод readParcelableArray повертає масив Parcelable (Parcelable []), який не можна просто передати в масив MyClass (MyClass []).

Але тепер я отримую виняток Null Pointer.

Важко сказати точну причину без детального трасування стека винятків.


Припустимо, ви зробили MyClass реалізує Parcelable належним чином, ось як ми зазвичай робимо для серіалізації / десеріалізації масиву об’єктів, які можна парцелювати:

public class ClassABC implements Parcelable {

  private List<MyClass> mObjList; // MyClass should implement Parcelable properly

  // ==================== Parcelable ====================
  public int describeContents() {
    return 0;
  }

  public void writeToParcel(Parcel out, int flags) {
    out.writeList(mObjList);
  }

  private ClassABC(Parcel in) {
    mObjList = new ArrayList<MyClass>();
    in.readList(mObjList, getClass().getClassLoader());
   }

  public static final Parcelable.Creator<ClassABC> CREATOR = new Parcelable.Creator<ClassABC>() {
    public ClassABC createFromParcel(Parcel in) {
      return new ClassABC(in);
    }
    public ClassABC[] newArray(int size) {
      return new ClassABC[size];
    }
  };

}

Сподіваюся, це допомагає.

Ви також можете використовувати такі методи:

  public void writeToParcel(Parcel out, int flags) {
      out.writeTypedList(mObjList);
  }

  private ClassABC(Parcel in) {
      mObjList = new ArrayList<ClassABC>();
      in.readTypedList(mObjList, ClassABC.CREATOR);
  }

5
Чи не повинно бути MyClass.class.getClassLoader()замість getClass().getClassLoader()? Тому що, на мою думку, завантажувач класів використовується для пошуку класу Parceleable, який ми намагаємося прочитати.
Асвін Кумар,

саме я не хочу ClassABC, я хочу MyClass
Srneczek

11

У мене була подібна проблема, і я вирішив її таким чином. Я визначив допоміжний метод у MyClass для перетворення масиву Parcelable в масив об’єктів MyClass:

public static MyClass[] toMyObjects(Parcelable[] parcelables) {
    MyClass[] objects = new MyClass[parcelables.length];
    System.arraycopy(parcelables, 0, objects, 0, parcelables.length);
    return objects;
}

Кожного разу, коли мені потрібно прочитати парцельований масив об’єктів MyClass, наприклад, з наміру:

MyClass[] objects = MyClass.toMyObjects(getIntent().getParcelableArrayExtra("objects"));

EDIT : Ось оновлена ​​версія тієї самої функції, яку я використовую нещодавно, щоб уникнути попереджень компіляції:

public static MyClass[] toMyObjects(Parcelable[] parcelables) {
    if (parcelables == null)
        return null;
    return Arrays.copyOf(parcelables, parcelables.length, MyClass[].class);
}

Думаю, зараз я відповім на цю відповідь. Я вже пару разів повертався, щоб скопіювати цей магічний код: D
Суфіан

@Giorgio Barchiesi, в андроїд-студії з'являється попередження: джерело Parcelable, а місце призначення MyClass, але програма працює, як очікувалося. Як?
kaushik

@kaushik, дякую за ваш коментар, я відредагував свою відповідь. Будь ласка, перегляньте нову версію функції "toMyObject".
Джорджо Бархісі

5

Вам потрібно записати масив, використовуючи Parcel.writeTypedArray()метод, і прочитати його разом із Parcel.readTypedArray()методом, приблизно так:

MyClass[] mObjArray;

public void writeToParcel(Parcel out, int flags) {
    out.writeInt(mObjArray.length);
    out.writeTypedArray(mObjArray, flags);
}

protected MyClass(Parcel in) {
    int size = in.readInt();
    mObjArray = new MyClass[size];
    in.readTypedArray(mObjArray, MyClass.CREATOR);
}

Для списків ви можете зробити наступне:

  ArrayList<MyClass> mObjList;

  public void writeToParcel(Parcel out, int flags) {
      out.writeTypedList(mObjList);
  }

  protected MyClass(Parcel in) {
      mObjList = new ArrayList<>(); //non-null reference is required
      in.readTypedList(mObjList, MyClass.CREATOR);
  }

Чи можете ви показати код, якщо він MyClassє interface? Я не впевнений у тому, як поводитись MyClass.CREATORу цьому випадку.
відсутній

@isabsent якщо MyClassє інтерфейсом, то його реалізації повинні всі реалізовуватись Parcelableі мати свої власні CREATORs.
EpicPandaForce

@isabsent для вашої проблеми: у мене також є Setтип інтерфейсу ( дозвольмо назвати його MyInterface), який реалізує Parcelable, але конкретних CREATORs немає . Раніше я dest.writeTypedList(new ArrayList<>(mySet))писав і ArrayList<MyInterface> data = (ArrayList<MyInterface>) in.readArrayList(MyInterface.class.getClassLoader());читав Parcel. Недолік: є негарне попередження про неперевірений акторський склад.
Денні

Теоретично @heisenberg, якщо ваш клас правильно реалізує Parcelable та правильно обробляє підтипи, тоді кожен підтип має свого ТВОРЦЯ, і Android, що робить у цьому випадку, - це шукати поле CREATOR для фактичного класу за допомогою відображення.
EpicPandaForce

@EpicPandaForce так, кожен підтип мого інтерфейсу (який реалізує Parcelable) має свій власний CREATOR. Але мій Об’єкт, який розпаковують, тримає Set<MyInterface>(просто введений MyInterface). Це Setможе містити MyInterfaceA, MyInterfaceBі т.д. Поки мій набір повинен бути набраний на MyInterface, я не можу використовувати CREATOR, або я помиляюся? Може, у вас є приклад для мене для цього сценарію? Використання BaseClass для всього MyInterface з CREATORреалізацією допомогло б, але тому мені потрібен новий клас лише для включення цієї парцеляції ... Дякую!
Денні
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.