Синглтон без утечек памяти с контекстом

Я пытаюсь реализовать следующий одноэлементный шаблон: SingletonClass.getInstance(context).callMethod()

Хотя существует множество руководств, объясняющих, как создавать синглтоны в Kotlin, ни в одном из них не рассматривается тот факт, что хранение context в статическом поле вызовет утечку памяти в Android.

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

Обновлять:

Вот моя реализация решения CommonsWare № 2. Я использовал Коин.

Синглтон класс:

class  NetworkUtils(val context: Context) {

}

Класс приложения:

class MyApplication : Application() {

    val appModule = module {
        single { NetworkUtils(androidContext()) }
    }

    override fun onCreate() {
        super.onCreate()
        startKoin(this, listOf(appModule))
    }
}

Класс активности:

class MainActivity : AppCompatActivity() {

    val networkUtils : NetworkUtils by inject()

}
7
0
2 952
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

Ответ принят как подходящий

Вариант № 1: Попросите getInstance(Context) вызвать applicationContext на поставляемый Context и удерживайте его. Синглтон Application создается, когда ваш процесс существует, и живет всю жизнь процесса. Это предварительная утечка; вы не можете пролить его дальше.

Вариант № 2: Избавьтесь от getInstance() и настройте какую-либо форму внедрения зависимостей (Dagger 2, Koin и т. д.). Существуют рецепты для этих DI-фреймворков, чтобы они предоставляли синглтон Application синглтонам, которые они создают и внедряют ниже по течению.

Можете ли вы привести простой пример варианта 2? Я прочитал документацию Koin и не смог понять, как создать синглтон с доступом к Context.

Roymunson 29.01.2019 18:39

@Роймунсон: вставка-koin.io/docs/1.0/documentation/reference/…

CommonsWare 29.01.2019 18:46

Спасибо. Я добавил свое решение, в котором используется Koin, внизу моего поста. Можете ли вы взглянуть на него быстро? Я никогда раньше не использовал внедрение зависимостей, поэтому не знаю, правильно ли я это делаю. Если мой рецепт правильный, смогу ли я использовать его для доступа к NetworkUtils в любом Activity или Fragment с простым val networkUtils : NetworkUtils by inject()?

Roymunson 29.01.2019 19:17

@Roymunson: Ах, я забыл о пробеле в документации: androidContext() возвращает Application, напечатанный как Context, but androidApplication()`, возвращает Application как Application. Итак, я бы использовал androidApplication() и class NetworkUtils(val app: Application), чтобы В самом деле был уверен, что у вас есть синглтон Application.

CommonsWare 29.01.2019 19:22

@Roymunson: «смогу ли я использовать его для доступа к NetworkUtils в любом действии или фрагменте с помощью простого val networkUtils : NetworkUtils с помощью inject ()?» -- да, это должно сработать.

CommonsWare 29.01.2019 19:23

В чем преимущество использования androidApplication() и class NetworkUtils(val app: Application) вместо использования androidContext() и class NetworkUtils(val context: Context). Поскольку context больше не является статическим полем, похоже, не имеет значения, к какому типу context имеет доступ класс NetworkUtils.

Roymunson 29.01.2019 19:31

@Roymunson: Ну, цель состояла в том, чтобы избежать утечки памяти. предположительно, в коине нет багов, а androidContext() действительно возвращает Application. Лично мне нравится добавлять дополнительную безопасность типов, чтобы быть чертовски уверенным, что я получаю Application. И хотя context не является static, Koin держит NetworkUtils (single), и поэтому NetworkUtils не может быть собран мусором.

CommonsWare 29.01.2019 19:33

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

public class MyClass {

    private Context context;

    public static getInstance(Context context){
         if (instance ==null)
             instance = new MyClass();
         instance.setContext(context);
         return instance;
    }

   public void setContext(Context context){
      this.context = context;
  }
}

На самом деле это не решит его проблемы. На самом деле это сделало бы его еще хуже, так как теперь вы сильно сломаетесь, если пользователь нанесет ответный удар или у вас есть Сервис.

Gabe Sechan 28.01.2019 21:09

Когда вы вызываете getInstance() в первый раз, Context, который вы передаете этой функции, сохраняется навсегда. Так что контекст в дальнейших вызовах getInstance() тут ни при чем. Я никогда не сохраняю этот Context.

Вот что я делаю:

Создайте object в Kotlin и инициализируйте объект контекстом сразу после запуска приложения. Вместо сохранения контекста я выполняю любую требуемую операцию с этим контекстом.

object PreferenceHelper {

    private var prefs: SharedPreferences? = null

    fun initWith(context: Context){
        if (prefs == null) this.prefs = context.getSharedPreferences("prefs", Context.MODE_PRIVATE)
    }

    fun someAction(){ .... }
}

и внутри класса Application:

class MyApp: Application(){
   override fun onCreate(){
      PreferenceHelper.initWith(this)
   }
 }

а затем в любом месте приложения:

 PreferenceHelper.someAction()

Вы можете сделать это, если вам не нужна ссылка на Context каждый раз, когда вы что-то делаете с классом Singleton.

Я бы не стал хранить контекст в SingletonClass, я бы просто передал контекст каждому методу класса через внедрение зависимостей. Что-то типа:

SingletonClass.callMethod(context)

Определите «статический» метод в объекте-компаньоне следующим образом:

 companion object {
        fun callMethod(context: Context) {
            // do Something
        }
    }

Затем вызовите его из своей деятельности с помощью:

SingletonClass.callMethod(this)

Надеюсь, что это поможет :)

Другие вопросы по теме