如何在Android中声明全局变量?

我正在创建一个需要登录的应用程序。我创建了主活动和登录活动。

在主活动onCreate方法中,我添加了以下条件:

public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);


...


loadSettings();
if(strSessionString == null)
{
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()方法被调用两次),而且当电话键盘滑动时,登录表单再次出现,我猜问题是变量strSessionString

有人知道如何设置变量全局,以避免登录表单出现后,用户已经成功验证?

310195 次浏览

您可以使用一个静态字段来存储这种状态。或者把它放到资源Bundle中,然后在onCreate(Bundle savedInstanceState)上从那里恢复。只要确保你完全理解Android应用程序管理的生命周期(例如,为什么login()会在键盘方向改变时被调用)。

我在09年写了这个答案,当时Android还相对较新,在Android开发中还有许多不成熟的领域。我在这篇文章的底部添加了一个很长的附录,解决了一些批评,并详细说明了我在使用单例而不是应用程序子类化方面的哲学分歧。请自行承担阅读风险。

# EYZ0

您遇到的更普遍的问题是如何跨多个activity和应用程序的所有部分保存状态。静态变量(例如,单例变量)是一种常见的Java实现方式。然而,我发现在Android中更优雅的方式是将你的状态与应用程序上下文关联起来。

如您所知,每个活动也是一个上下文,从最广泛的意义上讲,上下文是关于其执行环境的信息。你的应用程序也有一个上下文,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 = /* 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();
...
}
}

虽然在这里我更喜欢应用程序子类化而不是使用单例对象作为更优雅的解决方案,但我宁愿开发人员在必要时使用单例对象,而不是完全不考虑将状态与应用程序子类关联的性能和多线程含义。

正如anticafe所评论的那样,为了正确地将你的应用程序覆盖到你的应用程序,在manifest文件中有一个标签是必要的。同样,更多信息请参见Android文档。一个例子:

<application
android:name="my.application.MyApp"
android:icon="..."
android:label="...">
</application>

注2: user608578在下面询问如何管理本机对象的生命周期。我不知道如何在Android上使用本机代码,我也没有资格回答如何与我的解决方案交互。如果有人确实有这个问题的答案,我愿意相信他们,并把这些信息放在这篇文章中以获得最大的能见度。

附录:

正如一些人注意到的,这是状态的解决方案,这一点我可能应该在最初的答案中更强调。也就是说,这并不是一种保存用户或其他信息的解决方案,这些信息应该在应用程序的生命周期中持久化。因此,我认为下面的大多数批评都与应用程序随时被杀死有关……,因为任何需要持久化到磁盘的东西都不应该通过Application子类存储。它是用来存储临时的、易于重新创建的应用程序状态(例如用户是否登录)和单实例组件(例如应用程序网络管理器)( singleton!)的解决方案。

Dayerman好心地指出了一个有趣的与Reto Meier和Dianne Hackborn的对话,在这个与Reto Meier和Dianne Hackborn的对话中,不鼓励使用应用程序子类,而支持单例模式。Somatik在早些时候也指出了这种性质的东西,尽管我当时没有看到。由于Reto和Dianne在维护Android平台方面的角色,我不能真诚地建议忽略他们的建议。他们说什么就去什么。我确实不同意关于更喜欢单例而不是应用程序子类的观点。在我的不同意见中,我将使用在这个单例设计模式的StackExchange解释中解释得最好的概念,这样我就不必在这个答案中定义术语。我强烈建议在继续之前浏览一下链接。逐点分析:

Dianne说:“没有理由从Application继承子类。这和制造一个单胎没有什么不同……”第一种说法是不正确的。这主要有两个原因。1) Application类为应用开发者提供了更好的生命周期保障;它保证具有应用程序的生命周期。单例没有显式地绑定到应用程序的生命周期(尽管它是有效的)。对于一般的应用程序开发人员来说,这可能不是问题,但我认为这正是Android API应该提供的契约类型,它通过最小化相关数据的生命周期,为Android系统提供了更大的灵活性。2) Application类为应用程序开发人员提供了一个状态的单一实例持有者,这与状态的Singleton持有者有很大的不同。有关差异的列表,请参阅上面的Singleton解释链接。

黛安继续说道:“……将来你可能会后悔,因为你发现你的应用程序对象变成了一个应该是独立的应用程序逻辑的大混乱。”这当然不是不正确的,但这不是选择单例而不是应用程序子类的原因。Diane的论点没有提供使用单例比应用程序子类更好的理由,她试图建立的是,使用单例并不比应用程序子类差,我认为这是错误的。

她接着说,“这就更自然地引出了你应该如何管理这些东西——根据需要初始化它们。”这忽略了一个事实,即没有理由不能使用Application子类按需初始化。同样没有区别。

Dianne最后说:“框架本身有大量的单例,用于维护应用程序的所有共享数据,比如加载资源的缓存、对象池等。效果很好。”我并不是说使用单例不能很好地工作,或者不是一个合理的替代方案。我认为单例不像使用Application子类那样为Android系统提供强大的契约,而且使用单例通常指向不灵活的设计,不容易修改,并导致许多问题。依我之见,Android API为开发者提供的强大契约是Android编程中最吸引人、最令人愉悦的方面之一,并帮助早期开发者采用Android平台,从而推动Android平台取得今天的成功。建议使用单例含蓄地背离了强大的API契约,在我看来,这削弱了Android框架。

Dianne也在下面发表了评论,提到了使用Application子类的另一个缺点,它们可能会鼓励或更容易编写性能较低的代码。这是非常正确的,我编辑了这个答案,以强调在这里考虑性能的重要性,以及在使用应用程序子类化时采取正确方法的重要性。正如Dianne所说,重要的是要记住,你的Application类将在每次加载你的进程时被实例化(如果你的应用程序运行在多个进程中,可能会一次实例化多次!),即使进程只是为了后台广播事件而加载。因此,重要的是将Application类更多地用作应用程序共享组件的指针存储库,而不是用作进行任何处理的地方!

以下是我从之前的StackExchange链接中偷来的关于singleton的缺点:

  • 不能使用抽象类或接口类;
  • 无法子类化;
  • 跨应用程序的高耦合(难以修改);
  • 难以测试(不能在单元测试中伪造/模拟);
  • 在可变状态的情况下难以并行(需要大量的锁定);

加上我自己的:

  • 不明确且难以管理的终身合同不适合Android(或大多数其他)开发;

我也找不到如何指定应用程序标签,但经过大量的谷歌搜索,从清单文件docs中可以明显看出:使用android:name,除了应用程序节中的默认图标和标签。

< p > android:名字 为应用程序实现的Application子类的完全限定名。当应用程序进程启动时,该类在应用程序的任何组件之前实例化

子类是可选的;大多数应用程序都不需要。在没有子类的情况下,Android使用基Application类的实例。

创建这个子类

public class MyApp extends Application {
String foo;
}

在AndroidManifest.xml中添加android:name

例子

<application android:name=".MyApp"
android:icon="@drawable/icon"
android:label="@string/app_name">

你只需要定义一个应用程序的名称,如下所示:

<application
android:name="ApplicationName" android:icon="@drawable/icon">
</application>

Soonil建议的保持应用程序状态的方法是好的,但是它有一个弱点——在某些情况下,操作系统会终止整个应用程序进程。下面是关于过程和生命周期的文档。

考虑一个案例-你的应用程序进入后台,因为有人正在呼叫你(电话应用程序现在在前台)。在这种情况下&&在其他一些条件下(检查上面的链接,它们可能是什么),操作系统可能会杀死你的应用程序进程,包括Application子类实例。结果,状态丢失了。当您稍后返回到应用程序时,操作系统将恢复其活动堆栈和Application子类实例,但myState字段将为null

AFAIK,保证状态安全的唯一方法是使用任何类型的持久化状态,例如为应用程序文件使用private或SharedPrefernces(它最终在内部文件系统中为应用程序文件使用private)。

如何使用这样的全局结构来确保本机内存的收集呢?

活动有一个onPause/onDestroy()方法,它在销毁时被调用,但是Application类没有等价的方法。当应用程序被杀死或任务栈被放到后台时,建议采用什么机制来确保全局结构(特别是那些包含对本机内存的引用)被适当地垃圾收集?

只是一个提示..

添加:

android:name=".Globals"

或者任何你给你的子类命名的现有的 <application>标签。我一直试图将另一个<application>标签添加到清单中,并会得到一个异常。

如果一些变量存储在sqlite中,你必须在应用程序的大多数活动中使用它们。 那么应用可能是实现它的最好方式。 当应用程序启动时,查询数据库中的变量,并将它们存储在一个字段中。 然后你可以在你的活动中使用这些变量

所以要找到正确的方法,没有最好的方法。

就像上面讨论的那样,操作系统可以在没有任何通知的情况下杀死应用程序(没有onDestroy事件),所以没有办法保存这些全局变量。

SharedPreferences可以是一个解决方案,除非你有复杂的结构化变量(在我的情况下,我有一个整数数组来存储用户已经处理的id)。SharedPreferences的问题在于,每次需要值时都很难存储和检索这些结构。

在我的情况下,我有一个后台服务,所以我可以把这些变量移动到那里,因为服务有onDestroy事件,我可以很容易地保存这些值。

你可以使用intent, Sqlite或Shared Preferences。当涉及到媒体存储时,如文档、照片和视频,您可以创建新文件。

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();
...
}
}

在活动结果之前在简历上被调用。所以移动你的登录检查到恢复和你的第二次登录可以被阻止,一旦第二个活动已经返回积极的结果。On简历每次都会被调用,所以不用担心第一次没有被调用。

BARACUS框架也使用了子类化的方法。从我的角度来看,子类化应用程序是为了与Android的生命周期工作;这就是任何应用程序容器所做的。我没有使用全局变量,而是将bean注册到这个上下文中,让它们被注入到上下文可管理的任何类中。每个注入的bean实例实际上都是单例的。

详细信息参见本例 . sh . sh

如果你能有这么多,为什么还要做体力活呢?

在清单文件中使用另一个<application>标签。只需要在现有的<application>标签中做一个更改,添加这一行android:name=".ApplicationName",其中ApplicationName将是你的子类的名称(用于存储全局),你即将创建。

所以,最后你的唯一的 <application>标签在清单文件应该看起来像这样:-

<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:theme="@style/Theme.AppCompat.NoActionBar"
android:name=".ApplicationName"
>

您可以创建一个扩展Application类的类,然后将您的变量声明为该类的字段,并为它提供getter方法。

public class MyApplication extends Application {
private String str = "My String";


synchronized public String getMyString {
return str;
}
}

然后在你的Activity中访问这个变量,使用这个:

MyApplication application = (MyApplication) getApplication();
String myVar = application.getMyString();

你可以使用两种方法来做到这一点:

  1. 使用应用程序类
  2. 使用共享首选项

  3. 使用应用程序类

例子:

class SessionManager extends Application{


String sessionKey;


setSessionKey(String key){
this.sessionKey=key;
}


String getSessisonKey(){
return this.sessionKey;
}
}

你可以使用上面的类在MainActivity中实现登录。代码看起来像这样:

@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.
}

此方法适用于临时存储。你真的不知道什么时候操作系统会因为内存不足而关闭应用程序。 当你的应用程序在后台,用户正在浏览其他需要更多内存的应用程序时,你的应用程序将被杀死,因为操作系统给前台进程比后台进程更多的优先级。 因此,在用户注销之前,应用程序对象将为空。因此,我建议使用上面指定的第二种方法

  1. 使用共享首选项。

    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");