Подтвердить что ты не робот

LinkedList, помещенный в Intent extra, перерабатывается в ArrayList при получении в следующем действии

Поведение, которое я наблюдаю за w.r.t, передавая сериализуемые данные как дополнительные, довольно странно, и я просто хотел уточнить, есть ли что-то, чего я не пропускаю.

Итак, я пытаюсь сделать это в том, что в ActivtyA я помещал экземпляр LinkedList в созданный intent для запуска следующего действия - ActivityB.

LinkedList<Item> items = (some operation);
Intent intent = new Intent(this, ActivityB.class);
intent.putExtra(AppConstants.KEY_ITEMS, items);

В onCreate из ActivityB я попытался извлечь дополнительный LinkedList следующим образом -

LinkedList<Item> items = (LinkedList<Item>) getIntent()
                             .getSerializableExtra(AppConstants.KEY_ITEMS);

При запуске этого процесса я снова получил ClassCastException в ActivityB в строке выше. В принципе, исключение говорит, что я получил ArrayList. Как только я изменил код выше, чтобы получить ArrayList, все работало нормально.

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

Спасибо.

4b9b3361

Ответ 1

Я могу сказать вам, почему это происходит, но вам это не понравится; -)

Сначала немного справочной информации:

Дополнительно Intent - это в основном Android Bundle, который в основном представляет собой HashMap пары ключ/значение. Поэтому, когда вы делаете что-то вроде

intent.putExtra(AppConstants.KEY_ITEMS, items);

Android создает новый Bundle для дополнительных функций и добавляет запись карты в Bundle, где ключ AppConstants.KEY_ITEMS, а значение элементов (это ваш объект LinkedList).

Все это прекрасно и хорошо, и если вы посмотрите на пакет дополнительных компонентов после выполнения кода, вы обнаружите, что он содержит LinkedList. Теперь идет интересная часть...

Когда вы вызываете startActivity() с содержащим дополняющие Intent, Android необходимо преобразовать дополнительные данные из карты пар ключ/значение в поток байтов. В основном это необходимо для сериализации Bundle. Он должен сделать это, потому что он может начать работу в другом процессе, и для этого ему необходимо сериализовать/десериализовать объекты в Bundle, чтобы он мог воссоздать их в новом процессе. Это также необходимо сделать, потому что Android сохраняет содержимое Intent в некоторых системных таблицах, чтобы он мог восстановить Intent, если это необходимо позже.

Чтобы сериализовать Bundle в поток байтов, он проходит через карту в комплекте и получает каждую пару ключ/значение. Затем он берет каждое "значение" (которое является каким-то объектом) и пытается определить, какой именно объект он имеет, чтобы он мог сериализовать его наиболее эффективным способом. Для этого он проверяет тип объекта на список известных типов объектов. Список "известных типов объектов" содержит такие вещи, как Integer, Long, String, Map, Bundle и, к сожалению, также List. Поэтому, если объект является List (из которого существует много разных типов, включая LinkedList), он сериализует его и помечает как объект типа List.

Когда дескриптор Bundle десериализуется, т.е. когда вы это делаете:

LinkedList<Item> items = (LinkedList<Item>)
        getIntent().getSerializableExtra(AppConstants.KEY_ITEMS);

он создает ArrayList для всех объектов в Bundle типа List.

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

Просто чтобы вы знали: я действительно написал небольшую тестовую программу для проверки этого поведения, и я посмотрел исходный код для Parcel.writeValue(Object v), который является методом, который вызывается из Bundle, когда он преобразует карту в байтовый поток.

Важное примечание: Так как List является интерфейсом, это означает, что любой класс, реализующий List, который вы помещаете в Bundle, выдается как ArrayList. Интересно также, что Map также входит в список "известных типов объектов", что означает, что независимо от того, какой объект Map вы помещаете в Bundle (например, TreeMap, SortedMap, или любой класс, реализующий интерфейс Map), вы всегда получите HashMap.

Ответ 2

Ответ @David Wasser находится на стадии диагностики проблемы. Это сообщение, чтобы поделиться тем, как я его обрабатывал.

Проблема с любым объектом List, выходящим как ArrayList, не ужасна, потому что вы всегда можете сделать что-то вроде

LinkedList<String> items = new LinkedList<>(
    (List<String>) intent.getSerializableExtra(KEY));

который добавит все элементы десериализованного списка в новый LinkedList.

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

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

public class <T extends Serializable> Wrapper implements Serializable {
    private T wrapped;

    public Wrapper(T wrapped) {
        this.wrapped = wrapped;
    }

    public T get() {
        return wrapped;
    }
}

Затем вы можете использовать это, чтобы скрыть свой List, Map или другой тип данных от проверки типа Android:

intent.putExtra(KEY, new Wrapper<>(items));

и позже:

items = ((Wrapper<LinkedList<String>>) intent.getSerializableExtra(KEY)).get();