【问题标题】:LinkedList put into Intent extra gets recast to ArrayList when retrieving in next activity在下一个活动中检索时,放入 Intent extra 的 LinkedList 被重铸为 ArrayList
【发布时间】:2012-09-06 13:10:56
【问题描述】:

我观察到的 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);

ActivityBonCreate 中,我尝试检索LinkedList 额外内容,如下所示 -

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

在运行此程序时,我在ActivityB 中反复得到ClassCastException,在上面的行中。基本上,异常说我收到了ArrayList。一旦我将上面的代码更改为接收ArrayList,一切正常。

现在我不能只从现有文档中弄清楚这是否是 Android 在传递可序列化列表实现时的预期行为。或者,我正在做的事情根本上是错误的。

谢谢。

【问题讨论】:

  • 但是LinkedList 发生这种行为有什么特别的原因吗,而如果我在intent 上添加一个ArrayList 实例作为额外数据,一切都会好起来的。而且,我不需要使用Parcelable?
  • +1 有趣的问题。我花了一些时间思考这个问题,并且很感兴趣,所以我自己去想办法。现在你和我都更聪明了(见我的回答)。
  • @anirvan 你不能以你的方式传递ArrayList&lt;CustomObject&gt;。只允许 ArrayList 的原始类型为 StringInteger
  • @LalitPoptani 你错了。只要CustomObject 实现Serializable 就没有错。而且效果很好。
  • @anirvan 是的,你是对的,但你必须实现 Serializable,否则我将无法实现。

标签: android android-intent


【解决方案1】:

我可以告诉你为什么会这样,但你不会喜欢它;-)

首先介绍一下背景信息:

Intent 中的

Extras 基本上是一个 Android Bundle,它基本上是一个键/值对的 HashMap。所以当你做类似的事情时

intent.putExtra(AppConstants.KEY_ITEMS, items);

Android 为 extras 创建一个新的 Bundle 并向 Bundle 添加一个映射条目,其中键是 AppConstants.KEY_ITEMS,值是 items(这是您的 LinkedList 对象)。

这一切都很好,如果您在代码执行后查看附加包,您会发现它包含一个LinkedList。现在是有趣的部分......

当您使用包含 extras 的 Intent 调用 startActivity() 时,Android 需要将 extras 从键/值对映射转换为字节流。基本上它需要序列化Bundle。它需要这样做,因为它可能会在另一个进程中启动活动,并且为了做到这一点,它需要序列化/反序列化 Bundle 中的对象,以便它可以在新进程中重新创建它们。它也需要这样做,因为 Android 将 Intent 的内容保存在一些系统表中,以便以后需要时可以重新生成 Intent。

为了将Bundle 序列化为字节流,它会遍历包中的映射并获取每个键/值对。然后它获取每个“值”(这是某种对象)并尝试确定它是哪种对象,以便它可以以最有效的方式对其进行序列化。为此,它会根据已知对象类型列表检查对象类型。 “已知对象类型”列表包含 IntegerLongStringMapBundle 之类的内容,不幸的是还有 List。因此,如果对象是List(其中有许多不同的类型,包括LinkedList),它会将其序列化并将其标记为List 类型的对象。

Bundle被反序列化时,即:当你这样做时:

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

它为Bundle 类型为List 的所有对象生成ArrayList

实际上,您无法做任何事情来改变 Android 的这种行为。至少现在你知道它为什么会这样了。

只是为了让您知道:我实际上编写了一个小测试程序来验证这种行为,并且我查看了 Parcel.writeValue(Object v) 的源代码,这是在将地图转换为地图时从 Bundle 调用的方法字节流。

重要提示:由于List 是一个接口,这意味着任何实现List 的类都将作为Bundle 输出。 同样有趣的是,Map 也在“已知对象类型”列表中,这意味着无论您将哪种类型的Map 对象放入Bundle(例如TreeMapSortedMap 或任何实现了Map 接口的类),你总会得到一个HashMap

【讨论】:

  • 天啊,天啊 - 我可以先说是的,我真的不喜欢这个。毕竟,“已知对象类型”的整个概念听起来像是有人想在构建这部分逻辑时偷工减料。而且,如果是这种情况,他们实际上应该防止所有“not-so-known”对象类型实现Serializable,或者不支持Serializable完全在意图extras 之内。无论如何,我非常感谢你,是的,对于你的发现,我们更聪明。
  • 非常感谢您的回答。节省了我数小时的调试时间。
  • 谢谢大卫,这救了我……我在传递给我的片段时遇到了同样的问题 - 我不明白为什么 Android 坚持要给我一个 ArrayList……跨度>
【解决方案2】:

@David Wasser 的答案在诊断问题方面是正确的。这篇文章是为了分享我是如何处理的。

任何List 对象作为ArrayList 出现的问题并不可怕,因为你总是可以做类似的事情

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

这会将反序列化列表的所有元素添加到新的LinkedList

Map 的问题要严重得多,因为您可能曾尝试序列化 LinkedHashMap,但现在丢失了元素排序。

幸运的是,有一种(相对)轻松的方法来解决这个问题:定义您自己的可序列化包装类。您可以针对特定类型执行此操作,也可以通用执行此操作:

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

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

    public T get() {
        return wrapped;
    }
}

然后您可以使用它来隐藏您的 ListMap 或其他数据类型以防止 Android 的类型检查:

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

及以后:

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

【讨论】:

  • 你的意思可能是public class Wrapper &lt;T extends Serializable&gt; implements Serializable
  • @SergioSerra - 确实。谢谢你的收获。
【解决方案3】:

如果您正在使用 IcePick 库并且遇到此问题,您可以将 Ted Hoop 的技术与自定义捆绑器一起使用,以避免在您的代码中处理 Wrapper 实例。

public class LinkedHashmapBundler implements Bundler<LinkedHashMap> {

 @Override
 public void put(String s, LinkedHashMap val, Bundle bundle) {
    bundle.putSerializable(s, new Wrapper<>(val));
 }

 @SuppressWarnings("unchecked")
 @Override
 public LinkedHashMap get(String s, Bundle bundle) {
   return ((Wrapper<LinkedHashMap>) bundle.getSerializable(s)).get();
 }
} 

// Use it like this
@State(LinkedHashmapBundler.class) LinkedHasMap map

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-02-24
    • 1970-01-01
    • 2012-11-16
    • 2018-04-19
    • 1970-01-01
    相关资源
    最近更新 更多