Как объявить глобальные переменные в Android?


595

Я создаю приложение, которое требует входа в систему. Я создал основной и логин активности.

В основной 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.

Кто-нибудь знает, как установить переменную глобальную, чтобы избежать появления формы входа после того, как пользователь уже успешно прошел аутентификацию?


хороший учебник о том, как обрабатывать состояние активности с помощью набора
Дипак Свами

Ответы:


954

Я написал этот ответ еще в '09, когда Android был относительно новым, и в разработке Android было много не очень хорошо проработанных областей. В конце этого поста я добавил длинное дополнение, посвященное некоторой критике и подробному изложению философских разногласий, которые у меня возникли с использованием Singletons, а не подкласса Application. Прочитайте это на свой страх и риск.

ОРИГИНАЛЬНЫЙ ОТВЕТ:

Более общая проблема, с которой вы сталкиваетесь, заключается в том, как сохранить состояние нескольких операций и всех частей вашего приложения. Статическая переменная (например, синглтон) является распространенным способом достижения Java. Однако я обнаружил, что более элегантный способ в Android - связать ваше состояние с контекстом приложения.

Как вы знаете, каждое действие также является контекстом, который представляет собой информацию о его среде выполнения в самом широком смысле. Ваше приложение также имеет контекст, и 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;
  }
}

Теперь эта медленная инициализация (например, попадание на диск, попадание в сеть, все, что блокируется и т. Д.) Будет выполняться каждый раз, когда создается экземпляр приложения! Вы можете подумать, ну, это только один раз для процесса, и я все равно должен буду оплатить стоимость, верно? Например, как упоминает Дайан Хэкборн ниже, вполне возможно, что ваш процесс будет инстанцирован - просто - обработать событие фоновой трансляции. Если ваша обработка широковещания не нуждается в этом состоянии, вы, возможно, просто выполнили целый ряд сложных и медленных операций даром. Ленивый экземпляр - это название игры здесь. Ниже приведен несколько более сложный способ использования приложения, который имеет больше смысла для всего, кроме простейшего использования:

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. Кроме того, как прокомментировал антивирус, для правильной привязки переопределения вашего приложения к вашему приложению необходим тег в файле манифеста. Опять же, смотрите документацию по Android для получения дополнительной информации. Пример:

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

ПРИМЕЧАНИЕ 2: user608578 спрашивает ниже, как это работает с управлением жизненными циклами собственных объектов. Я не в курсе использования нативного кода с Android в малейшей степени, и я не квалифицирован, чтобы ответить, как это будет взаимодействовать с моим решением. Если у кого-то есть ответ на этот вопрос, я хочу отдать ему должное и разместить информацию в этом сообщении для максимальной наглядности.

ДОПОЛНЕНИЕ:

Как отмечают некоторые люди, это не решение для постоянного состояния, что я, возможно, должен был бы подчеркнуть больше в первоначальном ответе. Т.е. это не означает решение для сохранения пользовательской или другой информации, которая должна сохраняться в течение всего времени жизни приложения. Таким образом, я считаю, что большинство нижеприведенных критических замечаний касаются уничтожения приложений в любое время и т. Д., Спорных, поскольку все, что когда-либо требовалось сохранять на диск, не должно храниться через подкласс приложения. Он предназначен для хранения временного, легко воссоздаваемого состояния приложения (например, вошел ли пользователь в систему) и компонентов, которые являются единым экземпляром (например, диспетчер сети приложения) ( НЕ singleton!).

Дайерман был достаточно любезен, чтобы отметить интересную беседу с Рето Мейером и Дайанной Хэкборн, в которой использование подклассов Application не рекомендуется в пользу шаблонов Singleton. Соматик также указывал на что-то подобное ранее, хотя я не видел этого в то время. Из-за роли Рето и Дайанны в поддержке платформы Android я не могу добросовестно рекомендовать игнорировать их советы. То, что они говорят, идет. Я хочу не согласиться с мнениями, высказанными в отношении предпочтения синглтонам над подклассами приложений. В своем несогласии я буду использовать концепции, лучше всего объясненные в этом объяснении StackExchange шаблона проектирования Singleton, так что мне не нужно определять термины в этом ответе. Я настоятельно рекомендую просмотреть ссылку, прежде чем продолжить. Точка за точкой:

Дайанн заявляет: «Нет причин для создания подкласса в Application. Это ничем не отличается от создания синглтона ...». Это первое утверждение неверно. Для этого есть две основные причины. 1) класс Application обеспечивает лучшую пожизненную гарантию для разработчика приложения; он гарантированно имеет срок действия приложения. Синглтон не привязан ТОЛЬКО к времени жизни приложения (хотя это эффективно). Это может быть не проблема для вашего среднего разработчика приложений, но я бы сказал, что это именно тот тип контракта, который должен предлагать Android API, и он также обеспечивает гораздо большую гибкость для системы Android, сводя к минимуму время жизни связанных данные. 2) Класс Application предоставляет разработчику приложения один держатель экземпляра для состояния, который очень отличается от одного держателя штата. Список различий см. По ссылке выше.

Дайанн продолжает: «... в будущем вы, вероятно, пожалеете о том, что ваш объект Application станет таким большим запутанным беспорядком, который должен быть независимой логикой приложения». Это, конечно, не правильно, но это не причина для выбора Singleton вместо Application подкласса. Ни один из аргументов Дианы не дает оснований полагать, что использование Singleton лучше, чем подкласс Application, все, что она пытается установить, это то, что использование Singleton не хуже, чем подкласс Application, что я считаю ложным.

Она продолжает: «И это более естественно приводит к тому, как вы должны управлять этими вещами - инициализировать их по требованию». Это игнорирует тот факт, что нет никакой причины, по которой вы не можете инициализировать по требованию, также используя подкласс Application. Опять нет разницы.

Дайанн заканчивает словами: «Фреймворк имеет тонны и тонны синглетонов для всех небольших общих данных, которые он поддерживает для приложения, таких как кеши загруженных ресурсов, пулы объектов и т. Д. Он прекрасно работает». Я не утверждаю, что использование Singletons не может работать нормально или не является законной альтернативой. Я утверждаю, что Singletons не обеспечивают такой сильный контракт с системой Android, как использование подкласса Application, и, кроме того, использование Singletons обычно указывает на негибкий дизайн, который нелегко модифицировать, и который ведет ко многим проблемам в будущем. ИМХО, сильный контракт, который Android API предлагает разработчикам приложений, является одним из наиболее привлекательных и приятных аспектов программирования на Android и помог привести к скорейшему принятию разработчика, что привело к успеху платформы Android на сегодняшний день.

Дайан также прокомментировала ниже, упомянув о дополнительном недостатке использования подклассов приложения, они могут способствовать или облегчать написание кода с меньшей производительностью. Это очень верно, и я отредактировал этот ответ, чтобы подчеркнуть важность рассмотрения здесь и правильного подхода, если вы используете подклассы Application. Как утверждает Дайанна, важно помнить, что ваш класс Application будет создаваться каждый раз при загрузке вашего процесса (может быть несколько раз за раз, если ваше приложение выполняется в нескольких процессах!), Даже если процесс загружается только для фоновой трансляции событие. Поэтому важно использовать класс Application больше как хранилище для указателей на общие компоненты вашего приложения, а не как место для какой-либо обработки!

Я оставляю вам следующий список недостатков для Singletons, украденный по предыдущей ссылке StackExchange:

  • Невозможность использования абстрактных или интерфейсных классов;
  • Невозможность подкласса;
  • Высокая связь по приложению (трудно изменить);
  • Трудно проверить (не может подделать / издеваться в юнит-тестах);
  • Сложно распараллелить в случае изменяемого состояния (требует обширной блокировки);

и добавить свой собственный:

  • Неясный и неуправляемый пожизненный контракт, не подходящий для разработки Android (или большинства других);

93
Спасибо, Сонил, такие ответы - вот причина, почему я так сильно люблю переполнение стека. ОТЛИЧНАЯ РАБОТА!
JohnnyLambada

5
Для тех, кто интересуется, как «указать этот класс в теге приложения в вашем манифесте», на момент написания этой статьи есть два других ответа на этот вопрос, которые описывают, как это сделать (используйте android: name), один от ebuprofen и один Майк Браун.
Тайлер Кольер

9
Вскоре ваш ответ правильный, но не могли бы вы заметить, что мы должны добавить <application android: name = ". MyApp" ... /> в файл манифеста Android?
Антифафе

12
Позвольте мне повторить еще раз, вы не должны использовать приложение для глобальных. Это бесполезно, не дает никаких преимуществ по сравнению с одиночными, и может быть активно вредным, например, ухудшать производительность запуска вашего процесса. Во время создания приложения вы не знаете, для чего создается ваш процесс. При ленивой инициализации синглетонов по мере необходимости вам нужно только выполнять работу, которая необходима. Например, если ваш процесс запускается для обработки широковещательной передачи о каком-либо фоновом событии, нет никакой причины инициализировать какое-либо глобальное состояние, необходимое для вашего пользовательского интерфейса.
hackbod

14
Кроме того, давайте будем действительно ясны - все ваши аргументы против синглетонов совершенно верны, когда мы говорим о ситуациях, когда вы фактически выбираете между синглтоном и другим подходом, который не является глобальным; синглтоны - это глобалы со всеми предостережениями о глобалах, которые применимы. Тем не менее, приложение также является синглтоном . Вы не избежите этих проблем, переключившись на подклассы Application, приложение точно такое же, как singleton (но хуже), оно просто позволяет вам обмануть себя тем, что вы делаете что-то более чистое. Но ты не.
hackbod

153

Создать этот подкласс

public class MyApp extends Application {
  String foo;
}

В AndroidManifest.xml добавьте android: имя

пример

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

1
Спасибо за это. Мне было интересно, как объявить это в манифесте
кто-то где-то

3
Чтобы это работало на меня, мне пришлось убрать "." в ".MyApp"
кто-то где-то

3
просто объявите это после основного действия, иначе это может не установить / развернуть
sami

11
просто хочу сказать, это идет в теге приложения MAIN, который уже есть ... это не второй :) пришлось учиться трудным путем.
Bwoogie

java.lang.IllegalAccessException: access to class is not allowed
Хищник

142

Предложенный Soonil способ сохранения состояния для приложения хорош, однако у него есть один слабый момент - бывают случаи, когда ОС убивает весь процесс приложения. Вот документация по этому вопросу - Процессы и жизненные циклы .

Рассмотрим случай - ваше приложение переходит в фоновый режим, потому что кто-то вам звонит (приложение «Телефон» сейчас на переднем плане). В этом случае && при некоторых других условиях (проверьте приведенную выше ссылку, какими они могут быть) ОС может уничтожить процесс вашего приложения, включая Applicationэкземпляр подкласса. В результате государство теряется. Когда вы позже вернетесь к приложению, ОС восстановит свой стек активности и Applicationэкземпляр подкласса, однако myStateполе будет null.

AFAIK, единственный способ гарантировать безопасность состояния состоит в том, чтобы использовать любой тип сохранения состояния, например, используя приватный файл приложения или SharedPrefernces(в конечном итоге он использует приватный файл приложения во внутренней файловой системе).


10
+1 за сохранение с SharedPreferences; Вот как я это сделал. Я нахожу странным злоупотреблять системой предпочтений для сохраненного состояния, но она работает настолько хорошо, что проблема становится просто вопросом терминологии.
Cheezmeister

1
Не могли бы вы опубликовать код (или предоставить ссылку на объяснение) о том, как SharedPreferences используется для решения проблемы, которую описывает Архимед
Кто-то где-то

2
Предпочтения, база данных, сериализация файлов и т. Д. Каждое действие может поддерживать состояние, если они используют onSaveInstanceState, но это не поможет, если пользователь выходит из действия и удаляет его из стека истории, принудительно закрывает или выключает свое устройство. ,
Даррен Хиндерер

1
Такое поведение очень раздражает - было бы не так плохо, если бы вызывался метод onTerminate () вашего приложения, чтобы вы могли элегантно справиться с ситуацией.
Дин Уайлд,

2
Это правильный ответ на мой взгляд. Это ошибка - полагаться на один и тот же экземпляр приложения, существующий в разных видах деятельности. По моему опыту, для Android вполне свойственно полностью разорвать и воссоздать весь процесс, пока вы находитесь на заднем плане. Нахождение на заднем плане может означать запуск камеры, браузера или получение телефонного звонка.
Джаред Келлс,

26

Просто записка ..

Добавить:

android:name=".Globals"

или как вы назвали свой подкласс для существующего <application> тега. Я продолжал пытаться добавить еще один <application>тег в манифест и получал исключение.


Привет, Гимбл. У меня такая же проблема. У меня также был собственный тег <application>, и при попытке добавить другой тег <application> у меня возникла та же проблема, что и у вас (сообщение об исключении). Но я сделал то, что вы упомянули, и это не сработало. Я добавляю android: name = ". GlobalClass" в мой тег <application>, но он не работает. Можете ли вы полностью объяснить, как вы решили это ??
Соня

3
Хорошо <manifest> <application android: name = ". GlobalData"> </ application> </ manifest>. Неверный <manifest> <application> </ application> <application android: name = ". GlobalData"> </ application> </ manifest>
Gimbl

13

Я также не смог найти, как указать тег приложения, но после большого количества поисков в Google это стало очевидно из документов файла манифеста: используйте android: name, в дополнение к значку и метке по умолчанию в разделе приложения.

android: name Полное имя подкласса приложения, реализованное для приложения. Когда процесс приложения запускается, этот класс создается перед любым из компонентов приложения.

Подкласс является необязательным; большинству приложений он не нужен. В отсутствие подкласса Android использует экземпляр базового класса Application.


13

Как насчет обеспечения сбора нативной памяти такими глобальными структурами?

У действий есть onPause/onDestroy()метод, который вызывается после уничтожения, но у класса Application нет эквивалентов. Какой механизм рекомендуется для обеспечения того, чтобы глобальные структуры (особенно те, которые содержат ссылки на собственную память) надлежащим образом собирались мусором, когда приложение либо уничтожается, либо стек задач помещается в фоновый режим?


1
Очевидное решение - реализовать интерфейс Closeable для ваших объектов, отвечающих за собственные ресурсы, и обеспечить управление ими с помощью оператора try-with-resources или чего-то еще. В худшем случае вы всегда можете использовать финализатор объекта.
скоро

5

Просто вам нужно определить имя приложения, как показано ниже, которое будет работать:

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

4

Как было сказано выше, ОС может уничтожить APPLICATION без какого-либо уведомления (нет события onDestroy), поэтому нет способа сохранить эти глобальные переменные.

SharedPreferences может быть решением, за исключением того, что у вас есть переменные COMPLEX STRUCTURED (в моем случае у меня был целочисленный массив для хранения идентификаторов, которые пользователь уже обработал). Проблема с SharedPreferences заключается в том, что трудно хранить и извлекать эти структуры каждый раз, когда требуются значения.

В моем случае у меня был фоновый СЕРВИС, чтобы я мог переместить эти переменные туда, и, поскольку у службы есть событие onDestroy, я мог бы легко сохранить эти значения.


onDestroy () не гарантированно вызывается даже для службы.
Изучай OpenGL ES

Да, это может произойти, но только в случае критических ситуаций.
Adorjan Princz

4

Если некоторые переменные хранятся в sqlite, и вы должны использовать их в большинстве действий в вашем приложении. Тогда приложение может быть лучшим способом для достижения этого. Запросите переменные из базы данных при запуске приложения и сохраните их в поле. Затем вы можете использовать эти переменные в своей деятельности.

Так что найдите правильный путь, и лучшего пути не существует.


3

Вы можете иметь статическое поле для хранения такого рода состояния. Или поместите его в ресурс Bundle и восстановите оттуда на onCreate (Bundle saveInstanceState). Просто убедитесь, что вы полностью понимаете управляемый жизненный цикл приложения Android (например, почему login () вызывается при изменении ориентации клавиатуры).


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

1

Вы можете использовать Intents, 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.
}

Этот метод будет работать для временного хранения. Вы действительно не представляете, когда операционная система убьет приложение, из-за нехватки памяти. Когда ваше приложение работает в фоновом режиме, а пользователь перемещается по другому приложению, для работы которого требуется больше памяти, ваше приложение будет убито, поскольку операционная система отдает приоритет приоритетным процессам, а не фону. Следовательно, ваш объект приложения будет нулевым, прежде чем пользователь выйдет из системы. Следовательно для этого я рекомендую использовать второй метод, указанный выше.

  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

По результатам деятельности вызывается раньше по резюме. Поэтому перенесите проверку входа в систему на резюме, и ваш второй вход в систему может быть заблокирован, как только операция secomd вернула положительный результат. О резюме вызывается каждый раз, поэтому не стоит беспокоиться, что его не вызовут с первого раза.


0

Подклассы также использовались в среде BARACUS. С моей точки зрения, приложение подклассов предназначено для работы с жизненными циклами Android; это то, что делает любой контейнер приложений. Вместо того, чтобы иметь глобальные переменные, я регистрирую 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класс, а затем объявить вашу переменную как поле этого класса и предоставить для него метод получения.

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

    synchronized public String getMyString {
        return str;
    }
}

И затем, чтобы получить доступ к этой переменной в вашей деятельности, используйте это:

MyApplication application = (MyApplication) getApplication();
String myVar = application.getMyString();
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.