我在 09 年写了这个答案,当时 Android 相对较新,Android 开发中有许多尚未完善的领域。我在这篇文章的底部添加了一个很长的附录,解决了一些批评,并详细说明了我对使用单例而不是继承应用程序的哲学分歧。阅读它需要您自担风险。
原始答案:
您遇到的更普遍的问题是如何跨多个活动和应用程序的所有部分保存状态。静态变量(例如,单例)是实现此目的的常见 Java 方法。然而,我发现,在 Android 中一种更优雅的方式是将您的状态与应用程序上下文相关联。
如你所知,每个Activity也是一个Context,是最广义上关于其执行环境的信息。您的应用程序也有一个上下文,Android 保证它将作为单个实例存在于您的应用程序中。
这样做的方法是创建您自己的android.app.Application子类,然后在清单的应用程序标记中指定该类。现在 Android 将自动创建该类的一个实例,并使其可用于您的整个应用程序。context
您可以使用该方法从任何地方访问它Context.getApplicationContext()
(Activity
还提供了一个getApplication()
具有完全相同效果的方法)。以下是一个极其简化的示例,需要注意以下事项:
class MyApp extends Application {
private String myState;
public String getState(){
return myState;
}
public void setState(String s){
myState = s;
}
}
class Blah extends Activity {
@Override
public void onCreate(Bundle b){
...
MyApp appState = ((MyApp)getApplicationContext());
String state = appState.getState();
...
}
}
这与使用静态变量或单例的效果基本相同,但可以很好地集成到现有的 Android 框架中。请注意,这不适用于跨进程(如果您的应用程序是少数具有多个进程的应用程序之一)。
从上面的例子中需要注意的一点;假设我们做了类似的事情:
class MyApp extends Application {
private String myState = /* complicated and slow initialization */;
public String getState(){
return myState;
}
}
现在这个缓慢的初始化(比如打磁盘、打网络、任何阻塞等)将在每次应用程序实例化时执行!你可能会想,好吧,这个过程只有一次,无论如何我都必须支付费用,对吧?例如,正如 Dianne Hackborn 下面提到的,您的进程完全有可能被实例化 - 只是 - 处理后台广播事件。如果您的广播处理不需要这种状态,那么您可能只是白白做了一系列复杂而缓慢的操作。延迟实例化是这里的游戏名称。以下是使用 Application 的稍微复杂的方法,除了最简单的用途之外,它对任何事情都更有意义:
class MyApp extends Application {
private MyStateManager myStateManager = new MyStateManager();
public MyStateManager getStateManager(){
return myStateManager ;
}
}
class MyStateManager {
MyStateManager() {
/* this should be fast */
}
String getState() {
/* if necessary, perform blocking calls here */
/* make sure to deal with any multithreading/synchronicity issues */
...
return state;
}
}
class Blah extends Activity {
@Override
public void onCreate(Bundle b){
...
MyStateManager stateManager = ((MyApp)getApplicationContext()).getStateManager();
String state = stateManager.getState();
...
}
}
虽然我更喜欢应用程序子类化而不是在这里使用单例作为更优雅的解决方案,但我宁愿开发人员在确实需要时使用单例,而不是根本不考虑将状态与应用程序子类关联的性能和多线程影响。
注意 1:正如 anticafe 所评论的那样,为了正确地将您的应用程序覆盖绑定到您的应用程序,清单文件中需要一个标签。再次,请参阅 Android 文档了解更多信息。一个例子:
<application
android:name="my.application.MyApp"
android:icon="..."
android:label="...">
</application>
注意 2: user608578 在下面询问这如何与管理本机对象生命周期一起工作。我丝毫没有跟上在 Android 上使用本机代码的速度,而且我没有资格回答这将如何与我的解决方案交互。如果有人对此有答案,我愿意相信他们并将信息放在这篇文章中以获得最大的知名度。
附录:
正如一些人所指出的,这不是持久状态的解决方案,我可能应该在原始答案中更多地强调这一点。即,这并不是一种用于保存用户或其他要在应用程序生命周期中持久保存的信息的解决方案。因此,我认为下面的大多数批评与应用程序随时被杀死等有关......,没有实际意义,因为任何需要持久保存到磁盘的东西都不应该通过应用程序子类存储。它旨在成为一种解决方案,用于存储临时的、易于重新创建的应用程序状态(例如用户是否登录)和本质上是单实例的组件(例如应用程序网络管理器)(不是单例!)。
Dayerman 非常友好地指出了与 Reto Meier 和 Dianne Hackborn 的一次有趣的对话,其中不鼓励使用 Application 子类,而是支持 Singleton 模式。Somatik 之前也指出了这种性质的东西,虽然我当时没有看到。由于 Reto 和 Dianne 在维护 Android 平台方面的角色,我不能真诚地建议忽略他们的建议。他们说什么,去。我确实希望不同意关于更喜欢单例而不是应用程序子类的观点。在我不同意的情况下,我将使用StackExchange 对 Singleton 设计模式的解释中最好解释的概念,因此我不必在此答案中定义术语。我强烈建议在继续之前浏览链接。逐点:
Dianne 说,“没有理由从 Application 子类化。这与做一个单例没有什么不同……”第一个说法是不正确的。这有两个主要原因。1)Application类为应用开发者提供了更好的生命周期保障;它保证具有应用程序的生命周期。单例与应用程序的生命周期没有明确的联系(尽管它是有效的)。对于普通的应用程序开发人员来说,这可能不是问题,但我认为这正是 Android API 应该提供的合约类型,它还通过最小化关联的生命周期为 Android 系统提供了更大的灵活性数据。2) Application 类为应用程序开发人员提供了一个用于状态的实例持有者,这与单例状态的持有者非常不同。有关差异的列表,请参阅上面的 Singleton 解释链接。
Dianne 继续说道,“……当你发现你的应用程序对象变成了一个应该是独立应用程序逻辑的大杂烩时,你可能会后悔。” 这当然不是错误的,但这不是选择 Singleton 而不是 Application 子类的原因。Diane 的所有论点都没有提供使用 Singleton 比 Application 子类更好的理由,她试图建立的只是使用 Singleton 并不比 Application 子类差,我认为这是错误的。
她继续说,“这更自然地导致你应该如何管理这些事情——按需初始化它们。” 这忽略了这样一个事实,即您没有理由也不能使用 Application 子类按需初始化。再次没有区别。
Dianne 以“框架本身为它为应用程序维护的所有小共享数据(例如加载资源的缓存、对象池等)提供了大量的单例。它工作得很好。” 我并不是说使用 Singletons 不能正常工作或者不是一个合法的选择。我认为 Singleton 与 Android 系统之间的契约不如使用 Application 子类强,而且使用 Singleton 通常指向不灵活的设计,不易修改,并导致许多问题。恕我直言,Android API 为开发人员应用程序提供的强大合同是使用 Android 编程最吸引人和最令人愉悦的方面之一,并帮助导致早期开发人员采用,从而推动 Android 平台取得今天的成功。
Dianne 在下面也发表了评论,提到了使用 Application 子类的另一个缺点,它们可能会鼓励或更容易编写性能较低的代码。这是非常正确的,我已经编辑了这个答案以强调在这里考虑性能的重要性,如果您使用的是应用程序子类化,请采取正确的方法。正如 Dianne 所说,重要的是要记住每次加载进程时都会实例化您的 Application 类(如果您的应用程序在多个进程中运行,则可能一次多次实例化!)即使该进程只是为后台广播而加载事件。因此,重要的是更多地将 Application 类用作指向应用程序共享组件的指针的存储库,而不是用作进行任何处理的地方!
我给你留下了以下单例的缺点列表,这些缺点是从早期的 StackExchange 链接中窃取的:
- 无法使用抽象类或接口类;
- 无法子类化;
- 跨应用程序的高耦合(难以修改);
- 难以测试(不能在单元测试中伪造/模拟);
- 在可变状态的情况下难以并行化(需要大量锁定);
并添加我自己的:
- 不适合 Android(或大多数其他)开发的不明确且难以管理的终身合同;