Як оголосити глобальні змінні в 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.

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


хороший підручник про те, як обробляти стан активності за допомогою збереженого пакета стану екземпляра quicktips.in/…
Deepak Swami

Відповіді:


954

Цю відповідь я написав ще у09 році, коли Android був порівняно новим, і в розробці Android було багато не добре сформованих областей. Я додав довге доповнення в нижній частині цього поста, торкаючись деякої критики та деталізуючи філософську незгоду, яку я маю з використанням Singletons, а не з додатком підкласифікації. Прочитайте його на свій страх і ризик.

ОРИГІНАЛЬНИЙ ВІДПОВІДЬ:

Більш загальна проблема, з якою ви стикаєтесь - це збереження стану в кількох видах діяльності та всіх частинах вашої програми. Статична змінна (наприклад, синглтон) є загальним способом досягнення цього 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. Соматік також зазначив щось подібне раніше, хоча я цього не бачив. Через ролі Рето та Діанна в підтримці платформи Android я не можу добросовісно рекомендувати ігнорувати їхні поради. Те, що вони кажуть, іде. Я не хочу погоджуватися з думками, висловленими щодо переваги Singleton перед підкласами Application. В свою незгоду я буду використовувати поняття, найкраще пояснені в цьому поясненні StackExchange схеми дизайну Singleton, так що мені не доведеться визначати терміни у цій відповіді. Я настійно закликаю прокрутити посилання, перш ніж продовжувати. По пункту:

Діанна заявляє: "Немає підстав для підкласу з Application. Це нічим не відрізняється від складання одиночного ..." Ця перша претензія є неправильною. Для цього є дві основні причини. 1) Клас програми забезпечує кращу довічну гарантію для розробника програми; гарантовано тривалість роботи програми. Однотонний НЕ ЕКСПЛІКАТНО пов'язаний із терміном служби програми (хоча це ефективно). Це може бути проблемою для середнього розробника додатків, але я заперечую, що це саме той тип контрактів, який має пропонувати API для Android, і він забезпечує набагато більшу гнучкість і для системи Android, скорочуючи термін експлуатації пов'язаних дані. 2) Клас програми надає розробнику програми єдиний власник екземпляра для стану, що сильно відрізняється від державника-власниці Сінглтона. Для переліку відмінностей див. Посилання на пояснення Singleton вище.

Діанна продовжує: "... це, ймовірно, буде щось, про що ви пошкодуєте в майбутньому, коли ви виявите, що ваш об'єкт Application стає таким великим заплутаним безладом того, що має бути незалежною логікою програми". Це, звичайно, невірно, але це не є причиною вибору Singleton над підкласом Application. Жоден з аргументів Діани не дає причини, що використання Singleton краще, ніж підклас Application, все, що вона намагається встановити, полягає в тому, що використання Singleton не гірше ніж підклас Application, який, на мою думку, є помилковим.

Вона продовжує: "І це природніше призводить до того, як ви повинні керувати цими речами - ініціалізуючи їх на вимогу". Це ігнорує той факт, що немає причини, щоб ви не могли ініціалізувати на вимогу, використовуючи також підклас програми. Знову немає різниці.

Діанна закінчується "У самій структурі є тонни і тони синглів для всіх малопоширених даних, які вони підтримують для додатка, таких як кеші завантажених ресурсів, пули об'єктів тощо. Це чудово працює". Я не стверджую, що використання Singletons не може спрацювати нормально або не є законною альтернативою. Я стверджую, що Singletons не забезпечує такий міцний контракт із системою Android, як використання підкласу Application, і, крім того, що використання Singletons, як правило, вказує на негнучку конструкцію, яка не легко змінюється, і призводить до багатьох проблем у дорозі. IMHO, сильний контракт, який Android API пропонує розробникам додатків, є одним із найпривабливіших та найприємніших аспектів програмування з Android, а також допоміг до раннього прийняття розробника, що призвело платформу Android до успіху, який вона має сьогодні.

Дайанн також прокоментував нижче, згадуючи додатковий недолік використання підкласів програми, вони можуть заохочувати або полегшувати написання менш ефективного коду. Це дуже вірно, і я відредагував цю відповідь, щоб підкреслити важливість розгляду тут перф і правильного підходу, якщо ви використовуєте підкласинг додатків. Як зазначає Діанне, важливо пам’ятати, що ваш клас програми буде інстанціюватися кожного разу, коли ваш процес завантажується (може бути кілька разів одночасно, якщо ваша програма працює в декількох процесах!), Навіть якщо процес завантажується лише для фонового мовлення подія. Тому важливо використовувати клас Application більше як сховище для покажчиків на спільні компоненти вашої програми, а не як місце для будь-якої обробки!

Я залишаю вам наступний список мінусів для Singletons, викрадений з попереднього посилання StackExchange:

  • Неможливість використання абстрактних або інтерфейсних класів;
  • Нездатність підкласу;
  • Висока зв'язок у додатку (важко модифікувати);
  • Важко перевірити (не можна підробити / знущатися в одиницях тестів);
  • Важко паралелізувати у разі зміни стану (потребує великого блокування);

і додати моє власне:

  • Неясний і некерований довічний контракт, не підходить для розробки Android (або більшості інших);

93
Дякую Сооніл - саме такі відповіді є причиною, чому я так люблю Stack Overflow. ЧУДОВА РОБОТА!
JohnnyLambada

5
Для тих, хто цікавиться, як "вказати цей клас у тезі програми у вашому маніфесті", на даний момент написано ще два відповіді на це запитання, які описують, як це зробити (використовувати android: ім'я), один - ebuprofen і один Майк Браун.
Тайлер Колліер

9
Soonil, ваша відповідь правильна, але чи могли ви помітити, що ми повинні додати <application android: name = ". MyApp" ... /> у файл Android Manifest?
антикафе

12
Дозвольте ще раз повторити, ви не повинні використовувати програму для глобальних. Це не приносить користі, не дає переваг над однотонними та може бути активно шкідливим, наприклад, шкодити продуктивності запуску вашого процесу. На момент створення програми ви не маєте поняття, для чого створюється ваш процес. Шляхом лінивої ініціалізації одиночних клавіш потрібно лише виконувати необхідну роботу. Наприклад, якщо ваш процес запускається для обробки трансляції про якусь фонову подію, немає причин ініціалізувати все, що потрібно для вашого глобального стану.
hackbod

14
Крім того, давайте будемо по-справжньому зрозумілі - всі ваші аргументи проти одиночних клавіш цілком справедливі, коли ми говоримо про ситуації, коли ви насправді вибираєте між однотонним та іншим підходом, який не є глобальним; одинаки - глобалі, з усіма застереженнями щодо глобалів, які застосовуються. Однак додаток також є однотонним . Ви не уникаєте цих проблем, перейшовши на додаток для підкласингу, додаток точно такий же, як сингл (але ще гірше), він просто дозволяє вам обдурити, що ви робите щось більш чисто. Але ти - ні.
hackbod

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
просто оголосити його після основної діяльності, інакше він може не встановити / розгорнути
самі

11
просто хочу сказати, що це йде в ОСНОВНИЙ тег додатка, який вже є ... це хіба другий :), довелося навчитися важкому шляху.
bwoogie

java.lang.IllegalAccessException: access to class is not allowed
Раптор

142

Запропонований Соонілом спосіб збереження стану для програми є хорошим, проте він має одну слабку сторону - є випадки, коли ОС вбиває весь процес подання заявки. Ось документація щодо цього - Процеси та життєві цикли .

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

AFAIK, єдиний спосіб гарантувати безпеку держави - це використовувати будь-який вид збереження стану, наприклад, використання приватного для файлу програми або SharedPrefernces(зрештою, він використовує приватний для файлу програми у внутрішній файловій системі).


10
+1 за наполягання SharedPreferences; ось як я це бачив. Мені здається дивним зловживати системою уподобань для збереженого стану, але вона працює настільки добре, що питання стає лише питанням термінології.
Чезмайстер

1
Ви можете, будь ласка, опублікувати код (або надати посилання на пояснення) щодо того, як SharedPreferences використовується для вирішення проблеми, яку описує Архімед
Хтось десь

2
Налаштування, база даних, серіалізація файлів тощо. Кожна діяльність може підтримувати стан, якщо вона використовує onSaveInstanceState, але це не допоможе, якщо користувач відмовиться від активності та вилучить її зі стека історії, змусить закрити або вимкнути свій пристрій .
Даррен Хіндерер

1
Така поведінка дуже дратує - це було б не так вже й погано, якби закликали метод onTerminate () вашої заявки, щоб ви могли елегантно вирішувати ситуацію.
Дін Дикий

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

26

Просто примітка ..

додати:

android:name=".Globals"

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


Привіт, Гімбл. У мене була така ж проблема. У мене також був власний тег <application> і, коли я намагаюся додати ще один тег <application>, у мене була така ж проблема, як і ви (повідомлення про виключення). Але я зробив те, що ви згадали, і це не вийшло. Я додаю android: name = ". GlobalClass" до свого тегу <application>, але він не працює. Чи можете ви повністю пояснити, як ви це вирішили ??
Sonhja

3
Добре <manifest> <додаток android: name = ". GlobalData"> </application> </manifest>. Bad <manifest><application> </application> <application android: name = ". GlobalData"> </application> </manifest>
Gimbl

13

Я не міг знайти, як вказати тег додатків, але після багатьох Googling це стало очевидним із файлів маніфесту docs: use android: name, крім значка за замовчуванням та мітки в строфі програми.

android: name Повна кваліфікована назва підкласу програми, реалізованого для програми. Коли запускається процес застосування, цей клас інстанціюється перед будь-яким із компонентів програми.

Підклас необов’язковий; більшість додатків не знадобиться. За відсутності підкласу, Android використовує екземпляр базового класу Application.


13

А як щодо забезпечення збору рідної пам’яті з такими глобальними структурами?

Діяльність має onPause/onDestroy()метод, який викликається знищенням, але клас Application не має еквівалентів. Який механізм рекомендується забезпечити, щоб глобальні структури (особливо ті, що містять посилання на рідну пам’ять) сміття збирали належним чином, коли додаток або вбито, або стек завдань буде поставлений на задній план?


1
Очевидним рішенням є реалізація інтерфейсу Closeable для ваших об'єктів, відповідальних за рідні ресурси, і гарантувати, що ними керує оператор "пробний ресурс" чи щось інше. Найгірший випадок, коли ви завжди можете використовувати фіналізатор об'єктів.
sooniln

5

Просто потрібно визначити назву програми, як нижче, яка працюватиме:

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

4

Як там було обговорено вище, ОС може знищити ЗАСТОСУВАННЯ без будь-якого повідомлення (немає події onDestroy), тому немає можливості зберегти ці глобальні змінні.

SharedPreferences може бути рішенням ЗА винятком того, що у вас є КОМПЛЕКСНІ СТРУКТУРОВАНІ змінні (у моєму випадку я мав цілий масив для зберігання ідентифікаторів, якими користувач уже обробив). Проблема з SharedPreferences полягає в тому, що важко зберігати та витягувати ці структури щоразу, коли потрібні значення.

У моєму випадку я мав фоновий сервіс, щоб я міг перемістити ці змінні туди і тому, що служба має подію onDestroy, я міг легко зберегти ці значення.


onDestroy () не гарантовано називається навіть для послуги.
Дізнайтеся OpenGL ES

Так, це може статися, але лише у випадку критичних ситуацій.
Adorjan Princz

4

Якщо деякі змінні зберігаються в sqlite, і ви повинні використовувати їх у більшості видів діяльності у вашому додатку. то застосування, можливо, найкращий спосіб досягти цього. Запросити змінні з бази даних при запуску програми та зберегти їх у полі. Тоді ви можете використовувати ці змінні у своїй діяльності.

Тож знайдіть правильний шлях, а найкращого шляху немає.


3

Ви можете мати статичне поле для зберігання подібного стану. Або помістіть його до ресурсу Bundle і відновіть звідти на 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

ви можете користуватися налаштуваннями Intents, Sqlite або Shared. Якщо мова йде про медіа-сховище, як-от документи, фотографії та відео, ви можете створити нові файли.


1

Це можна зробити за допомогою двох підходів:

  1. Використання класу Application
  2. Використання загальних налаштувань

  3. Використання класу Application

Приклад:

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

За результатами діяльності називається раніше при резюме. Тож перенесіть реєстраційну перевірку на резюме, і ваше друге вхід може бути заблоковано, коли активність секому поверне позитивний результат. Про резюме викликають кожен раз, тому немає побоювань, щоб його не викликали вперше.


0

Підхід підкласифікації також використовувався в рамках BARACUS. З моєї точки зору, субкласифікація Приклад призначений для роботи з життєвими циклами Android; це те, що робить будь-який контейнер додатків. Замість того щоб мати глобальні топи, я реєструю боби в цьому контексті, і нехай вони вводяться в будь-який клас, керований контекстом. Кожен екземпляр бобів, що вводиться, насправді є однотонним.

Деталі див. У цьому прикладі

Навіщо робити ручну роботу, якщо у вас може бути стільки більше?


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();
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.