606

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

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

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

4

17 回答 17

963

我在 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(或大多数其他)开发的不明确且难以管理的终身合同;
于 2009-04-02T04:34:27.543 回答
155

创建这个子类

public class MyApp extends Application {
  String foo;
}

在 AndroidManifest.xml 添加 android:name

例子

<application android:name=".MyApp" 
       android:icon="@drawable/icon" 
       android:label="@string/app_name">
于 2010-07-26T20:31:08.863 回答
142

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

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

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

于 2011-01-09T21:49:13.443 回答
25

只是一个注释..

添加:

android:name=".Globals"

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

于 2011-04-13T21:29:47.427 回答
13

那么如何确保收集具有这种全局结构的本机内存呢?

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

于 2011-02-25T18:32:51.533 回答
12

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

android:name 为应用程序实现的应用程序子类的完全限定名称。当应用程序进程启动时,这个类在应用程序的任何组件之前被实例化。

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

于 2010-01-14T05:26:17.713 回答
5

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

<application
  android:name="ApplicationName" android:icon="@drawable/icon">
</application>
于 2011-01-07T05:57:56.413 回答
4

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

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

于 2011-04-20T07:32:17.910 回答
4

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

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

就我而言,我有一个后台 SERVICE,因此我可以将这些变量移到那里,并且因为该服务具有 onDestroy 事件,我可以轻松地保存这些值。

于 2012-11-02T08:53:25.603 回答
3

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

于 2009-04-02T02:19:44.217 回答
2

不要<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"
        >
于 2015-06-22T11:20:42.113 回答
1

您可以使用 Intents、Sqlite 或 Shared Preferences。对于媒体存储,如文档、照片和视频,您可以创建新文件。

于 2013-07-16T01:35:07.333 回答
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.
}

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

  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");
    
于 2015-12-19T13:03:07.883 回答
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();
    ...
    }
}
于 2014-07-17T10:31:46.187 回答
0

在恢复之前调用活动结果。因此,将您的登录检查移到简历上,一旦第二次活动返回肯定结果,您的第二次登录就会被阻止。On resume 每次都会被调用,所以不用担心它不会被第一次调用。

于 2014-10-11T07:36:48.110 回答
0

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

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

如果你能拥有这么多,为什么还要手工工作?

于 2014-11-11T13:01:09.300 回答
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();
于 2015-11-28T13:05:10.287 回答