如何通过 Bundle 传递对象

121

我需要通过一个bundle传递对负责大部分处理的类的引用。

问题在于它与意图或上下文无关,并且具有大量非原始对象。如何将类打包成parcelable/serializable并将其传递给startActivityForResult


2
我需要通过一个bundle传递到执行大部分处理的类的引用 -- 为什么? - CommonsWare
1
我有一个对象(DataManager),它处理一个服务器并运行一些GUI的后端。每当建立新连接时,我希望用户能够启动一个新活动,列出ListView中所有活动连接,并让用户选择一个。然后,生成的数据将绑定到一个新的GUI上。这实际上只是一个后端皮肤选择器。 - ahodder
3
如果您需要在多个活动中处理同一对象实例,您可能需要考虑使用单例模式。这里有一个很好的教程链接 - sotrh
11个回答

160

你还可以使用Gson将对象转换为JSONObject并在bundle中传递。对我来说,这是我发现的最优雅的方法。我没有测试它对性能的影响。

在初始活动中

Intent activity = new Intent(MyActivity.this,NextActivity.class);
activity.putExtra("myObject", new Gson().toJson(myobject));
startActivity(activity);

在下一个活动中

String jsonMyObject;
Bundle extras = getIntent().getExtras();
if (extras != null) {
   jsonMyObject = extras.getString("myObject");
}
MyObject myObject = new Gson().fromJson(jsonMyObject, MyObject.class);

3
由于涉及在活动之间传递数据,因此它并不经常发生,对应用程序的整体性能影响不大。话虽如此,我怀疑对原帖中的DataManager进行序列化不会奏效,因为它似乎具有套接字连接和其他类似的类。 - britzl
4
谷歌建议使用这种替代方案而不是Serialize:请查看此文档页面的最后一节“推荐替代方案”。请注意,不要改变原来的意思,尽可能使翻译通俗易懂。 - Niki Romagnoli
3
仅作为警告,我一段时间以来一直在使用这种技术,但是你可以传递的字符串有一个内存限制,所以请确保你的数据不是太大。 - jiduvah
聪明的解决方案,向你致敬! - Rohit Mandiwal
这不是一个好主意,因为它使用了反射机制,对性能不利 =(请使用Parceleable替代。 - raphaelbgr
显示剩余2条评论

56

弄清楚该走哪条路不仅需要回答CommonsWare的关键问题“为什么”,还需要回答“要到哪里去?”的问题。

现实情况是,唯一可以通过包传输的是纯净数据——其他所有内容都基于对那些数据意义或指向的解释。你不能直接传递一个对象,但你可以做以下三件事之一:

1) 你可以将对象拆分为其组成数据,并且如果另一端具有相同类型对象的知识,则可以从序列化数据中组装出克隆体。这就是大多数常见类型通过包传递的方式。

2) 您可以传递一个不透明的句柄。如果您在相同的上下文中传递它(虽然可能会问为什么要这样做),那么它将是一个可以调用或引用的句柄。但是,如果您通过Binder将其传递到不同的上下文中,则它的字面值将是一个任意数字(实际上,这些任意数字从启动开始按顺序计数)。除了跟踪它之外,您什么也不能做,直到将其传回原始上下文,这将使Binder将其转换回原始句柄,使其再次有用。

3) 您可以传递一个魔法句柄,例如文件描述符或对某些操作系统/平台对象的引用,如果您设置了正确的标志,则Binder将为接收者创建指向相同资源的克隆体,实际上可以在另一端使用它。但是,这仅适用于非常少量的对象类型。

最有可能的情况是,您要么只是将类传递给另一端以便稍后返回给您,要么将其传递到可以从序列化组成数据中创建克隆体的上下文中……否则,您正在尝试做一些根本行不通的事情,需要重新考虑整个方法。


1
感谢您的回复。你是对的,我所需要做的就是将对象列表的引用传递给我的新活动。新活动将从列表中获取一些数据并显示一个可选择的ListView。在选择后,该活动将向主机活动返回一个结果(有关点击对象的一些数据)。如果我理解正确,我相信您的选项2最合适;我如何获得这个不透明的句柄? - ahodder
你的其他活动无法从不透明对象中提取任何数据以显示。你可能想要做的是创建并传递一些支持类型的代理对象,其中包含将要显示的信息的副本。 - Chris Stratton

22

2
它们也可以被序列化。 - Jeffrey Blattman
1
但是会大幅度降低性能10倍!!请查看此基准测试:http://www.developerphil.com/parcelable-vs-serializable/ - saiyancoder
2
+1 @Mati的评论,但是要将其放入上下文中,当应用于单个对象时,10x相当于1毫秒。因此可能并没有听起来那么糟糕。 - pinoyyid
1
同意。问题在于当你处理集合时,这是一个非常常见的用例,如果你从Rest API获取资源的话。但对于单个对象,不应该是什么显著的问题。无论如何,如果所有的样板代码都妨碍了你的工作,你可以尝试使用这个库,它会为你生成所有的代码:https://github.com/johncarl81/parceler。这是一个非常好的方法! - saiyancoder
损坏的链接:404(未找到) - Gallal

13

1
我尝试过那个方法,很快就意识到它是不切实际的。我认为大多数存储在传递类(线程)中的对象都不可序列化。:) 谢谢。 - ahodder

13
你可以使用全局应用程序状态。

更新:

自定义然后将其添加到AndroidManifest.xml中:

<application android:label="@string/app_name" android:debuggable="true" android:name=".CustomApplication"

然后在你的项目中有一个类,像这样:

package com.example;

import android.app.Application;

public class CustomApplication extends Application {
    public int someVariable = -1;
}

并且由于“它可以通过从任何Activity或Service中的getApplication()方法访问”,所以您可以像这样使用它:

CustomApplication application = (CustomApplication)getApplication();
application.someVariable = 123; 
希望这能有所帮助。

我相信你只需要继承Application类,然后就可以存储任何你喜欢的东西。你需要进行的xml更改在上面的链接中已经提到了。 - Mark Storer
9
作为一般的设计原则,避免使用全局变量是个好主意,除非真的需要。在这种情况下,有很好的替代方案。 - dhaag23
好的,我想我明白你的意思了。只需扩展Application并加入一个变量来保存需要传递的对象;我查看了参考页面,没有看到必要的XML更改。 - ahodder
我也想把这个写成答案。这绝对是其中一种方法。但请记住,除非您取消引用它们(或应用程序上下文被销毁),否则这些对象将留在内存中,并且可能会占用空间,当您不需要它们时。 - Igor Čordaš

11

可能的解决方案:

Bundle bundle = new Bundle();
bundle.putSerializable("key", new CustomObject());

自定义对象类 CustomObject:

class CustomObject implements Serializable{
 private SubCustomObject1 sc1;
 private SubCustomObject2 sc2;
}

子自定义对象:

class SubCustomObject1 implements Serializable{ }

class SubCustomObject2  implements Serializable{ }

7

通过使用 bundle.putByteArray,还有一种将对象通过 bundle 发送的方法。


示例代码

public class DataBean implements Serializable {
private Date currentTime;

public setDate() {
    currentTime = Calendar.getInstance().getTime();
 }

public Date getCurrentTime() {
    return currentTime;
 }
}

将DataBean的对象放入Bundle中:
class FirstClass{
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//Your code...

//When you want to start new Activity...
Intent dataIntent =new Intent(FirstClass.this, SecondClass.class);
            Bundle dataBundle=new Bundle();
            DataBean dataObj=new DataBean();
            dataObj.setDate();
            try {
                dataBundle.putByteArray("Obj_byte_array", object2Bytes(dataObj));

            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();

            }

            dataIntent.putExtras(dataBundle);

            startActivity(dataIntent);
}

将对象转换为字节数组

/**
 * Converting objects to byte arrays
 */
static public byte[] object2Bytes( Object o ) throws IOException {
      ByteArrayOutputStream baos = new ByteArrayOutputStream();
      ObjectOutputStream oos = new ObjectOutputStream( baos );
      oos.writeObject( o );
      return baos.toByteArray();
    }

从Bundle中获取对象:
class SecondClass{
DataBean dataBean;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//Your code...

//Get Info from Bundle...
    Bundle infoBundle=getIntent().getExtras();
    try {
        dataBean = (DataBean)bytes2Object(infoBundle.getByteArray("Obj_byte_array"));
    } catch (IOException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    } catch (ClassNotFoundException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
}

从字节数组中获取对象的方法:

/**
 * Converting byte arrays to objects
 */
static public Object bytes2Object( byte raw[] )
        throws IOException, ClassNotFoundException {
      ByteArrayInputStream bais = new ByteArrayInputStream( raw );
      ObjectInputStream ois = new ObjectInputStream( bais );
      Object o = ois.readObject();
      return o;
    }

希望这能对其他小伙伴有所帮助。

这段代码看起来很流畅和简单。但我感觉SDK为什么不提供像这样传递对象的功能还有其他原因。你能告诉我更多关于这个解决方案的信息吗? - Mario Lenci
3
完全不需要那么多代码!使用bundle.putSerializable(objectImplementingSerializable) - 这会在底层执行您在此处重新实现的操作... - Risadinha

4

1.一个非常直接且易于使用的例子,使对象实现Serializable接口以便进行传递。

class Object implements Serializable{
    String firstName;
   String lastName;
}

2.在bundle中传递对象

Bundle bundle = new Bundle();
Object Object = new Object();
bundle.putSerializable("object", object);

3. 从Bundle中作为Serializable传递的对象,然后将其转换为Object。

Object object = (Object) getArguments().getSerializable("object");

1
我在寻找一种传递Date对象的方法时遇到了这个问题。如答案中所建议的那样,在我的情况下,我使用了Bundle.putSerializable(),但对于原始帖子中描述的复杂的DataManager来说,这并不起作用。
我的建议是将DataManager绑定到Singleton范围并使用依赖注入,在需要的地方注入DataManager,这将给出与将该DataManager放入应用程序或使其成为Singleton相似的结果。你不仅可以获得增加的可测试性的好处,还可以获得更干净的代码,而不需要在类和活动之间传递所有的依赖关系代码。(Robo)Guice非常易于使用,新的Dagger框架看起来也很有前途。

1
好的,对于像日期这样的东西,你可以只传递长整型值。但是,其他部分听起来不错。谢谢。 - ahodder

0

使用Bundle传递对象的另一种简单方法:

  • 在类对象中,创建一个带有键的静态列表或其他数据结构
  • 当您创建对象时,使用键(例如创建对象时的长时间戳)将其放入列表/数据结构中
  • 创建静态方法static getObject(long key)以从列表中获取对象
  • 在Bundle中传递键,以便稍后可以从代码中的另一点获取对象

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接