在活动之间共享数据的最佳方法是什么?


239

我有一个活动,这是整个应用程序中使用的主要活动,它具有许多变量。我还有另外两个活动,我希望能够使用第一个活动中的数据。现在我知道我可以做这样的事情:

GlobalState gs = (GlobalState) getApplication();
String s = gs.getTestMe();

但是我想共享很多变量,有些可能很大,所以我不想像上面那样创建它们的副本。

有没有一种方法可以直接获取和更改变量而无需使用get和set方法?我记得在Google开发人员网站上读过一篇文章,建议不要在Android上使用此功能。


2
从Android 2.3(Gingerbread)开始,Dalvik自动执行get / set的优化;仅当您针对较旧版本的Android时才有意义。
StellarVortex 2014年

请注意,该示例不复制字符串数据。而是创建对相同字符串对象的引用。
法学徒

很难相信,为什么不可能从另一个活动开始一个活动并将任何复杂的对象从第一个传递到第二个?如果不进行序列化,则可以节省对象和所有工作。如果两个活动都在同一个应用程序中,这是安全漏洞还是有其他原因阻止简单地传递对象引用?(我知道它们在不同的应用程序中会有所不同)
Droidum


LiveData是最好的最新解决方案。在下面检查我的答案。
阿米尔·乌瓦尔

Answers:


476

这里是实现此目的最常用方法的汇编:

  • 在意图内发送数据
  • 静态场
  • 的HashMap WeakReferences
  • 持久对象(sqlite,共享首选项,文件等)

TL; DR:有两种共享数据的方式:在Intent的Extras中传递数据或将其保存在其他地方。如果数据是基元,字符串或用户定义的对象:请将其作为附加意图的一部分发送(用户定义的对象必须实现Parcelable)。如果传递复杂对象,则将实例保存在其他位置的一个实例中,然后从启动的活动中访问它们。

有关如何以及为何实施每种方法的一些示例:

在意图内发送数据

Intent intent = new Intent(FirstActivity.this, SecondActivity.class);
intent.putExtra("some_key", value);
intent.putExtra("some_other_key", "a value");
startActivity(intent);

在第二项活动中:

Bundle bundle = getIntent().getExtras();
int value = bundle.getInt("some_key");
String value2 = bundle.getString("some_other_key");

如果要传递原始数据或String,请使用此方法。您还可以传递实现的对象Serializable

尽管很诱人,但在使用之前您应该三思而后行Serializable:它容易出错并且非常慢。因此,一般而言:尽可能远离Serializable。如果要传递复杂的用户定义对象,请查看Parcelableinterface。实施起来比较困难,但是与相比,它具有可观的速度提升Serializable

共享数据而不持久化到磁盘

考虑到大多数情况下两个活动都在同一进程中运行,可以通过将活动保存在内存中来共享活动之间的数据。

注意:有时候,当用户离开您的活动(不退出活动)时,Android可能会决定终止您的应用程序。在这种情况下,我遇到过一些案例,其中android尝试使用应用程序被杀死之前提供的意图启动最后一个活动。在这种情况下,存储在单例(您的或Application)中的数据将丢失,并且可能会发生不良情况。为避免这种情况,您可以将对象持久保存到磁盘上,或者在使用数据以确保其有效之前检查数据。

使用单例课程

有一个类来保存数据:

public class DataHolder {
  private String data;
  public String getData() {return data;}
  public void setData(String data) {this.data = data;}

  private static final DataHolder holder = new DataHolder();
  public static DataHolder getInstance() {return holder;}
}

从启动的活动中:

String data = DataHolder.getInstance().getData();

使用应用程序单例

应用程序单例android.app.Application是启动应用程序时创建的实例。您可以通过扩展来提供自定义的Application

import android.app.Application;
public class MyApplication extends Application {
  private String data;
  public String getData() {return data;}
  public void setData(String data) {this.data = data;}
}

在启动活动之前:

MyApplication app = (MyApplication) getApplicationContext();
app.setData(someData);

然后,从启动的活动中:

MyApplication app = (MyApplication) getApplicationContext();
String data = app.getData();

静态场

这个想法与单例基本相同,但是在这种情况下,您提供对数据的静态访问:

public class DataHolder {
  private static String data;
  public static String getData() {return data;}
  public static void setData(String data) {DataHolder.data = data;}
}

从启动的活动中:

String data = DataHolder.getData();

的HashMap WeakReferences

同样的想法,但是允许垃圾收集器删除未引用的对象(例如,当用户退出活动时):

public class DataHolder {
  Map<String, WeakReference<Object>> data = new HashMap<String, WeakReference<Object>>();

  void save(String id, Object object) {
    data.put(id, new WeakReference<Object>(object));
  }

  Object retrieve(String id) {
    WeakReference<Object> objectWeakReference = data.get(id);
    return objectWeakReference.get();
  }
}

在启动活动之前:

DataHolder.getInstance().save(someId, someObject);

从启动的活动中:

DataHolder.getInstance().retrieve(someId);

您可能需要也可能不必使用意图的附加对象传递对象ID。这完全取决于您的特定问题。

将对象持久保存到磁盘

这个想法是在启动其他活动之前将数据保存在磁盘中。

优点:您可以从其他地方启动活动,如果数据已经保存,则应该可以正常工作。

缺点:它很麻烦并且需要更多的时间来实施。需要更多代码,因此有更多引入错误的机会。它也会慢很多。

持久化对象的一些方法包括:


11
我认为这不是更大/更复杂数据的“正常”方式。使用静态单例或Application对象要容易得多,并且效果很好。既然说OP在示例中确实使用了String,那么Intent是完美的并且是首选的。
查理·柯林斯

10
发现Serializable在Android流程模型上存在严重的性能问题。这就是为什么他们引入了Parcelable。阅读以上回答中的Parcelable而不是Serializable
Subin Sebastian 2012年

3
通过该setResult方法完成。同样,在这种情况下,必须使用startActivityForResult方法来调用辅助活动。
克里斯蒂安

2
很棒的总结!关于单例被销毁的问题,对于具有大量活动和对象的应用程序有一个简单的解决方案:使用Activity贯穿的子类,并在其onCreate()检查启动应用程序时填充的单例的任何静态字段。如果该字段为空,请使用FLAG_ACTIVITY_CLEAR_TASK或返回到开始活动,BroadcastReceiver以终止其他活动。
Janosch 2014年

1
我不建议将数据保存在应用程序类中。在此处阅读更多信息:developerphil.com/dont-store-data-in-the-application-object
Shayan_Aryan

22

您可以使用什么:

  1. 在活动之间传递数据(如克里斯蒂安所说)
  2. 使用带有大量静态变量的类(因此您可以在不使用类实例且不使用getter / setter的情况下调用它们)
  3. 使用数据库
  4. 共享首选项

选择什么取决于您的需求。当您拥有“很多”时,可能会使用多种方法


1
请注意,流程死亡
后将

@EpicPandaForce当然还有关闭设备的时间。
WarrenFaith

1
但是,如果设备已关闭,则应用将从MAIN操作中重新启动。进程终止后,您将重新启动最后打开的任何活动,这可能是应用程序深处的详细信息页面。
EpicPandaForce

@EpicPandaForce似乎您错过了我的讽刺意味,即使您是Cpt显而易见的。
WarrenFaith

16

谷歌命令你做什么!此处:http : //developer.android.com/resources/faq/framework.html#3

  • 原始数据类型
  • 非持久对象
  • 单身人士班-我最喜欢的:D
  • 公共静态字段/方法
  • 对对象的弱引用的HashMap
  • 持久对象(应用程序首选项,文件,contentProviders,SQLite DB)


仅反模式,单例类是第一个设计模式,具有静态字段/方法的类的行为与单例类似,并且使数据库用于持久化busniss对象有时不是一件好事,当然这不是您的错,并且您可能会列出实现它的方法,但是我不知道为什么Google会复杂到如此愚蠢的事情,性能问题还是什么?还是我不了解Android方式?
La VloZ Merrill's

链接已断开:(
Shayan_Aryan '16

14

“但是我想共享很多变量,有些可能很大,所以我不想像上面那样创建它们的副本。”

那不会产生拷贝(特别是使用String,但是即使对象都是通过引用的值传递的,而不是对象本身,并且getter这样的用法很好用-可以说比其他方法好用,因为它们很常见并且非常明白)。较早的“性能神话”(例如不使用getter和setter)仍然具有一定的价值,但在文档中也进行了更新

但是,如果您不想这样做,也可以在GlobalState中将变量设为公共或受保护的,然后直接访问它们。并且,您可以使静态单例成为Application对象JavaDoc所指示的

通常不需要子类化Application。在大多数情况下,静态单例可以以更模块化的方式提供相同的功能。如果您的单身人士需要全局上下文(例如,注册广播接收者),则可以在首次构造单身人士时为上下文提供一个上下文,该上下文在内部使用Context.getApplicationContext()。

如其他答案所述,使用Intent数据是传递数据的另一种方法,但通常用于较小的数据和简单类型。您可以传递更大/更复杂的数据,但它不仅涉及静态单例,而且涉及更多的内容。该应用我仍然偏爱对象,因为它在Android应用程序组件之间共享更大/更复杂的非持久数据(因为它在Android应用程序中具有明确的生命周期)。

另外,正如其他人指出的那样,如果数据变得非常复杂并且需要持久化,那么您也可以使用SQLite或文件系统。


1
实际上,我最近在文档中偶然发现了这一点:developer.android.com/guide/appendix/faq/framework.html#3。对于“非持久复杂对象”,建议使用Application类创建并拆除静态单例!这样,您将获得应用程序提供的明确定义的生命周期,并易于使用静态单例。
查理·柯林斯

2
常见问题的那一部分似乎现在已被删除(我只看到“非持久对象”,而没有提到Application类)。无论如何你可以详细说明吗?
东尼·陈

1
当前位于developer.android.com/guide/faq/framework.html#3 “如何在单个应用程序内的活动/服务之间传递数据?”,并且未提及Application类。
2014年

我喜欢按照您在“ Android in Action”中设置的先例使用Application对象。但是,许多准雇主在代码挑战中看到这一点时就不喜欢它。他们可能是错的,但他们却费劲。顺便说一句:'framework.html#3链接不再起作用。
Matt J.17年


4

有一种新的更好的活动之间共享数据的方法,那就是LiveData。请特别注意Android开发人员页面上的以下引用:

LiveData对象具有生命周期感知这一事实意味着您可以在多个活动,片段和服务之间共享它们。为了使示例简单,可以将LiveData类作为单例实现

这意味着巨大的隐患-任何模型数据都可以在LiveData包装器内的公共单例类中共享。ViewModel为了可测试性,可以将其从活动中注入到它们各自的活动中。而且您不再需要担心弱引用以防止内存泄漏。


2

使用上文和http://developer.android.com/guide/faq/framework.html中所述的弱引用方法的哈希图对我来说似乎是个问题。如何回收整个条目,而不仅仅是地图值?您在什么范围内分配它?由于该框架控制着Activity的生命周期,拥有一个参与的Activity会在所有者在其客户之前被销毁时冒着运行时错误的风险。如果应用程序拥有它,则某些活动必须显式删除该条目,以避免哈希映射保留具有有效键和可能已被垃圾回收的弱引用的条目。此外,当键的返回值为null时,客户端应该怎么做?

在我看来,由应用程序拥有或位于单个实例内的WeakHashMap是更好的选择。通过键对象访问映射中的值,并且当不存在对该键的强引用时(即,所有“活动”均使用该键及其映射的对象完成),GC可以回收该映射条目。


2

活动之间有多种共享数据的方式

1:使用Intent在活动之间传递数据

Intent intent=new Intent(this, desirableActivity.class);
intent.putExtra("KEY", "Value");
startActivity(intent)

2:使用static关键字,将变量定义为public static并在项目中的任何位置使用

      public static int sInitialValue=0;

使用classname.variableName在项目中的任何地方使用;

3:使用数据库

但过程冗长,您必须使用查询来插入数据,并在需要时使用游标迭代数据。但是,如果不清理缓存,就不会丢失数据。

4:使用共享的首选项

比数据库容易得多。但是有一些限制,您不能保存ArrayList,List和custome对象。

5:在Aplication类中创建getter setter并访问项目中的任何位置。

      private String data;
      public String getData() {
          return data;
      }

      public void setData(String data) {
          this.data = data;
      }

从这里开始活动

         ((YourApplicationClass)getApplicationContext()).setData("abc"); 

         String data=((YourApplicationClass)getApplicationContext()).getData();  

1

好吧,我有一些想法,但是我不知道它们是否是您想要的。

您可以使用包含所有数据的服务,然后将活动绑定到该服务以进行数据检索。

或将数据打包为可序列化或可打包的数据,并将其附加到捆绑包中,然后在活动之间传递捆绑包。

这个可能根本不是您想要的东西,但是您也可以尝试使用SharedPreferences或一般的首选项。

不管哪种方式,我都会知道您的决定。



1

如果您打算从当前活动中调用其他活动,则应使用“ 意图”。您关注的重点可能不是持久数据,而是根据需要共享数据。

但是,如果您确实需要保留这些值,则可以将它们保留在本地存储中的某种结构化文本文件或数据库中。属性文件,XML文件或JSON文件可以存储您的数据,并在创建活动时轻松对其进行解析。也不要忘记在所有Android设备上都有SQLite,因此您可以将它们存储在数据库表中。您也可以使用Map存储键值对并将该序列序列化到本地存储,但这可能太麻烦了,以至于无法用于简单的数据结构。


1

前面提到的所有答案都是很棒的...我只是添加一个没有人提到的关于通过活动持久化数据的方法,那就是使用内置的android SQLite数据库来持久化相关数据...实际上,您可以放置​​您的处于应用程序状态的databaseHelper,并在整个激活过程中根据需要调用它。。或者只是创建一个帮助器类,并在需要时进行DB调用...只需添加另一层供您考虑...但是所有其他答案就足够了以及..真的只是偏爱


1

在活动之间共享数据,例如登录后通过电子邮件发送

“电子邮件”是可用于引用所请求活动上的值的名称

1登录页面上的代码

Intent openLoginActivity = new Intent(getBaseContext(), Home.class);
    openLoginActivity.putExtra("email", getEmail);

主页上的2个代码

Bundle extras = getIntent().getExtras();
    accountEmail = extras.getString("email");

1

如果您想使用数据对象,这两个实现非常重要:

可序列化与可打包

  • 可序列化是一个标记界面,这意味着用户无法根据他们的要求封送数据。因此,当对象实现Serializable时,Java将自动对其进行序列化。
  • Parcelable是android自己的序列化协议。在Parcelable中,开发人员编写用于编组和拆组的自定义代码。因此与序列化相比,它创建的垃圾对象更少
  • 与Serializable相比,Parcelable的性能非常高,因为其自定义实现。强烈建议在android中序列化对象时使用Parcelable植入。

public class User implements Parcelable

这里查看更多

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.