Итак, перед нами стоит задача передать из одной activity в другую некий
List или Map <key, value>
. Казалось бы тривиальная задача? Ан нет.
Естественно, в случае, если у вас достаточно большой набор объектов, лучше использовать Parcelable.
На хабре и статья была на эту тему:
http://habrahabr.ru/post/174015/
А некоторые вообще предлагают отказаться от использования стандартного механизма Java Serialization, и не безосновательно, как вы в дальнейшем увидите:
https://twitter.com/JakeWharton/status/313422727149137920
Впрочем, я сторонник того, что при передаче небольшого количества информации через Intent
можно использовать стандартный механизм сериализации, потому что это дешевле с точки зрения разработки, чем писать самостоятельно упаковку через Parcelable
.
Давайте напишем немного кода. Пусть у нас есть ActivityA
и ActivityB
.
В ActivityA
напишем:
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.some_layout);
LinkedList <String> stringList = new LinkedList <String>();
stringList.add("one");
stringList.add("two");
stringList.add("three");
Intent intent = new Intent(this, ActivityB.class);
//Constants.STRINGS_LIST - любая строковая уникальная константа
intent.putExtra(Constants.STRINGS_LIST, stringList);
startActivity(intent);
}
В ActivityB
напишем:
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.some_layout);
LinkedList<String> stringList = (LinkedList<String>) getIntent()
.getSerializableExtra(Constants.STRINGS_LIST);
}
Вроде все правильно? Однако, IDE радостно рапортует o ClassCastException
и сообщает, что был получен ArrayList
.
Аналогично, если поменять ArrayList
, скажем, на TreeMap
:
ActivityA:
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.some_layout);
TreeMap<String, String> stringMap = new TreeMap <String, String>();
stringMap.put("1", "one");
stringMap.put("2", "two");
stringMap.put("3", "three");
Intent intent = new Intent(this, ActivityB.class);
//Constants.STRINGS_MAP - любая строковая уникальная константа
intent.putExtra(Constants.STRINGS_MAP, stringMap);
startActivity(intent);
}
ActivityB:
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.some_layout);
TreeMap<String, String> stringMap = (TreeMap<String, String> ) getIntent()
.getSerializableExtra(Constants.STRINGS_MAP);
}
В итоге вы получите тот же ClassCastException
, и сообщение, что, соответственно, был получен HashMap
.
В чем же причина такого поведения?
Extras
в Intent
- обычный HashMap <key, value>
.
Соответственно, Android кладет строковый параметр key
и ArrayList, TreeMap, etc
как value
в Bundle
. Дальше он должен сериализовать в байтовый поток, и десериализовать обратно эти объекты. Здесь происходит самое интересное, идет проверка во внутреннем списке типов, в котором есть Integer, Long, List, Map
... Как известно, последние 2 пункта - интерфейсы. Таким образом, если мы передаем объект любого типа, реализующего интерфейс List
, то на выходе получим LinkedList
, если Map - то HashMap
соответственно.
Возможные пути решения:
1. Иметь в виду и просто принимать объекты соответствующего типа (не очень удачно).
2. Отказаться от использования Intent.
Вместо этого можно использовать, например, сохранение в базу (долго).
3. Использовать кастомный объект с полем типа LinkedList
или TreeMap
(костыль).
4. Использовать Parcelable
(неудобство).
ps.
Возможно есть еще какие-нибудь подобные нестыковки? Например с concurrent-коллекциями? Или альтернативные пути решения проблемы? Пишите в комментариях.