web-dev-qa-db-fra.com

Comment déclarer des variables globales dans Android?

Je crée une application qui nécessite une connexion. J'ai créé l'activité principale et l'activité de connexion.

Dans l'activité principale onCreate, j'ai ajouté la condition suivante:

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

    ...

    loadSettings();
    if(strSessionString == null)
    {
        login();
    }
    ...
}

La méthode onActivityResult qui est exécutée à la fin du formulaire de connexion ressemble à ceci:

@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);
            }
        }
    }

Le problème est que le formulaire de connexion apparaît parfois deux fois (la méthode login() est appelée deux fois) et lorsque le clavier du téléphone est glissé, le formulaire de connexion apparaît à nouveau et je suppose que le problème est la variable strSessionString.

Est-ce que quelqu'un sait comment définir la variable globale afin d'éviter que le formulaire de connexion n'apparaisse après l'authentification réussie de l'utilisateur?

590
Niko Gamulin

J'ai répondu à cette question en 2009 lorsque Android était relativement nouveau et que de nombreuses zones de développement Android n'étaient pas bien établies. J'ai ajouté un long addendum au bas de cet article, abordant certaines critiques et détaillant un désaccord philosophique que j'ai avec l'utilisation de Singletons plutôt que d'Application de sous-classes. Lisez-le à vos risques et périls.

RÉPONSE ORIGINALE:

Le problème plus général que vous rencontrez est de savoir comment enregistrer l’état sur plusieurs activités et sur toutes les parties de votre application. Une variable statique (par exemple, un singleton) est un moyen courant Java d'y parvenir. J'ai cependant constaté qu'une manière plus élégante dans Android consiste à associer votre état au contexte de l'application.

Comme vous le savez, chaque activité est aussi un contexte, c'est-à-dire des informations sur son environnement d'exécution au sens le plus large. Votre application a également un contexte et Android garantit qu’elle existera en tant qu’instance unique dans toute votre application.

Pour ce faire, vous devez créer votre propre sous-classe Android.app.Application , puis spécifier cette classe dans la balise d'application de votre manifeste. Maintenant, Android créera automatiquement une instance de cette classe et la rendra disponible pour l'ensemble de votre application. Vous pouvez y accéder depuis n'importe quel context en utilisant la méthode Context.getApplicationContext() (Activity fournit également une méthode getApplication() qui a exactement le même effet). Voici un exemple extrêmement simplifié, avec des mises en garde à suivre:

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

Cela a essentiellement le même effet que d'utiliser une variable statique ou un singleton, mais s'intègre plutôt bien dans le cadre existant Android. Notez que cela ne fonctionnera pas d'un processus à l'autre (si votre application est l'une des rares à disposer de plusieurs processus).

Quelque chose à noter de l'exemple ci-dessus; supposons que nous ayons plutôt fait quelque chose comme:

class MyApp extends Application {

  private String myState = /* complicated and slow initialization */;

  public String getState(){
    return myState;
  }
}

Désormais, cette lente initialisation (frappe de disque, réseau, blocage, etc.) sera effectuée chaque fois que l'application est instanciée! Vous pensez peut-être que ce n’est qu’une fois pour le processus et que je devrai quand même en payer le coût, non? Par exemple, comme Dianne Hackborn le mentionne ci-dessous, il est tout à fait possible que votre processus soit instancié - juste - pour gérer un événement de diffusion en arrière-plan. Si votre traitement de diffusion n'a pas besoin de cet état, vous pouvez potentiellement effectuer toute une série d'opérations lentes et compliquées pour rien. L'instanciation paresseuse est le nom du jeu ici. Ce qui suit est une manière légèrement plus compliquée d’appliquer Application qui est plus logique dans les utilisations les plus simples:

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

Bien que je préfère les sous-classes d'application à l'utilisation de singletons ici comme solution plus élégante, je préférerais que les développeurs utilisent des singletons si c'est vraiment nécessaire plutôt que de ne pas penser du tout aux implications en termes de performances et de multithreading d'association d'état avec la sous-classe Application.

NOTE 1: De plus, comme l'a indiqué anticafe, pour lier correctement le remplacement de votre application à votre application, une balise est nécessaire dans le fichier manifeste. Encore une fois, consultez la documentation Android pour plus d'informations. Un exemple:

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

REMARQUE 2: user608578 demande ci-dessous comment cela fonctionne avec la gestion du cycle de vie des objets natifs. Je ne suis pas du tout en mesure d'utiliser le code natif avec Android, et je ne suis pas qualifié pour dire comment cela interagirait avec ma solution. Si quelqu'un a une réponse à cela, je suis prêt à le créditer et à mettre les informations dans cet article pour une visibilité maximale.

ADDENDUM:

Comme certaines personnes l'ont noté, ceci n'est pas une solution pour un état persistant , quelque chose que j'aurais peut-être dû souligner davantage dans la réponse originale. C'est à dire. il ne s'agit pas d'une solution permettant de sauvegarder des informations utilisateur ou autres devant être conservées tout au long de la vie des applications. Par conséquent, j’estime que la plupart des critiques ci-dessous relatives aux applications tuées à tout moment, etc., sont sans objet, étant donné que tout ce qui devait jamais être conservé sur disque ne devrait pas être stocké dans une sous-classe Application. Il s’agit d’une solution permettant de stocker l’état de l’application temporaire et facilement reconfigurable (qu’un utilisateur soit connecté par exemple) et des composants à instance unique (gestionnaire de réseau d’applications, par exemple) (PAS singleton!) dans la nature.

Dayerman a eu la gentillesse de signaler un intéressant conversation avec Reto Meier et Dianne Hackborn dans lequel l’utilisation des sous-classes d’application est découragée au profit des modèles de Singleton. Somatik a également souligné quelque chose de cette nature plus tôt, bien que je ne l'ai pas vu à l'époque. En raison des rôles de Reto et Dianne dans le maintien de la plate-forme Android, je ne peux pas, de bonne foi, recommander de ne pas tenir compte de leurs conseils. Ce qu'ils disent, va. Je ne souhaite pas être en désaccord avec les opinions exprimées concernant la préférence accordée à Singleton par rapport aux sous-classes d’application. Dans mon désaccord, je me servirai des concepts les mieux expliqués dans cette explication StackExchange du motif de conception Singleton , de sorte que je n'ai pas à définir les termes dans cette réponse. J'encourage fortement à parcourir le lien avant de continuer. Point par point:

Dianne déclare: "Il n'y a aucune raison de sous-classer dans Application. Ce n'est pas différent de faire un singleton ..." Cette première affirmation est incorrecte. Il y a deux raisons principales pour cela. 1) La classe d’application offre une meilleure garantie à vie pour un développeur d’applications; il est garanti d'avoir la durée de vie de l'application. Un singleton n'est pas EXPLICITEMENT lié à la durée de vie de l'application (bien que ce soit effectivement). C’est peut-être un problème pour votre développeur d’applications moyen, mais je dirais que c’est exactement le type de contrat que l’API Android devrait offrir, et cela offre beaucoup plus de souplesse au système Android, Eh bien, en minimisant la durée de vie des données associées. 2) La classe Application fournit au développeur d'applications un titulaire d'instance unique pour state, qui est très différent d'un détenteur d'état Singleton. Pour une liste des différences, voir le lien d’explication Singleton ci-dessus.

Dianne continue, "... il est probable que vous regretterez à l'avenir si vous trouvez votre objet Application en train de devenir ce grand fouillis de ce qui devrait être une logique d'application indépendante." Ce n’est certainement pas faux, mais ce n’est pas une raison pour choisir Singleton au lieu de la sous-classe Application. Les arguments de Diane ne justifient en rien l'utilisation d'un Singleton par rapport à une sous-classe Application. Tout ce qu'elle a tenté de démontrer, c'est que l'utilisation d'un Singleton n'est pas pire qu'une sous-classe Application, ce qui, à mon avis, est faux.

Elle poursuit: "Et cela nous amène plus naturellement à la façon dont vous devriez gérer ces choses - en les initialisant à la demande." Cela ignore le fait qu'il n'y a aucune raison pour que vous ne puissiez pas également initialiser à la demande à l'aide d'une sous-classe Application. Encore une fois, il n'y a pas de différence.

Dianne se termine par "Le cadre lui-même contient des tonnes et des tonnes de singletons pour toutes les données partagées qu’elle conserve pour l’application, telles que des caches de ressources chargées, des pools d’objets, etc. Cela fonctionne très bien." Je ne dis pas que l'utilisation de singletons ne peut pas fonctionner correctement ou n'est pas une alternative légitime. Je soutiens que Singletons ne fournit pas un contrat aussi fort avec le système Android que l’utilisation d’une sous-classe d’application. En outre, utiliser Singletons indique généralement une conception inflexible, qui n’est pas facilement modifiable, et pose de nombreux problèmes dans le futur. route. IMHO, le contrat solide que l'API Android offre aux applications de développement est l'un des aspects les plus attrayants et les plus agréables de la programmation avec Android, et a contribué à l'adoption précoce par les développeurs qui ont conduit la plate-forme Android à son succès. a aujourd'hui. Suggérer l’utilisation de Singletons s’éloigne implicitement d’un contrat API fort et affaiblit, selon moi, le cadre Android.

Dianne a également commenté ci-dessous, mentionnant un inconvénient supplémentaire lié à l’utilisation des sous-classes d’application, qui peuvent encourager ou faciliter l’écriture de code de performance inférieur. C’est très vrai et j’ai modifié cette réponse pour souligner l’importance de considérer perf ici et d’adopter la bonne approche si vous utilisez le sous-classement d’application. Comme Dianne le dit, il est important de se rappeler que votre classe Application sera instanciée chaque fois que votre processus est chargé (peut être plusieurs fois à la fois si votre application s'exécute dans plusieurs processus!) Même si le processus n'est chargé que pour une diffusion en arrière-plan. un événement. Il est donc important d'utiliser davantage la classe Application en tant que référentiel pour les pointeurs sur les composants partagés de votre application plutôt qu'en tant que lieu de traitement.

Je vous laisse avec la liste suivante des inconvénients de Singletons, volés à partir du lien StackExchange précédent:

  • Incapacité d'utiliser des classes abstraites ou d'interface;
  • Incapacité à sous-classer;
  • Haut couplage dans l'application (difficile à modifier);
  • Difficile à tester (impossible de simuler/simuler des tests unitaires);
  • Difficulté à paralléliser dans le cas d'un état mutable (nécessite un verrouillage important);

et ajouter le mien:

  • Contrat à vie incertain et ingérable inadapté au développement Android (ou à la plupart des autres);
951
sooniln

Créer cette sous-classe

public class MyApp extends Application {
  String foo;
}

Dans le fichier AndroidManifest.xml, ajoutez Android: name

Exemple

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

Le moyen suggéré par Soonil de conserver un état pour l'application est bon, mais il a un point faible: dans certains cas, le système d'exploitation tue l'intégralité du processus de demande. Voici la documentation à ce sujet - Processus et cycles de vie .

Considérons un cas - votre application passe en arrière-plan parce que quelqu'un vous appelle (l'application Phone est au premier plan). Dans ce cas, && dans certaines autres conditions (vérifiez le lien ci-dessus pour connaître leur nature), le système d’exploitation peut tuer votre processus d’application, y compris l’instance Application de la sous-classe. En conséquence, l'état est perdu. Lorsque vous revenez plus tard dans l'application, le système d'exploitation restaure sa pile d'activités et l'instance de la sous-classe Application. Toutefois, le champ myState sera null.

Autant que je sache, le seul moyen de garantir la sécurité de l’État consiste à utiliser un type quelconque de maintien de l’État, par exemple. en utilisant un fichier privé pour le fichier d'application ou SharedPrefernces (il utilise finalement un fichier privé pour le fichier d'application dans le système de fichiers interne).

142
Vit Khudenko

Juste une note ..

ajouter:

Android:name=".Globals"

ou ce que vous avez nommé votre sous-classe à la balise existante<application>. J'ai continué à essayer d'ajouter une autre balise <application> au manifeste et j'aurais une exception.

26
Gimbl

Je ne trouvais pas non plus comment spécifier le tag de l'application, mais après de nombreuses recherches sur Google, les documents du fichier de manifeste: Google: utilisez Android: name, en plus de l'icône et du libellé par défaut dans la strophe de l'application.

Android: name Nom complet d'une sous-classe Application implémentée pour l'application. Lorsque le processus d'application est démarré, cette classe est instanciée avant l'un des composants de l'application.

La sous-classe est facultative. la plupart des applications n'en auront pas besoin. En l'absence de sous-classe, Android utilise une instance de la classe Application de base.

13
Mike Brown

Qu'en est-il de garantir la collecte de mémoire native avec de telles structures globales?

Les activités ont une méthode onPause/onDestroy() qui est appelée à la destruction, mais la classe Application n'a pas d'équivalent. Quel mécanisme est recommandé pour garantir que les structures globales (en particulier celles contenant des références à la mémoire native) sont correctement collectées lorsque l’application est supprimée ou que la pile de tâches est placée en arrière-plan?

13
user608578

Il vous suffit de définir un nom d’application comme ci-dessous, qui fonctionnera:

<application
  Android:name="ApplicationName" Android:icon="@drawable/icon">
</application>
5
Anand

Si certaines variables sont stockées dans sqlite et que vous devez les utiliser dans la plupart des activités de votre application. alors Application peut-être le meilleur moyen d'y parvenir. Interrogez les variables de la base de données au démarrage de l'application et stockez-les dans un champ. Ensuite, vous pouvez utiliser ces variables dans vos activités.

Alors, trouvez le bon chemin, et il n’ya pas de meilleur moyen.

4
user716653

Comme il a été discuté ci-dessus, le système d'exploitation peut tuer l'application sans aucune notification (il n'y a pas d'événement onDestroy). Il n'y a donc aucun moyen de sauvegarder ces variables globales.

SharedPreferences pourrait être une solution SAUF que vous avez des variables COMPLEX STRUCTURED (dans mon cas, j'avais un tableau entier pour stocker les identifiants que l'utilisateur a déjà gérés). Le problème des SharedPreferences est qu’il est difficile de stocker et de récupérer ces structures à chaque fois que les valeurs requises sont nécessaires.

Dans mon cas, j'avais un SERVICE d'arrière-plan afin de pouvoir y déplacer ces variables et, étant donné que le service avait un événement onDestroy, je pouvais sauvegarder facilement ces valeurs.

4
Adorjan Princz

Vous pouvez avoir un champ statique pour stocker ce type d'état. Ou mettez-la dans la ressource Bundle et restaurez-la à partir de onCreate (Bundle savedInstanceState). Assurez-vous simplement de bien comprendre le cycle de vie géré par l'application Android (par exemple, pourquoi login () est appelé lors d'un changement d'orientation du clavier).

3
yanchenko

DO N'T Utilisez une autre balise <application> dans le fichier manifeste. Vous devez juste modifier la balise <application> existante, ajoutez cette ligne Android:name=".ApplicationName" où, ApplicationName sera le nom de votre sous-classe (utilisez pour stocker global) que vous allez créer.

alors, finalement, votre balise N ET SEULEMENT<application> dans le fichier manifeste devrait ressembler à ceci: -

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

Vous pouvez le faire en utilisant deux approches:

  1. Utilisation de la classe d'application
  2. Utiliser les préférences partagées

  3. Utilisation de la classe d'application

Exemple:

class SessionManager extends Application{

  String sessionKey;

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

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

Vous pouvez utiliser la classe ci-dessus pour implémenter la connexion dans votre MainActivity comme ci-dessous. Le code ressemblera à ceci:

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

Cette méthode fonctionnera pour le stockage temporaire. Vous ne savez vraiment pas quand le système d'exploitation va tuer l'application, à cause du manque de mémoire. Lorsque votre application est en arrière-plan et que l'utilisateur navigue dans une autre application nécessitant plus de mémoire, votre application sera supprimée car le système d'exploitation accorde plus de priorité aux processus de premier plan qu'à l'arrière-plan. Par conséquent, votre objet d'application sera NULL avant que l'utilisateur ne se déconnecte. C'est pourquoi je recommande d'utiliser la deuxième méthode spécifiée ci-dessus.

  1. Utiliser les préférences partagées.

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

vous pouvez utiliser les préférences Intentions, Sqlite ou Shared. En ce qui concerne le stockage multimédia, comme les documents, les photos et les vidéos, vous pouvez créer les nouveaux fichiers.

1
Raju yourPepe

Le résultat de l'activité est appelé avant le CV. Alors déplacez votre vérification de connexion sur le CV et votre deuxième connexion peut être bloquée une fois que l'activité secomd a donné un résultat positif. On resume appelle à chaque fois, de sorte que personne ne craint de ne pas être appelé la première fois.

0
user3044482
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
Vaishali Sutariya

L’approche du sous-classement a également été utilisée par le cadre BARACUS. De mon point de vue sous-classe Application était destinée à fonctionner avec les cycles de vie d'Android; c'est ce que n'importe quel conteneur d'application fait. Au lieu d’avoir globals, j’enregistre les beans dans ce contexte et les laisse être injectés dans n’importe quelle classe gérable par le contexte. Chaque instance de haricot injecté est en fait un singleton.

Voir cet exemple pour plus de détails

Pourquoi faire du travail manuel si vous pouvez avoir tellement plus?

0
gorefest

Vous pouvez créer une classe qui étend la classe Application, puis déclarer votre variable en tant que champ de cette classe et en fournissant la méthode getter correspondante.

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

    synchronized public String getMyString {
        return str;
    }
}

Et puis pour accéder à cette variable dans votre activité, utilisez ceci:

MyApplication application = (MyApplication) getApplication();
String myVar = application.getMyString();
0
Amit Tiwari