我在'09年写下了这个答案,当时Android相对较新,在Android开发的许多不太成熟的领域中存在着许多问题。在此帖子底部添加了一个长的附录,解决了一些批评,并详细说明了我与使用Singletons而不是子类化Application存在的哲学分歧。阅读风险自负。
原始答案:
你遇到的更普遍的问题是如何跨多个Activity和应用程序所有部分保存状态。静态变量(例如单例)是实现这一目标的常见Java方式。然而,我发现在Android中更优雅的方法是将状态与应用程序上下文相关联。
正如您所知,每个Activity也是一个Context,它是关于其执行环境的最广泛的信息。您的应用程序也有一个上下文,并且Android保证它将作为单个实例存在于整个应用程序中。
要做到这一点,您需要创建自己的android.app.Application子类,然后在清单文件中的应用程序标记中指定该类。现在,Android将自动创建该类的实例并使其在整个应用程序中可用。您可以使用Context.getApplicationContext()
方法从任何context
访问它(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 = ;
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() {
}
String getState() {
...
return state;
}
}
class Blah extends Activity {
@Override
public void onCreate(Bundle b){
...
MyStateManager stateManager = ((MyApp)getApplicationContext()).getStateManager();
String state = stateManager.getState();
...
}
}
虽然我更喜欢使用应用程序子类化作为更优雅的解决方案,但如果开发人员真的需要将状态与应用程序子类相关联,则我宁愿他们使用单例模式而不是完全不考虑性能和多线程影响。
注意1:正如anticafe所评论的那样,在将您的应用程序覆盖关联到应用程序时,manifest文件中需要一个<application>标签。再次参见Android文档获取更多信息。这是一个示例:
<application
android:name="my.application.MyApp"
android:icon="..."
android:label="...">
</application>
注意2: 用户user608578在下方询问如何管理本地对象的生命周期。我对在Android中使用本地代码一无所知,因此不能回答它与我的解决方案如何交互的问题。如果有人有答案,我愿意给他们信用并将信息放在这篇文章中以获得最大的可见性。
附录:
正如一些人所指出的,这不是一个持久化状态的解决方案,这是我在原始答案中可能应该更加强调的。即,这不是为了保存用户或其他需要在应用程序生命周期内保持的信息而设计的解决方案。因此,我认为大多数批评都与应用程序随时被杀死等相关的问题无关,因为任何需要存储到磁盘的东西都不应该通过Application子类来存储。它旨在为存储临时、易于重新创建的应用程序状态(例如用户是否已登录)和单实例组件(例如应用程序网络管理器)(不是单例!)提供解决方案。
Dayerman很友好地指出了一个有趣的与Reto Meier和Dianne Hackborn的对话,其中不鼓励使用Application子类而更喜欢Singleton模式。Somatik也早些时候指出了这样的事情,尽管我当时没有看到。由于Reto和Dianne在维护Android平台方面的角色,我不能诚信地建议忽略他们的建议。他们说什么就是什么。我确实希望反对有关更喜欢Singleton而不是Application子类的观点。在我的反对中,我将利用最好在Singleton设计模式的这个StackExchange解释中解释的概念,以便我不必在这个答案中定义术语。我强烈建议在继续之前浏览该链接。逐点说明:
Dianne表示:“没有理由从Application进行子类化。它与创建Singleton没有区别...”这个第一个声明是不正确的。这有两个主要原因。1)Application类为应用程序开发人员提供了更好的生命周期保证;它保证具有应用程序的生命周期。Singleton没有EXPLICITLY与应用程序的生命周期绑定(尽管它是有效的)。这可能对您的普通应用程序开发人员来说不是问题,但我会认为这正是Android API应该提供的类型的合同,并且它还为Android系统提供了更大的灵活性,通过最小化相关数据的生命周期。2)Application类为应用程序开发人员提供了一个状态的单实例持有者,这与Singleton状态的持有者非常不同。有关差异的列表,请参见上面的Singleton解释链接。
Dianne说,“...这可能会让你在未来后悔,因为你的Application对象会变成一个混乱的东西,应该是独立的应用逻辑。” 这当然不是错误的,但这并不是选择Singleton而不是Application子类的原因。 Diane的论点没有提供使用Singleton比Application子类更好的理由,她试图建立的只是使用Singleton并不比Application子类更差,我认为这是错误的。
她接着说,“这更自然地引导你如何管理这些东西——按需初始化。” 这忽略了一个事实,即你也可以使用Application子类按需初始化。再次,没有区别。
Dianne最后说:“框架本身有大量单例,用于维护应用程序的所有共享数据,例如缓存加载的资源、对象池等等。它运行得很好。” 我并不是在争论使用Singleton不能很好地工作或不是一个合法的替代方案。我争论的是,与使用Application子类相比,Singletons没有提供与Android系统一样强的契约,并且使用Singletons通常指向不灵活的设计,这些设计不容易修改,并且会在以后引起许多问题。在我看来,Android API为开发者应用程序提供的强大契约是使用Android编程中最吸引人和令人愉悦的方面之一,这帮助推动了Android平台的早期开发者采用,从而推动了今天Android平台的成功。建议使用Singletons隐含地远离了强大的API契约,而且在我看来,削弱了Android框架。
Dianne还在下面发表了评论,提到了使用Application子类的另一个缺点,它可能鼓励或使编写性能较差的代码更容易。这是非常正确的,我已经编辑了这个答案,以强调考虑性能的重要性,并采取正确的方法,如果你正在使用Application子类。正如Dianne所说,重要的是要记住,每次加载进程时都会实例化你的Application类(如果你的应用程序在多个进程中运行,则可能同时多次加载!),即使进程仅被加载用于后台广播事件。因此,更重要的是将Application类用作指向应用程序共享组件的指针的存储库,而不是进行任何处理的地方!
我给你留下以下关于Singletons的缺点列表,从之前的StackExchange链接中窃取:
- 无法使用抽象或接口类;
- 无法子类化;
- 应用程序耦合度高(难以修改);
- 难以测试(无法在单元测试中伪造/模拟);
- 在可变状态的情况下难以并行化(需要广泛的锁定);
我自己加上了:
- 不清楚和无法管理的生命周期合同不适用于Android(或大多数其他)开发;