Утечка памяти класса вспомогательных объектов Kotlin

#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 не будет его собирать. Это при условии, что ОС или пользователь не убьют ваше приложение в то же время, что полностью очистит всю память. Причина, по которой следует избегать использования большего объема памяти, чем вам нужно, заключается в том, что это увеличивает вероятность того, что ОС убьет ваше приложение, когда ему потребуется освободить память, и опытный пользователь, который на самом деле достаточно внимательно смотрит, может заметить, что ваше приложение-это свинья памяти.