Синглтоны против контекста приложения в Android?


362

Вспоминая этот пост, перечисляя несколько проблем использования синглетонов и видя несколько примеров приложений Android, использующих шаблон синглтонов, мне интересно, будет ли хорошей идеей использовать одиночные экземпляры вместо отдельных экземпляров, совместно используемых через глобальное состояние приложения (создание подклассов android.os.Application и получение его). через context.getApplication ()).

Какие преимущества / недостатки будут у обоих механизмов?

Если честно, я ожидаю того же ответа в этом посте Шаблон Singleton с веб-приложением, Не очень хорошая идея! но применяется к Android. Я прав? Чем отличается DalvikVM от других?

РЕДАКТИРОВАТЬ: Я хотел бы иметь мнения по нескольким аспектам, связанным с:

  • синхронизация
  • Повторное использование
  • тестирование

Ответы:


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 () также вызывается в основном потоке.
hackbod

2
Ладно. Не могли бы вы указать мне какой-нибудь материал для чтения (я бы предпочел код), где я могу увидеть механизм распределения основного потока? Думаю, это прояснит сразу несколько концепций. Заранее спасибо.
mschonaker

2
Это основной код отправки на стороне приложения: android.git.kernel.org/?p=platform/frameworks/…
hackbod

8
В использовании синглетонов нет ничего плохого. Просто используйте их правильно, когда это имеет смысл. .. конечно, точно, хорошо сказано. Фреймворк Android на самом деле имеет их много, чтобы поддерживать кеширование загруженных ресурсов для каждого процесса и другие подобные вещи. Точно так, как вы говорите. От ваших друзей в мире iOS, «все является синглтоном» в iOS. Ничто не может быть более естественным для физических устройств, чем концепция синглтона: gps, часы, гироскопы и т. Д. - концептуально, как иначе вы бы их спроектировали? как синглтоны? Так что да.
Толстяк

22

От: Разработчик> Ссылка - Приложение

Обычно нет необходимости создавать подкласс Application. В большинстве случаев статические синглтоны могут предоставлять ту же функциональность более модульным способом. Если вашему синглтону необходим глобальный контекст (например, для регистрации широковещательных приемников), функции для его получения может быть задан контекст, который внутренне использует Context.getApplicationContext () при первом создании синглтона.


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

11

Приложение не совпадает с Singleton. Причины:

  1. Метод приложения (например, onCreate) вызывается в потоке пользовательского интерфейса;
  2. метод синглтона может быть вызван в любом потоке;
  3. В методе «onCreate» приложения вы можете создать экземпляр Handler;
  4. Если синглтон выполняется в потоке без пользовательского интерфейса, вы не можете создать экземпляр Handler;
  5. Приложение имеет возможность управлять жизненным циклом действий в приложении. У него есть метод «registerActivityLifecycleCallbacks». Но у синглетонов нет такой возможности.

1
Примечание: вы можете создать экземпляр Handler в любом потоке. из документа: «Когда вы создаете новый обработчик, он привязывается к потоку / очереди сообщений потока, который его создает»
Христос,

1
@Christ Спасибо! Только сейчас я изучил «механизм лупера». Если экземпляр обработчика в потоке без пользовательского интерфейса без кода «Looper.prepare ()», система сообщит об ошибке «java.lang.RuntimeException: Can создать обработчик внутри потока, который не вызвал Looper.prepare () ".
Sunhang

11

У меня была такая же проблема: синглтон или сделать подкласс android.os.Application?

Сначала я попробовал с Singleton, но мое приложение в какой-то момент звонит в браузер

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

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

Решение: поместите необходимые данные в подкласс класса Application.


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

Что ж, ленивая загрузка Singleton после перезапуска приложения не является правильным способом, позволяющим объектам охватывать GC. Слабые ссылки есть, правда?
mschonaker

15
В самом деле? Dalvik выгружает классы и теряет состояние программы? Вы уверены, что дело не в том, чтобы собирать мусор в виде объектов, связанных с жизненным циклом ограниченного жизненного цикла, которые вы не должны в первую очередь помещать в синглтоны? Вы должны привести примеры для такого необычного заявления!
android.weasel

1
Если не произошло изменений, о которых я не знаю, Dalvik не выгружает классы. Когда-либо. Поведение, которое они видят, заключается в том, что их процесс убивается в фоновом режиме, чтобы освободить место для браузера. Вероятно, они инициализировали переменную в своем «основном» действии, которое, возможно, не было создано в новом процессе при возврате из браузера.
Groxx

5

Рассмотрим и то и другое одновременно:

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

Кроме того, я предлагаю вам расширить свой Контекст, включив в него не только доступ к одноэлементным объектам, но и некоторые функции, к которым необходимо обращаться глобально, например: context.logOffUser (), context.readSavedData () и т. Д. Вероятно, переименуйте Контекст в Фасад будет иметь смысл тогда.


4

Они на самом деле одинаковы. Есть одно отличие, которое я вижу. С помощью класса Application вы можете инициализировать свои переменные в Application.onCreate () и уничтожить их в Application.onTerminate (). С синглтоном вам нужно полагаться на инициализацию и уничтожение ВМ статики.


16
Документы для onTerminate говорят, что он вызывается только эмулятором. На устройствах этот метод, вероятно, не будет вызван. developer.android.com/reference/android/app/...
danb

3

Мои 2 цента:

Я заметил, что некоторые синглтон / статические поля были сброшены, когда моя деятельность была разрушена. Я заметил это на некоторых младших устройствах 2.3.

Мой случай был очень прост: у меня просто есть закрытое поле init_done и статический метод init, который я вызывал из activity.onCreate (). Я замечаю, что метод init выполнял себя заново при некотором воссоздании действия.

Хотя я не могу доказать свое утверждение, оно может быть связано с тем, КОГДА синглтон / класс был создан / использован первым. Когда действие уничтожается / рециркулируется, кажется, что все классы, на которые ссылается только это действие, тоже перерабатываются.

Я переместил свой экземпляр singleton в подкласс Application. Я получаю доступ к ним из экземпляра приложения. и, с тех пор, не заметил проблемы снова.

Я надеюсь, что это может кому-то помочь.


3

Из уст пословиц лошади

При разработке приложения может возникнуть необходимость в общем доступе к данным, контексту или службам по всему приложению. Например, если в вашем приложении есть данные сеанса, такие как текущий вошедший в систему пользователь, вы, вероятно, захотите предоставить эту информацию. В Android шаблон решения этой проблемы заключается в том, чтобы ваш экземпляр android.app.Application владел всеми глобальными данными, а затем обрабатывал ваш экземпляр 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

Моя активность вызывает метод finish () (который не приводит к немедленному завершению, но в конечном итоге завершится) и вызывает Google Street Viewer. Когда я отлаживаю его в Eclipse, мое соединение с приложением разрывается, когда вызывается Street Viewer, что я понимаю как закрытое (целое) приложение, предположительно для освобождения памяти (так как одиночное завершающееся действие не должно вызывать такое поведение) , Тем не менее, я могу сохранить состояние в Bundle с помощью onSaveInstanceState () и восстановить его в методе onCreate () следующего действия в стеке. При использовании статического одноэлементного приложения или приложения подклассов я сталкиваюсь с состоянием закрытия и потери приложения (если я не сохраню его в Bundle). Так что из моего опыта они одинаковы в отношении сохранения государства. Я заметил, что соединение потеряно в 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.