Я пытаюсь реализовать следующий одноэлементный шаблон: 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()
}
Вариант № 1: Попросите getInstance(Context) вызвать applicationContext на поставляемый Context и удерживайте его. Синглтон Application создается, когда ваш процесс существует, и живет всю жизнь процесса. Это предварительная утечка; вы не можете пролить его дальше.
Вариант № 2: Избавьтесь от getInstance() и настройте какую-либо форму внедрения зависимостей (Dagger 2, Koin и т. д.). Существуют рецепты для этих DI-фреймворков, чтобы они предоставляли синглтон Application синглтонам, которые они создают и внедряют ниже по течению.
@Роймунсон: вставка-koin.io/docs/1.0/documentation/reference/…
Спасибо. Я добавил свое решение, в котором используется Koin, внизу моего поста. Можете ли вы взглянуть на него быстро? Я никогда раньше не использовал внедрение зависимостей, поэтому не знаю, правильно ли я это делаю. Если мой рецепт правильный, смогу ли я использовать его для доступа к NetworkUtils в любом Activity или Fragment с простым val networkUtils : NetworkUtils by inject()?
@Roymunson: Ах, я забыл о пробеле в документации: androidContext() возвращает Application, напечатанный как Context, but androidApplication()`, возвращает Application как Application. Итак, я бы использовал androidApplication() и class NetworkUtils(val app: Application), чтобы В самом деле был уверен, что у вас есть синглтон Application.
@Roymunson: «смогу ли я использовать его для доступа к NetworkUtils в любом действии или фрагменте с помощью простого val networkUtils : NetworkUtils с помощью inject ()?» -- да, это должно сработать.
В чем преимущество использования androidApplication() и class NetworkUtils(val app: Application) вместо использования androidContext() и class NetworkUtils(val context: Context). Поскольку context больше не является статическим полем, похоже, не имеет значения, к какому типу context имеет доступ класс NetworkUtils.
@Roymunson: Ну, цель состояла в том, чтобы избежать утечки памяти. предположительно, в коине нет багов, а androidContext() действительно возвращает Application. Лично мне нравится добавлять дополнительную безопасность типов, чтобы быть чертовски уверенным, что я получаю Application. И хотя context не является static, Koin держит NetworkUtils (single), и поэтому NetworkUtils не может быть собран мусором.
если вы должны создать одноэлементный класс с задействованным контекстом, вы можете сделать это следующим образом. Это поможет. В этом случае ваш контекст будет сбрасываться в каждом действии, когда вы вызываете 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;
}
}
На самом деле это не решит его проблемы. На самом деле это сделало бы его еще хуже, так как теперь вы сильно сломаетесь, если пользователь нанесет ответный удар или у вас есть Сервис.
Когда вы вызываете 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)
Надеюсь, что это поможет :)
Можете ли вы привести простой пример варианта 2? Я прочитал документацию Koin и не смог понять, как создать синглтон с доступом к
Context.