Android 中的 Intent 和 Parcelable 对象

8

即使我只需要将对象发送到同一任务的另一个线程,为什么我仍然需要打包它?实际上,我需要打开一个活动,即使在同一线程(主线程)上也能运行。

换句话说,为什么Google没有提供一个版本的startActivity,它接受一个通用对象作为参数而不是一个bundle,让我可以启动一个活动,以防我知道它在同一进程或(大多数时候)甚至是同一线程(主线程)中?


你为什么对这个问题开了悬赏?你真的不指望有人回答问题中的“为什么谷歌没有……”部分,是吗?那么你想知道的是什么,而现有的答案又没有涵盖到呢? - David Wasser
1
我只是想确保我没有忘记任何可以解释sdk为什么会这样工作的东西。这个问题没有答案。我不是在谈论第二部分,那只是为了更清楚地表达。你说不需要它,因为你可以使用静态变量。至少在我的情况下,我不认为这是一个真正的解决方案,所以问题仍然存在。为什么我不能这样做? - user1175011
1
更普遍地说,当我使用某些东西时,我不太自信,但我不知道为什么它会这样工作。 - user1175011
我在我的回答中添加了一些更多的细节。也许这会有所帮助。如果不行,请具体指出您想要知道什么。 - David Wasser
5个回答

12

您不需要使用Parcelable将对象从一个活动传递到另一个活动。您可以将该对象的引用存储在静态成员变量中,就像这样:

public class Globals {
     public static MyObject myObject;
}

现在,在拥有该对象的代码中,您只需执行以下操作:

Globals.myObject = object;

在新的活动中,您可以像这样获取它:

doSomethingWith(Globals.myObject);
现在说到这里,你需要了解以下几点: Android可以随时在后台结束你的应用程序进程。当用户返回到你的应用程序时,Android将为你的应用程序创建一个新的进程,然后它将只重新创建在活动栈顶部的活动(即显示的活动)。在这种情况下,新创建的活动将无法通过访问Globals.myObject来获取对象,因为进程已经重新创建并且该成员变量为空。 为了解决这个问题,你可以采取以下两种方法之一: 1.检查Globals.myObject == null以确定你的进程是否被杀死并重新启动,并相应地做出反应-告诉用户他需要返回,或者自己返回,或者显示对话框或其他内容; 2.在你的活动中,当Android调用onSaveInstanceState()时保存对象(Android在将你的应用程序发送到后台之前会这样做),并在onCreate()中恢复对象。 希望这些能回答你的问题并解释如何处理这个问题。 编辑:添加有关为什么意图包含序列化(Parcelable)对象而不是对象本身的更多信息 1.当你调用startActivity()或startService()时,Android可能会在另一个进程中启动活动或服务。在这种情况下,如果你在Intent中传递了一个对象,Android需要对该对象进行序列化才能将其传递到另一个进程中。由于Android使用的“隐式Intent解析”来确定哪个组件将处理Intent,调用方可能知道或不知道哪个组件将被启动。 2.Android保存意图的内容有各种原因: A.Android可以随时终止进程。如果这样做并且用户想要返回到应用程序,则Android会创建一个新的进程,然后根据需要重新创建该进程中的活动。为了创建活动,Android还需要使意图可供活动使用。如果进程已被杀死,则Intents中的任何“对象”都必须被保存和恢复。因为意图包含序列化对象,所以可以根据需要重新创建这些对象,这不是问题。 B.PendingIntent是Android作为操作系统代理发送Intent的一种方式。Android组件可以创建PendingIntent,并将其提供给操作系统,以便在稍后的某个时间触发该Intent的发送。调用组件可能在PendingIntent实际发送时处于活动状态,也可能不处于活动状态。这意味着可以通过序列化传递在PendingIntent中传递的任何对象,以便Android即使没有调用组件也能保留它。 3.意图不是一个组件之间的通用“参数传递”机制。当然你可以这样使用它,但你也可以使用其他(更简单)的机制。在给定的进程内,你可以使用标准的Java机制传递对象。使用静态(类)变量进行此操作是没有问题的。

谢谢。我正在考虑这个解决方案。正如你所解释的,这总是有一些风险,不仅因为你描述的风险,还因为我需要小心地打开两个具有不同对象的相同活动。 - user1175011
你几乎完全可以控制打开多个活动的实例,所以我不会担心这个问题。只要你明白自己在做什么就好了。事实上,我认为这通常是“从一个活动传递对象到另一个活动”的更好方式,因为你不需要对它们进行序列化/反序列化操作。只要你理解后果,这就不是问题。 - David Wasser
实际上我并不知道。如果该活动未定义为singleTask或singleInstance,则可以打开多个实例,在这种情况下,这种解决方案并不是最容易实现的。 - user1175011
如果您的应用程序不是由其他应用程序启动的,则不需要使用singleTasksingleInstance。如果必要,您可以使用singleTop来控制其余部分,但我仍然认为您可以使用这种机制来实现可靠性较高且开销较低的操作。 - David Wasser
1
如果您的活动被销毁(由于屏幕方向改变)或者您的进程被终止(以释放内存),然后用户返回到您的应用程序,活动将会被重新创建,并且从onSaveInstanceState()返回的Bundle将传递给onCreate()方法。这是正确的。但是...如果您的应用程序调用getIntent(),原始的Intent也将可用。 - David Wasser
显示剩余3条评论

4

Android官方文档(FAQ部分)提供了很多关于如何传递复杂数据结构的信息。

如何在单个应用程序中的活动和服务之间传递数据? http://developer.android.com/guide/faq/framework.html#3

Primitive Data Types

To share primitive data between Activities/Services in an application, use Intent.putExtras(). For passing primitive data that needs to persist use the Preferences storage mechanism. Non-Persistent Objects

For sharing complex non-persistent user-defined objects for short duration, the following approaches are recommended:

Singleton class

You can take advantage of the fact that your application components run in the same process through the use of a singleton. This is a class that is designed to have only one instance. It has a static method with a name such as getInstance() that returns the instance; the first time this method is called, it creates the global instance. Because all callers get the same instance, they can use this as a point of interaction. For example activity A may retrieve the instance and call setValue(3); later activity B may retrieve the instance and call getValue() to retrieve the last set value.

A public static field/method

An alternate way to make data accessible across Activities/Services is to use public static fields and/or methods. You can access these static fields from any other class in your application. To share an object, the activity which creates your object sets a static field to point to this object and any other activity that wants to use this object just accesses this static field.

A HashMap of WeakReferences to Objects

You can also use a HashMap of WeakReferences to Objects with Long keys. When an activity wants to pass an object to another activity, it simply puts the object in the map and sends the key (which is a unique Long based on a counter or time stamp) to the recipient activity via intent extras. The recipient activity retrieves the object using this key.

Persistent Objects

Even while an application appears to continue running, the system may choose to kill its process and restart it later. If you have data that you need to persist from one activity invocation to the next, you need to represent that data as state that gets saved by an activity when it is informed that it might go away.

For sharing complex persistent user-defined objects, the following approaches are recommended:

- Application Preferences
- Files
- contentProviders
- SQLite DB

If the shared data needs to be retained across points where the application process can be killed, then place that data in persistent storage like Application Preferences, SQLite DB, Files or ContentProviders. Please refer to the Data Storage for further details on how to use these components.

为什么需要这样做:

由于Android具有多任务处理的独特能力和组件生命周期的概念。为了让用户离开应用程序(例如拥有活动A、B、C以及意图i1、i2、i3),系统将应用程序分成组件

这样,如果用户使用意图i2启动Activity B,但接到电话或检查电子邮件,他们可以回到Activity B并且Android系统可以重新传递意图i2。用户可以轻松无缝地在您的应用程序中恢复之前的操作。

使用组件的生命周期可以使每个单独组件的进入和退出更加容易。它还允许不同类型的组件之间进行异步通信,例如BroadcastReceiversServicesActivities

为了实现这一点,操作系统执行所谓的“marshaling”。它将数据压缩为基本数据类型(如intchar),以便可以轻松地保存在内存中或写入临时存储以供以后检索。

此外,任何类型的IPC(进程间通信)都需要这样做。

使用Intent允许开发人员让Android操作系统执行marshaling,这通常是繁琐的(可能也很难或出现错误)。


2
Android要求任何你在不同任务、活动、线程或服务中传递的对象都必须被压缩成一个Parcel,或者实现Serializable接口。这是为了让你能够通过值来传递对象。当你向方法或函数传递对象时,你通过引用来传递。意图被传递到系统中,系统会决定如何路由你的意图,无论是启动你的一个活动还是打开另一个应用。

如果接收对象不是你的应用程序的一部分,那么接收方将无法访问你的应用程序的内存。因此,通过传递一个对象引用会导致接收应用程序崩溃。通过将对象压缩成一个Parcel或Serializable blob,接收方可以在没有访问原始引用的情况下对发送的值进行操作。如果Google实现了一个可以传递基本对象的设施,他们将需要为所有对象编写深层拷贝函数,并且你将需要为所有自定义对象编写深层拷贝函数,这将变得非常混乱、缓慢并占用相当大的系统内存,可能会导致VM溢出。通过提供一个通用的基本传递对象的设施,它们能够加快性能、减少内存需求,并确保安全性,不允许其他应用程序引用你的应用程序内存。

然而,正如前面所提到的,你可以为想要在活动之间传递的对象创建全局引用,只要它们都是你的应用程序的一部分。这并不是最优雅的解决方案,需要在完成后将引用设置为空,以便不占用不必要的内存,但它足够好用。


1

所有的活动都只能在UI线程上创建运行。没有其他方法。关于您关于需要打包对象的问题,实际上这不是唯一的方法。您还可以通过使对象实现Serializable接口来传递对象。

在高层次上,Intent是一种异步消息传递机制,用于在不同组件之间进行通信,例如:活动、服务和广播接收器。源组件和目标组件可能属于同一个应用程序(因此进程)也可能不属于同一个应用程序,如果需要,在Android框架中需要一种标准化的方法来跨进程传递对象。


抱歉,那是一个打字错误。第二部分不是一个问题。 - user1175011

-3
为了在活动/服务等之间使用 BundleIntent 传递自定义类对象,您的类必须实现 ParcelableSerializable 接口。

这就是为什么您需要在将对象放入 Bundle 前对其进行打包的原因。


抱歉,但这并不是真正的答案。我问为什么需要这样做,你回答说“因为你需要这样做”。我知道如果不能假设目标将在同一个任务上运行,则必须对对象进行序列化,但问题明确是关于当我知道接收方将在同一任务上运行的情况。 - user1175011
你是什么意思说“接收器将在同一任务上运行”?如果你想通过Intent传递自己的对象,那么你的类应该正确实现Parcelable。由于这是要求,所以你必须遵循它。Parcelable实际上序列化了你的对象,以便可以在另一端发送和重建。更多信息请阅读此处 - waqaslam
但是你一直回答我必须这样做,因为这是必需的。但问题是为什么需要这样做。 - user1175011
为什么需要使用Parcelable?因为你需要通过意图传递对象,而要通过意图传递对象,你的类必须实现Parcelable接口。如果你还不理解这个简单的逻辑,那我很抱歉。也许你可以仔细阅读一些谷歌文档 :) - waqaslam
看起来我没有成功地让问题清晰明了。如果有人明白为什么这不是一个答案,能否帮助我解释一下为什么呢?提前感谢您。 - user1175011

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