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


595

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

在主要活动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

有谁知道如何设置全局变量以避免在用户已经成功通过身份验证后出现登录表单?


一个很好的教程,介绍如何使用保存的实例状态捆绑包quicktips.in/…
Deepak Swami

Answers:


954

我在09年Android相对较新的时候就写下了这个答案,并且在Android开发中有很多尚未完善的领域。我在本文的底部添加了很长的附录,以解决一些批评,并详细说明了我对使用Singleton而不是对Application进行子类化时的哲学分歧。阅读它需要您自担风险。

原始答案:

您遇到的更普遍的问题是如何在多个“活动”和应用程序的所有部分中保存状态。静态变量(例如,单例)是实现此目的的常见Java方法。但是,我发现,Android中一种更优雅的方法是将您的状态与Application上下文相关联。

如您所知,每个Activity也是一个Context,它是广义上有关其执行环境的信息。您的应用程序还具有上下文,Android保证它在您的应用程序中作为单个实例存在。

这样做的方法是创建自己的android.app.Application子类,然后在清单的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)在下文中提到的那样,完全有可能实例化您的过程(仅)以处理后台广播事件。如果您的广播处理不需要这种状态,则可能只是无所事事地完成了一系列复杂而缓慢的操作。延迟实例化是这里的游戏名称。以下是使用应用程序的稍微复杂的方法,除了最简单的使用方法之外,其他方法都更有意义:

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子类相关联的性能和多线程含义。

注意1:同样,在反咖啡厅中,为了正确地将应用程序替代项与应用程序相关联,清单文件中必须有一个标记。同样,请参阅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子类的观点。在我的不同意中,我将利用在StackExchange对Singleton设计模式的解释中最好地解释的概念,这样我就不必在此答案中定义术语。我强烈建议先跳过链接,然后再继续。逐点:

Dianne指出:“没有理由从Application进行子类化。这与创建单例没有什么不同……”第一个主张是不正确的。这有两个主要原因。1)Application类为应用程序开发人员提供了更好的生命周期保证;它保证了应用程序的生命周期。单例并没有明确地依赖于应用程序的生命周期(尽管有效)。对于您的普通应用程序开发人员来说,这可能不是问题,但是我认为这正是Android API应该提供的合同类型,并且通过最大程度地减少关联的生命周期,它还为Android系统提供了更大的灵活性。数据。2)Application类为应用程序开发人员提供了状态的单个实例持有者,这与单身汉的州持有人有很大的不同。有关差异的列表,请参见上面的Singleton说明链接。

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

她继续说:“这更自然地导致您应该如何管理这些事情-根据需要初始化它们。” 这忽略了以下事实:您也没有理由也无法使用Application子类按需初始化。再次没有区别。

Dianne的结尾是:“框架本身为应用程序维护的所有少量共享数据都有成千上万的单例,例如已加载资源的缓存,对象池等。它运作良好。” 我不是在争论使用Singletons不能正常工作或不是合法的选择。我在争辩说,Singletons与Android系统之间的合同不如使用Application子类提供的合同强大,而且使用Singletons通常指向不灵活的设计,这种设计不容易修改,并且会导致很多问题。恕我直言,Android API为开发人员应用程序提供的强大合同是Android编程中最吸引人,最令人愉悦的方面之一,它有助于导致早期开发人员采用,这促使Android平台取得了今天的成功。

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

我留下了以下从SingleStack链接窃取的Singletons缺点:

  • 无法使用抽象或接口类;
  • 无法分类;
  • 整个应用程序之间的高度耦合(难以修改);
  • 难以测试(在单元测试中不能伪造/模拟);
  • 在可变状态下很难并行化(需要广泛的锁定);

并添加我自己的:

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

93
谢谢Soonil -这些答案是我如此热爱Stack Overflow的原因。做得好!
JohnnyLambada

5
对于任何想知道如何“在清单中的应用程序标签中指定该类的人”,截至撰写本文时,针对此问题还有两个其他答案描述了如何做到这一点(使用android:name),一个由ebuprofen提出,另一个由Mike Brown。
Tyler Collier 2010年

9
很快,您的回答是正确的,但是您是否注意到我们应该将<application android:name =“。MyApp” ... />添加到Android Manifest文件中?
anticafe 2011年

12
让我再重复一遍,您不应该对全局变量使用Application。它是没有用的,不会给单例带来任何好处,并且可能会造成有害影响,例如损害启动过程的性能。在创建应用程序时,您不知道要为什么创建进程。通过根据需要延迟初始化单例,您只需要做必要的工作。例如,如果启动您的进程来处理有关某个背景事件的广播,则没有理由初始化UI所需的任何全局状态。
hackbod 2014年

14
另外,让我们在这里很清楚-当您谈论实际在单例和另一种非全局方法之间进行选择的情况时,您对单例的所有论点都是完全正确的。单例是全局变量,所有有关全局变量的警告都适用。但是,Application也是单例。您并没有通过切换到子类化Application来逃避这些问题,Application与单例完全相同(但更糟),只是让您欺骗自己正在做的事情更干净。但是你不是。
hackbod 2014年

153

创建这个子类

public class MyApp extends Application {
  String foo;
}

在AndroidManifest.xml中添加android:name

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

1
感谢那。我想知道如何在清单中声明它
某处某人

3
为了使它对我有用,我必须删除“。” 在“ .MyApp”中
某处某人

3
只需在主要活动声明它,否则它可能无法安装/部署
sami

11
只是想说,这已经存在于MAIN应用程序标签中了……这不是第二个问题了:)必须学习困难的方法。
bwoogie 2011年

java.lang.IllegalAccessException: access to class is not allowed
猛禽2014年

142

Soonil建议的保持应用程序状态的方法是好的,但是它有一个弱点-在某些情况下,OS会杀死整个应用程序进程。这是有关此文档- 流程和生命周期

考虑一种情况-您的应用程序进入后台,因为有人在打给您(电话应用程序现在处于前台)。在这种情况下,&&在某些其他情况下(请检查上面的链接以了解可能的情况),操作系统可能会杀死您的应用程序进程,包括Application子类实例。结果,状态丢失了。当您稍后返回到应用程序时,操作系统将还原其活动堆栈和Application子类实例,但是该myState字段将为null

AFAIK,保证状态安全的唯一方法是使用任何形式的持久状态,例如对应用程序文件使用私有状态SharedPrefernces(或最终在内部文件系统中对应用程序文件使用私有状态)。


10
+1表示坚持SharedPreferences;这就是我看到的方式。我确实发现滥用保存状态的首选项系统很奇怪,但是它的工作原理如此之好,以至于该问题仅是术语问题。
Cheezmeister

1
您能否张贴代码(或提供解释的链接),以了解如何使用SharedPreferences解决Arhimed描述的问题
某处某人

2
首选项,数据库,文件序列化等。每个活动如果使用onSaveInstanceState都可以保持状态,但是如果用户退出该活动并将其从历史记录堆栈中删除,强制关闭或关闭其设备,则将无济于事。 。
达伦·辛德勒

1
这种行为非常令人讨厌-如果调用了应用程序的onTerminate()方法,那就还不错,这样您就可以优雅地处理这种情况了。
迪恩·维尔德

2
我认为这是正确的答案。依赖于跨活动存在的同一应用程序实例是一个错误。以我的经验,在后台运行时,Android完全拆除并重新创建整个过程是很普遍的。具有背景可能只是意味着启动摄像头意图,浏览器意图或接听电话。
Jared Kells

26

只是一个音符..

加:

android:name=".Globals"

或您将子类命名为现有 <application>标签的任何名称。我一直试图<application>在清单中添加另一个标签,但会出现异常。


嗨,Gimbl。我有同样的问题。我也有自己的<application>标记,当我尝试添加另一个<application>标记时,我遇到了与您相同的问题(异常消息)。但是我照你说的做了,但是没用。我将android:name =“。GlobalClass”添加到我的<application>标记中,但是它不起作用。您能完全说明您如何解决它吗?
Sonhja 2011年

3
好的 <manifest> <application android:name =“。GlobalData”> </ application> </ manifest>。 错误的 <manifest> <application> </ application> <application android:name =“。GlobalData”> </ application> </ manifest>
Gimbl 2011年

13

我也找不到如何指定应用程序标签的方法,但是经过大量Google搜索后,清单文件中的内容变得显而易见:除了应用程序节中的默认图标和标签外,还使用android:name。

android:name为应用程序实现的Application子类的完全限定名称。启动应用程序过程时,将在应用程序的任何组件之前实例化此类。

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


13

如何确保具有此类全局结构的本地内存的收集呢?

活动具有onPause/onDestroy()销毁方法,但是Application类没有等效方法。建议使用什么机制来确保在应用程序被杀死或将任务堆栈置于后台时适当地垃圾收集了全局结构(尤其是那些包含对本机内存的引用的结构)?


1
显而易见的解决方案是为负责本机资源的对象实现Closeable接口,并确保它们由try-with-resources语句或其他方式进行管理。最坏的情况是,您始终可以使用对象终结器。
sooniln

5

只需您定义一个如下所示的应用程序名称即可使用:

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

4

就像上面讨论的一样,OS可以在不发出任何通知的情况下终止该APPLICATION(没有onDestroy事件),因此无法保存这些全局变量。

除非您具有COMPLEX STRUCTURED变量(在我的情况下,我有一个整数数组来存储用户已经处理过的ID),否则SharedPreferences可能是一个解决方案。SharedPreferences的问题在于,每次需要值时都很难存储和检索这些结构。

以我为例,我有一个后台SERVICE,因此可以将这些变量移到该位置,并且由于该服务具有onDestroy事件,因此可以轻松保存这些值。


即使对于服务也不能保证onDestroy()被调用。
了解OpenGL ES

是的,这可能会发生,但仅在紧急情况下才会发生。
Adorjan Princz

4

如果某些变量存储在sqlite中,则必须在应用程序的大多数活动中使用它们。那么应用程序也许是实现它的最佳方法。应用程序启动时从数据库中查询变量,并将其存储在字段中。然后,您可以在活动中使用这些变量。

因此,找到正确的方法,没有最佳的方法。


3

您可以有一个静态字段来存储这种状态。或者将其放到资源包中,然后在onCreate(Bundle savedInstanceState)上还原。只要确保您完全了解Android应用程序托管的生命周期即可(例如,为什么在键盘方向更改时调用login())。


2

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

因此,最后清单文件中的ONE AND ONLY <application>标签应如下所示:-

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

1

您可以使用Intent,Sqlite或Shared Preferences。对于诸如文档,照片和视频之类的媒体存储,您可以改为创建新文件。


1

您可以使用两种方法来执行此操作:

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

此方法适用于临时存储。您真的不知道什么时候操作系统会因为内存不足而终止应用程序。当您的应用程序在后台运行并且用户在需要更多内存才能运行的其他应用程序中导航时,由于操作系统对前台进程的优先级高于后台,因此您的应用程序将被杀死。因此,在用户注销之前,您的应用程序对象将为null。因此,为此,我建议使用上面指定的第二种方法。

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

0

活动结果在恢复之前被调用。因此,将您的登录检查移至恢复状态,一旦第二活动返回了积极结果,您的第二次登录便会被阻止。每次都会调用简历,因此不必担心第一次不会调用它。


0

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

有关详细信息,请参见此示例

如果您能做的更多,为什么还要手工工作呢?


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

0

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

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

    synchronized public String getMyString {
        return str;
    }
}

然后在“活动”中访问该变量,请使用以下命令:

MyApplication application = (MyApplication) getApplication();
String myVar = application.getMyString();
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.