#android #kotlin #memory-leaks
Вопрос:
Я хочу создать вспомогательный объект, который поможет мне получить доступ к строкам из любого места проекта. Кроме того, этот вспомогательный объект также должен работать в модульных тестах. Но я не могу быть уверен, что есть ли какой-либо риск утечки памяти, например, при использовании ?
Это вспомогательный объект.
object ResourceHelper {
private var getString: (Int) -> String = {
it.toString()
}
private var getStringWithArgs: (Int, Array<out Any>) -> String = { id, args ->
"$id${args.contentToString()}"
}
fun getString(@StringRes id: Int): String {
return getString.invoke(id)
}
fun getString(@StringRes id: Int, vararg args: Any): String {
return getStringWithArgs.invoke(id, args)
}
fun initialize(resources: Resources) {
getString = { id -> resources.getString(id) }
getStringWithArgs = { id, args -> resources.getString(id, *args) }
}
}
Это единственное мероприятие в проекте.
class MyActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?) {
super.onCreate(savedInstanceState, persistentState)
ResourceHelper.initialize(resources)
}
}
Это класс модели представления, у меня нет очень подробных знаний о том, как хранятся ссылки в стеке. Есть ли какие-либо проблемы с этим подходом ?
class MyViewModel : ViewModel() {
fun printString(id: Int) {
val s = ResourceHelper.getString(id)
Log.d("*****", s)
}
}
Ответ №1:
Объект ресурсов из Действия отличается каждый раз при запуске Действия из-за изменения конфигурации.
Ваш код временно приведет к утечке объекта ресурсов в промежутке между уничтожением Действия и его последующим воссозданием (при onCreate()
повторном вызове initialize()
и перезаписи объекта ресурсов по ссылке). Если это просто время во время смены ориентации, это тривиально. Но если это произойдет в течение времени между отказом пользователя от выполнения действия и его следующим возвращением в приложение, то ваше приложение будет занимать немного больше памяти, чем необходимо.
В любом случае, такие синглтоны, принадлежащие государству, затрудняют модульное тестирование. Я предлагаю создать функции расширения в качестве альтернативы. Если вам нужен доступ к строковым ресурсам в ViewModel, вы можете расширить его с помощью AndroidViewModel, которая имеет доступ к ресурсам через приложение.
fun AndroidViewModel.getString(@StringRes id: Int) =
getApplication<Application>().resources.getString(id)
fun AndroidViewModel.getString(@StringRes id: Int, vararg args: Any) =
getApplication<Application>().resources.getString(id, args)
Комментарии:
1. Во втором случае, при выходе из действия и возвращении в приложение, сборщик мусора сможет удалить старую ссылку на ресурсы. Разве это не старые ресурсы, перезаписанные новыми ресурсами ?
2. В течение времени после завершения резервного копирования действия и при следующем возвращении пользователя в приложение ваш одноэлементный объект ResourceHelper все еще находится в памяти и содержит ссылку на объект ресурсов, поэтому GC не будет его собирать. Это при условии, что ОС или пользователь не убьют ваше приложение в то же время, что полностью очистит всю память. Причина, по которой следует избегать использования большего объема памяти, чем вам нужно, заключается в том, что это увеличивает вероятность того, что ОС убьет ваше приложение, когда ему потребуется освободить память, и опытный пользователь, который на самом деле достаточно внимательно смотрит, может заметить, что ваше приложение-это свинья памяти.