如何在Android中声明全局variables?
我正在创build一个需要login的应用程序。 我创build了主要和login活动。
在主要活动onCreate
方法中,我添加了以下条件:
public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); ... loadSettings(); if(strSessionString == null) { login(); } ... }
在login表单终止时执行的onActivityResult
方法如下所示:
@Override public void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); switch(requestCode) { case(SHOW_SUBACTICITY_LOGIN): { if(resultCode == Activity.RESULT_OK) { strSessionString = data.getStringExtra(Login.SESSIONSTRING); connectionAvailable = true; strUsername = data.getStringExtra(Login.USERNAME); } } }
问题是login表单有时会出现两次( login()
方法被调用两次),当电话键盘滑动时,login表单又出现了,我猜测问题是variablesstrSessionString
。
有谁知道如何设置variables全球为了避免login窗体出现后,用户已经成功validation?
我在09年的时候写了这个答案,当时Android相对比较新,而且Android开发中还有很多不是很成熟的领域。 我在这篇文章的底部增加了一个附录,讨论了一些批评,并详细说明了我使用Singleton而不是子类化Application的哲学上的分歧。 阅读它需要您自担风险。
原文答案:
遇到的更一般的问题是如何在多个活动和应用程序的所有部分中保存状态。 一个静态variables(例如,一个单例)是实现这一点的常见Java方法。 然而,我发现Android中更优雅的方式是将您的状态与应用程序上下文相关联。
如你所知,每个Activity也是一个Context,它是关于其最广泛执行环境的信息。 你的应用程序也有一个上下文,Android保证它将作为一个实例存在你的应用程序中。
做到这一点的方法是创build你自己的android.app.Application的子类,然后在清单中的应用程序标签中指定该类。 现在,Android将自动创build该类的一个实例,并使其可用于整个应用程序。 您可以使用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(); ... } }
这与使用静态variables或单例的效果基本相同,但与现有的Android框架很好地集成在一起。 请注意,这不会跨进程工作(如果你的应用程序是一个罕见的有多个进程)。
从上面的例子中要注意的事情; 假设我们做了这样的事情:
class MyApp extends Application { private String myState = /* complicated and slow initialization */; public String getState(){ return myState; } }
现在这个缓慢的初始化(例如打磁盘,打networking,阻塞等)将在每次应用程序实例化时执行! 你可能会认为,这只是一次的过程,我将不得不支付成本,对不对? 例如,正如Dianne Hackborn在下面提到的那样,您的stream程完全可以被实例化 – 只要处理背景广播事件。 如果你的广播处理不需要这个状态,你可能只是做了一整套复杂而缓慢的操作。 懒惰的实例是这里游戏的名字。 以下是一个稍微复杂一点的使用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(); ... } }
虽然我更喜欢Application子类化,但在这里,我更愿意使用singleton,因为如果真的有必要的话,开发人员通常不会考虑将状态与Application子类相关联的性能和multithreading含义,而使用单例。
注1:也作为anticafe评论,为了正确地绑定您的应用程序覆盖到您的应用程序标签是必要的清单文件。 再次,请参阅Android文档以获取更多信息。 一个例子:
<application android:name="my.application.MyApp" android:icon="..." android:label="..."> </application>
注2: user608578询问如何在pipe理原生对象生命周期中工作。 我没有加速在Android上使用本地代码的速度,我没有资格回答这将如何与我的解决scheme进行交互。 如果有人有这个答案,我愿意把他们的信息,并把这个信息,以最大限度地提高知名度。
附录:
正如一些人所指出的那样,这不是 持久状态的解决办法,在原来的答案中我应该强调一些。 也就是说,这并不意味着成为保存用户或其他信息的解决scheme。 因此,我认为下面的大多数批评都与应用程序在任何时候被杀害等等无关,因为任何永久需要保存到磁盘的内容都不应该通过应用程序子类来存储。 这意味着存储临时的,易于重新创build的应用程序状态(例如用户是否login)以及本质上是单一实例(例如应用程序networkingpipe理器)( 非单例!)的组件。
Dayerman非常友好地指出了与Reto Meier和Dianne Hackborn的一个有趣的对话,其中使用Application子类不鼓励使用Singleton模式。 Somatik早些时候也指出了这种性质,虽然我当时没有看到。 由于Reto和Dianne在维护Android平台方面的angular色,我不能真诚地推荐忽视他们的build议。 他们说什么,去。 我不希望不同意这个意见,expression了对Singleton比Application子类更喜欢的观点。 在我的不同意见中,我将使用这个StackExchange对Singletondevise模式的解释中最好的解释,以便我不必在这个答案中定义术语。 我强烈build议在继续之前浏览链接。 逐点:
Dianne说:“没有理由从应用程序中进行子类化,它与创build单例模式没有区别……”这第一个要求是不正确的。 这主要有两个原因。 1)Application类为应用程序开发人员提供了更好的生命周期保证。 它保证了应用程序的生命周期。 一个单身人士并不明确地束缚在应用程序的生命周期中(虽然它是有效的)。 这对于普通应用程序开发人员来说可能不是问题,但我认为这正是Android API应该提供的契约types,它也为Android系统提供了更大的灵活性,最大限度地缩短了关联的生命周期数据。 2)Application类为应用程序开发者提供了一个单实例持有者的状态,这与一个Singleton持有者是非常不同的。 有关这些差异的列表,请参阅上面的单例解释链接。
Dianne继续说道:“只要你将来发现你的应用程序对象变成了这个应该是独立应用程序逻辑的混乱的东西,你可能会后悔的。 这当然不是不正确的,但是这不是selectSingleton over Application子类的原因。 Diane的论点没有提供使用Singleton比Application子类更好的一个原因,她试图build立的一个原则是使用Singleton并不比应用程序子类更糟,我相信这个子类是错误的。
她继续说:“这更自然地引导你如何pipe理这些东西 – 按需求初始化它们。” 这忽略了这样的事实,即没有理由不能使用应用程序子类按需进行初始化。 再次没有区别。
Dianne以“这个框架本身拥有大量的单身人士为应用程序所维护的所有小共享数据,例如加载资源的caching,对象池等等。 我不是说使用单身人士不能正常工作,或者不是合法的select。 我认为Singletons并不像使用Application子类那样提供与Android系统的强大合同,而且使用Singletons通常指的是不易修改的不灵活的devise,并且导致许多问题在后面。 恕我直言,Android API为开发者应用程序提供的强有力契约是Android编程中最吸引人和令人愉悦的方面之一,也促成了开发者的早期采用,推动了Android平台取得今天的成功。 build议使用单身人士隐含地远离强大的API合同,在我看来,削弱了Android框架。
Dianne也在下面进行了评论,提到了使用Application子类的另外一个缺点,他们可能鼓励或者更容易编写更less的性能代码。 这是非常真实的,我已经编辑了这个答案,强调了在这里考虑perf的重要性,如果你使用Application子类,采取正确的方法。 正如Dianne所说的,重要的是要记住,每当你的进程被加载的时候,你的Application类将被实例化(如果你的应用程序在多个进程中运行的话,可能会一次多次)!即使进程只是被加载用于后台广播事件。 因此,更重要的是使用Application类作为指向应用程序共享组件指针的存储库,而不是作为任何处理的地方!
我给你留下以下单身人士名单,从早期的StackExchange链接中被盗:
- 无法使用抽象或接口类;
- 无法子类;
- 应用程序之间高度耦合(难以修改);
- 难以testing(在unit testing中不能伪造/模拟);
- 在可变状态下难以并行化(需要大量locking);
并添加我自己的:
- 不明确和不可pipe理的终身合同不适合Android(或大多数)开发;
创build这个子类
public class MyApp extends Application { String foo; }
在AndroidManifest.xml中添加android:name
例
<application android:name=".MyApp" android:icon="@drawable/icon" android:label="@string/app_name">
Soonil提出的保持应用程序状态的方法是很好的,但是它有一个弱点 – 有些情况下OS会杀死整个应用程序进程。 这里是关于这个的文档 – 进程和生命周期 。
考虑一个案例 – 你的应用进入后台,因为有人打电话给你(电话应用程序现在在前台)。 在这种情况下&&在其他一些条件下(检查上面的链接可能是什么)操作系统可能会终止您的应用程序进程,包括Application
子类实例。 结果是国家失去了。 当您稍后返回到应用程序时,操作系统将恢复其活动堆栈和Application
子类实例,但myState
字段将为null
。
AFAIK,保证国家安全的唯一方法是使用任何forms的持久化状态,例如使用专用于应用程序文件或SharedPrefernces
(它最终在内部文件系统中使用私有的应用程序文件)。
只是一个说明..
加:
android:name=".Globals"
或者你将你的子类命名为现有的 <application>
标签。 我一直试图添加另一个<application>
标签到清单,并会得到一个exception。
我找不到如何指定应用程序标签,但是经过大量的谷歌search之后,从清单文件docs中显而易见:使用android:name,除了应用程序节中的默认图标和标签。
android:name为应用程序实现的Application子类的标准名称。 当应用程序启动时,这个类在任何应用程序的组件之前被实例化。
子类是可选的; 大多数应用程序将不需要一个。 在没有子类的情况下,Android使用基类Application类的一个实例。
如何确保收集与这样的全球结构的本地记忆?
活动有一个在销毁时调用的onPause/onDestroy()
方法,但Application类没有等价物。 build议使用什么机制来确保在应用程序被终止或任务堆栈放在后台时,全局结构(特别是那些包含对本机内存的引用的结构)是否被恰当地垃圾回收?
只需要定义一个如下所示的应用程序名称即可:
<application android:name="ApplicationName" android:icon="@drawable/icon"> </application>
就像上面讨论的那样,操作系统可能在没有任何通知的情况下杀死应用程序(没有onDestroy事件),所以没有办法保存这些全局variables。
SharedPreferences可能是一个解决scheme,除了你有COMPLEX STRUCTUREDvariables(在我的情况下,我有整数数组来存储用户已经处理的ID)。 SharedPreferences的问题在于,每次需要值时都很难存储和检索这些结构。
在我的情况下,我有一个后台服务,所以我可以移动这个variables到那里,因为服务有onDestroy事件,我可以很容易地保存这些值。
如果一些variables存储在sqlite中,并且你的应用程序中的大部分活动都必须使用它们。 那么应用程序可能是实现它的最好方法。 当应用程序启动时从数据库查询variables并将它们存储在一个字段中。 那么你可以在你的活动中使用这些variables。
所以find正确的方法,没有最好的办法。
你可以有一个静态字段来存储这种状态。 或者把它放在资源包中并从onCreate(Bundle savedInstanceState)中从那里恢复。 只要确保你完全理解Android应用程序托pipe生命周期(例如为什么login()被键盘方向更改调用)。
不要在清单文件中使用另一个<application>
标签。只要在现有的<application>
标签中做一个更改,添加这一行android:name=".ApplicationName"
其中, ApplicationName
将是您的子类的名称(用于存储全局),你即将创build。
所以,最后你清单文件中的你唯一的 <application>
标签应该看起来像这样:
<application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:theme="@style/Theme.AppCompat.NoActionBar" android:name=".ApplicationName" >
你可以使用两种方法来做到这一点:
- 使用应用程序类
-
使用共享首选项
-
使用应用程序类
例:
class SessionManager extends Application{ String sessionKey; setSessionKey(String key){ this.sessionKey=key; } String getSessisonKey(){ return this.sessionKey; } }
您可以使用上面的类来实现在您的MainActivity中的login,如下所示。 代码将如下所示:
@override public void onCreate (Bundle savedInstanceState){ // you will this key when first time login is successful. SessionManager session= (SessionManager)getApplicationContext(); String key=getSessisonKey.getKey(); //Use this key to identify whether session is alive or not. }
这种方法将用于临时存储。 你真的不知道什么时候操作系统会杀死应用程序,因为内存不足。 当你的应用程序在后台,并且用户正在通过需要更多内存运行的其他应用程序进行导航时,那么你的应用程序将会被终止,因为操作系统比前台处理更优先于前台处理。 因此,在用户注销之前,您的应用程序对象将为空。 因此,我build议使用上面指定的第二种方法。
-
使用共享首选项。
String MYPREF="com.your.application.session" SharedPreferences pref= context.getSharedPreferences(MyPREF,MODE_PRIVATE); //Insert key as below: Editot editor= pref.edit(); editor.putString("key","value"); editor.commit(); //Get key as below. SharedPreferences sharedPref = getActivity().getPreferences(Context.MODE_PRIVATE); String key= getResources().getString("key");
您可以使用Intents,Sqlite或Shared Preferences。 对于媒体存储,如文档,照片和video,您可以改为创build新文件。
活动结果在恢复之前被调用。 所以移动你的login检查到恢复和第二login可以被阻止一旦secomd活动已经返回一个积极的结果。 在简历每次都被叫,所以没有第一次不叫的担心。
子类化的方法也被BARACUS框架使用。 从我的angular度来看, 子类化应用程序旨在与Android的生命周期一起工作; 这是任何应用程序容器所做的。 然后,我不注册全局variables,而是将bean注册到这个上下文中,让它们被注入上下文可pipe理的任何类中。 每个注入的bean实例实际上是一个单例。
查看这个例子的细节
为什么手工工作,如果你可以有这么多?
class GlobaleVariableDemo extends Application { private String myGlobalState; public String getGlobalState(){ return myGlobalState; } public void setGlobalState(String s){ myGlobalState = s; } } class Demo extends Activity { @Override public void onCreate(Bundle b){ ... GlobaleVariableDemo appState = ((GlobaleVariableDemo)getApplicationContext()); String state = appState.getGlobalState(); ... } }
您可以创build一个扩展Application
类的类,然后将该variables声明为该类的一个字段并为其提供getter方法。
public class MyApplication extends Application { private String str = "My String"; synchronized public String getMyString { return str; } }
然后在你的Activity中访问这个variables,使用这个:
MyApplication application = (MyApplication) getApplication(); String myVar = application.getMyString();