Singleton vs. контекст програми в Android?


362

Згадуючи цю публікацію, що перераховує декілька проблем використання одиночних клавіш та побачивши кілька прикладів програм для Android, що використовують шаблон однотонної форми, мені цікаво, чи корисно використовувати Singletons замість поодиноких екземплярів, що передаються через глобальний стан програми (підкласифікація android.os.Application і отримання її) через context.getApplication ()).

Які переваги / недоліки мали б обидва механізми?

Якщо чесно, я чекаю такої ж відповіді в цій публікації Singleton з веб-додатком. Не дуже гарна ідея! але застосовано до Android. Я прав? Що відрізняється від DalvikVM в іншому випадку?

EDIT: Я хотів би мати думки щодо декількох аспектів:

  • Синхронізація
  • Багаторазовість
  • Тестування

Відповіді:


295

Я дуже не згоден з відповіддю Діанні Хакборн. Ми потроху видаляємо всі одиночні кнопки з нашого проекту на користь легких об'єктів, що охоплюють завдання, які можна легко створити, коли вони вам справді потрібні.

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

Я вважаю це антидіаграмою, це поганий об'єктно-орієнтований стиль, який по суті становить підтримку глобального стану.

Щоб повернутися до свого питання:

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


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

18
Щоправда, і я зазначив, що "контекст програми можна вважати синглом". Різниця полягає в тому, що з екземпляром програми стріляти в ногу набагато складніше, оскільки її життєвий цикл керується рамками. Рамки DI, такі як Guice, Hivemind або Spring, також використовують одиночні кнопки, але це детальна інформація про впровадження, яку розробник не повинен хвилювати. Я думаю, що взагалі безпечніше покладатися на правильну реалізацію семантики фреймів, а не на власний код. Так, я визнаю, що це роблю! :-)
Маттіас

93
Чесно кажучи, це не заважає тобі більше стріляти в ногу, ніж одинак. Це трохи заплутано, але немає життєвого циклу застосування. Він створюється, коли ваш додаток запускається (до того, як будь-який з його компонентів буде створено миттєво) і його onCreate () викликається в цей момент, і ... це все. Він сидить там і живе вічно, поки процес не буде вбитий. Так само, як сингл. :)
hackbod

30
О, одне, що може бентежити це - Android дуже розроблений навколо запуску програм у процесах та управління життєвим циклом цих процесів. Отже, для синглонів Android є дуже природним способом скористатись цим керуванням процесом - якщо ви хочете щось кешувати у своєму процесі, поки платформі не потрібно буде відновити пам'ять процесу на щось інше, введення цього стану в синглтон зробить це.
hackbod

7
Гаразд, досить справедливо. Я можу лише сказати, що я не озирнувся з тих пір, як ми зробили крок від самоврядування одиноких. Зараз ми вибираємо легке рішення в стилі DI, де ми зберігаємо один заводський однотонний (RootFactory), який, в свою чергу, керується екземпляром програми (якщо це буде делегат). Цей сингл управляє загальними залежностями, на які покладаються всі компоненти додатків, але інстанція керується в одному місці - класі додатків. Хоча при такому підході залишається один сингтон, він обмежений класом Application, тому жоден інший модуль коду не знає про цю "деталь".
Маттіас

231

Я дуже рекомендую одиночні. Якщо у вас є синглтон, який потребує контексту, майте:

MySingleton.getInstance(Context c) {
    //
    // ... needing to create ...
    sInstance = new MySingleton(c.getApplicationContext());
}

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

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

Крім того, для простих додатків багатопотоковість не стає проблемою для одиночних кнопок, оскільки, за задумом, всі стандартні зворотні виклики до програми надсилаються в основний потік процесу, тому у вас не буде багатопотокової роботи, якщо ви не введете це явно через потоки або неявно, публікуючи постачальник контенту або послугу IBinder в інших процесах.

Просто подумайте, що ви робите. :)


1
Якщо через деякий час я захочу прослухати зовнішню подію або поділитися на IBinder (я думаю, це не було б простою програмою), мені доведеться додати подвійне блокування, синхронізацію, мінливі, правда? Дякую за вашу відповідь :)
mschonaker

2
Не для зовнішньої події - BroadcastReceiver.onReceive () також викликається в основному потоці.
хакбод

2
Добре. Чи хотіли б ви вказати мені якийсь матеріал для читання (я вважаю за краще код), де я можу побачити основний механізм диспетчеризації потоку? Я думаю, що це прояснить декілька понять одночасно для мене. Заздалегідь спасибі.
mschonaker

2
Це основний код диспетчеризації додатка: android.git.kernel.org/?p=platform/frameworks/…
hackbod

8
Немає нічого по суті неправильного в використанні одинакових. Просто використовуйте їх правильно, коли це має сенс. .. впевнений, точно, добре сказано. На Android-платформі насправді їх багато, для того щоб підтримувати кеш-пам’яті завантажених ресурсів та інші подібні речі. Точно так, як ви кажете. Від ваших друзів у світі iOS "все є синглтон" в iOS. На фізичних пристроях нічого не може бути більш природним, ніж поняття синглтон: gps, годинник, гіроскоп і т. Д. - концептуально, як би ви не інженеру це, ніж як одиночні? Так що так.
Fattie

22

Від: Розробник> посилання - Додаток

Зазвичай немає необхідності в підкласі Application. У більшості випадків статичні одинаки можуть надавати однакові функціональні можливості більш модульним способом. Якщо вашому синглтону потрібен глобальний контекст (наприклад, для реєстрації приймачів широкомовної передачі), функція для його отримання може бути надана Контексту, який внутрішньо використовує Context.getApplicationContext () під час першої побудови сингтона.


1
І якщо ви пишете інтерфейс для одиночного, залишаючи getInstance нестатичним, ви навіть можете зробити конструктор за замовчуванням класу, що використовує синглтон, ввести виробництво синглтона через конструктор, що не використовується за замовчуванням, який також є конструктором, який ви використовуєте для створення креативу клас одинарного використання у своїх одиничних тестах.
android.weasel

11

Додаток не такий, як Singleton. Причини:

  1. Метод програми (наприклад, onCreate) викликається у потоці ui;
  2. метод одиночного можна викликати в будь-якій нитці;
  3. У методі "onCreate" програми ви можете інстанціювати обробник;
  4. Якщо синглтон виконується в none-ui потоці, ви не зможете інстанціювати обробник;
  5. У додатку є можливість керувати життєвим циклом діяльності в додатку. Він має метод "registerActivityLifecycleCallbacks". Але одиночні кнопки не мають можливості.

1
Примітка. Ви можете інстанціювати обробник на будь-якій темі. від doc: "Коли ви створюєте новий обробник, він прив'язується до черги потоку / повідомлення нитки, яка його створює"
Христос,

1
@Christ Дякую! Просто зараз я дізнався "механізм Looper". Якщо екземпляр обробника в потоці none-ui без коду "Looper.prepare ()", система повідомить про помилку "java.lang.RuntimeException: Можна не створіть обробник всередині потоку, який не викликав Looper.prepare () ".
sunhang

11

У мене була така ж проблема: Singleton чи зробити підклас android.os.Application?

Спершу я спробував з Singleton, але моя програма в якийсь момент телефонує в браузер

Intent myIntent = new Intent(Intent.ACTION_VIEW, Uri.parse("http://www.google.com"));

і проблема полягає в тому, що якщо у телефону не вистачає пам’яті, більшість ваших класів (навіть Singletons) очищаються, щоб отримати деяку кількість пам’яті, тому, повертаючись із браузера до мого додатка, він ламається щоразу.

Рішення: розмістити потрібні дані всередині підкласу класу Application.


1
Я часто натрапляв на пости, де народи заявляють, що це може статися. Тому я просто приєдную до програми такі об’єкти, як сингли, з ледачим завантаженням і т. Д. Просто для того, щоб переконатися, що життєвий цикл задокументований і відомий. Просто не забудьте зберегти сотні зображень у вашому додатку, оскільки я розумію, що це не буде очищено з пам'яті, якщо ваша програма знаходиться у фоновому режимі, а всі дії знищені, щоб звільнити пам'ять для інших процесів.
Януш

Що ж, ледаче завантаження Singleton після перезавантаження програми не є правильним способом дозволити GC до об'єктів. Слабкіреференції є, правда?
mschonaker

15
Дійсно? Далвік вивантажує класи та втрачає стан програми? Ви впевнені, що це не так, що це збирання сміття таку кількість об'єктів, що стосуються обмеженого життєвого циклу, які ви не повинні в першу чергу ставити в одиночні кнопки? Ви повинні навести приклади кластерів для такої надзвичайної претензії!
android.weasel

1
Якщо не було змін, про які я не знаю, Далвік не розвантажує заняття. Колись. Поведінка, яку вони бачать, - це їх процес вбивати на задньому плані, щоб звільнити місце для браузера. Вони, ймовірно, ініціалізували змінну в своїй "основній" діяльності, яка, можливо, не була створена в новому процесі при поверненні з браузера.
Groxx

5

Розглянемо обидва одночасно:

  • маючи одиночні об'єкти як статичні екземпляри всередині класів.
  • мати загальний клас (контекст), який повертає екземпляри синглтона для всіх об'єктів singelton у вашій програмі, що має перевагу в тому, що імена методів у Context матимуть значення, наприклад: context.getLoggedinUser () замість User.getInstance ().

Крім того, я пропоную розширити свій контекст, щоб він включав не лише доступ до однотонних об'єктів, але й деякі функції, до яких потрібно отримати доступ у глобальному масштабі, наприклад: context.logOffUser (), context.readSavedData () тощо. Ймовірно, перейменуючи контекст на Фасад тоді мав би сенс.


4

Вони насправді такі самі. Я бачу одну різницю. За допомогою класу Application ви можете ініціалізувати свої змінні в Application.onCreate () та знищити їх у Application.onTerminate (). З одиночним ви повинні покластися на ініціалізацію та знищення VM статики.


16
документи для onTerminate кажуть, що емулятор викликає його лише коли-небудь. На пристроях цей метод, ймовірно, не буде викликаний. developer.android.com/reference/android/app/…
день

3

Мої 2 копійки:

Я помітив, що деякі однотонні / статичні поля були скинуті, коли моя діяльність була знищена. Я помітив це на деяких пристроях низького рівня 2.3.

У мене випадок був дуже простим: у мене просто приватний файл "init_done" і статичний метод "init", який я викликав з Activity.onCreate (). Я зауважую, що метод init відновлював себе деяким відновленням діяльності.

Хоча я не можу довести своє твердження, це може бути пов'язано з тим, КОЛИ вперше був створений / використаний сингл / клас. Коли діяльність знищується / переробляється, здається, що всі класи, на які посилається лише ця діяльність, також переробляються.

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

Я сподіваюся, що це може комусь допомогти.


3

З прислів’я коня з вуст ...

Розробляючи свою програму, вам може знадобитися обмін даними, контекстом або послугами в усьому світі через вашу програму. Наприклад, якщо у вашій програмі є дані сеансу, такі як наразі користувач, який увійшов, ви, ймовірно, захочете викрити цю інформацію. В системі Android для вирішення цієї проблеми необхідно, щоб ваш екземпляр android.app.Application володів усіма глобальними даними, а потім розглядав ваш екземпляр програми як одиночний з статичними аксесуарами до різних даних і служб.

Коли ви пишете додаток для Android, ви гарантовано матимете лише один екземпляр класу android.app.Application, і тому безпечно (і рекомендується командою Google Android) трактувати його як одиноку. Тобто ви можете сміливо додати статичний метод getInstance () до своєї програми. Так:

public class AndroidApplication extends Application {

    private static AndroidApplication sInstance;

    public static AndroidApplication getInstance(){
        return sInstance;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        sInstance = this;
    }
}

2

Моя активність дзвінків закінчує () (що не дозволяє закінчити її одразу, але в кінцевому підсумку) і викликає Google Street Viewer. Коли я налагоджую це на Eclipse, моє з'єднання з додатком розривається, коли викликається програма перегляду вулиць, яку я розумію як закриту (цілу) програму, нібито звільняючи пам’ять (оскільки закінчення однієї діяльності не повинно спричиняти такої поведінки) . Тим не менш, я можу зберегти стан у Bundle через onSaveInstanceState () і відновити його в методі onCreate () наступної активності в стеку. Або за допомогою статичного однодонового або додаткового підкласифікації я стикаюся із закриттям програми та втратою програми (якщо я не зберігаю її в пакеті). Тож з мого досвіду вони однакові щодо збереження держави. Я помітив, що зв’язок втрачено в Android 4.1.2 та 4.2.2, але не в 4.0.7 або 3.2.4,


"Я помітив, що з'єднання втрачається в Android 4.1.2 і 4.2.2, але не в 4.0.7 або 3.2.4, що, на мій розуміння, говорить про те, що механізм відновлення пам'яті змінився в якийсь момент". ..... Я думаю, що на ваших пристроях не буде встановлено однакового обсягу пам’яті, а також того ж додатка. і тому ваш висновок може бути невірним
Христос

@Christ: Так, ви повинні мати рацію. Було б дивно, якби механізм відновлення пам'яті змінювався між версіями. Можливо, різне використання пам'яті спричинило різну поведінку.
Піовезан
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.