在下一个活动中检索时,置于Intent extra中的LinkedList会重新转换为ArrayList

ani*_*van 26 android android-intent

我正在观察wrt传递可序列化数据作为intent extra的行为是非常奇怪的,我只想澄清是否有一些我不会错过的东西.

所以我试图做的是在ActivtyALinkedListintent我开始下一个活动而创建一个实例- ActivityB.

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

onCreateActivityB,我试图获取LinkedList额外如下-

LinkedList<Item> items = (LinkedList<Item>) getIntent()
                             .getSerializableExtra(AppConstants.KEY_ITEMS);
Run Code Online (Sandbox Code Playgroud)

在运行这个时,我ClassCastExceptionActivityB上面的一行中反复进入.基本上,例外说我收到了ArrayList.一旦我改变了上面的代码来接收一个代码ArrayList,一切都运行得很好.

现在我不能从现有文档中找出这是否是Android传递可序列化List实现时的预期行为.或者,或许,我正在做的事情存在根本性的错误.

谢谢.

Dav*_*ser 49

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

首先是一些背景信息:

一个Intent基本上是Android的附加功能,Bundle它基本上是HashMap键/值对.所以,当你做类似的事情

intent.putExtra(AppConstants.KEY_ITEMS, items);
Run Code Online (Sandbox Code Playgroud)

Android Bundle为附加组件创建一个新的,并Bundle在键所在的位置添加一个映射条目,AppConstants.KEY_ITEMS值为items(这是您的LinkedList对象).

这一切都很好,如果你在代码执行后查看extras包,你会发现它包含一个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);
Run Code Online (Sandbox Code Playgroud)

它为类型ArrayList中的所有对象生成一个.BundleList

你无法改变Android的这种行为.至少现在你知道为什么会这样做.

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

重要说明:因为List是一个接口,这意味着任何实现List你所放入的类的类Bundle都会以ArrayList.这也是有趣的是,Map也这意味着,不管是什么样的"已知对象类型"列表中Map的对象,你投入Bundle(例如TreeMap,SortedMap或实现的任何类Map接口),你总是会得到一个HashMap出它.

  • 我的,我的 - 我可以先说*是的,我真的不喜欢这个*.毕竟,"*已知对象类型*"的整个概念听起来像是在构建逻辑部分时想要偷工减料.而且,如果是这种情况,他们应该实际上要防止所有"*not-so-known*"对象类型实现`Serializable`,或者根本不支持`Serializable`. extras`.无论如何,我不能够感谢你,是的,我们对你所发现的东西更加聪明. (3认同)
  • 非常感谢你的回答.节省了我几个小时的调试时间. (3认同)
  • 谢谢大卫,这救了我......我和传递给我的片段的savedInstanceState有同样的问题 - 我无法弄清楚为什么Android坚持要给我一个ArrayList ... (2认同)

Ted*_*opp 5

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

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

LinkedList<String> items = new LinkedList<>(
    (List<String>) intent.getSerializableExtra(KEY));
Run Code Online (Sandbox Code Playgroud)

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

当涉及到 时Map,问题会更糟,因为您可能已经尝试序列化 aLinkedHashMap并且现在丢失了元素排序。

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

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

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

    public T get() {
        return wrapped;
    }
}
Run Code Online (Sandbox Code Playgroud)

然后你可以用它来隐藏自己ListMap或其他数据来自Android的类型检查类型:

intent.putExtra(KEY, new Wrapper<>(items));
Run Code Online (Sandbox Code Playgroud)

然后:

items = ((Wrapper<LinkedList<String>>) intent.getSerializableExtra(KEY)).get();
Run Code Online (Sandbox Code Playgroud)