Android TextView утечки с setMovementMethod
У меня есть ListView
и в это adapter
"s getView
метод, я возвращаю RelativeLayout
с MyButton
внутри него.
MyButton
имеет textView
и у меня внутри есть кликабельные слова (ClickableSpan
).
Чтобы сделать это, я начну со следующей строки:textView.setMovementMethod(LinkMovementMethod.getInstance());
Все работает отлично, но МАТ показывает, что MyButton
утечки из-за textView
, Когда я комментирую строку выше, ничего не просачивается.
Должен ли я установить movementMethod
в null
? Но даже если так, я не могу знать момент разрушения кнопки, чтобы установить это null
как и во многих других взглядах.
Что я делаю неправильно? Как предотвратить эту утечку?
Обновить
Устранить утечку, установив текст для пустой строки внутри onDetachedFromWindow
, но я все еще пытаюсь найти документацию, связанную с этим поведением. Почему я должен установить textview
в ""
?
4 ответа
Я столкнулся с очередной утечкой памяти TextView
, ClickableSpan
, а также LinkMovementMethod
делая гиперссылки внутри Fragment
, После первого щелчка по гиперссылке и ротации устройства, было невозможно нажать его снова из-за NPE.
Чтобы выяснить, что происходит, я провел расследование, и вот результат.
TextView
сохраняет копию поля mText
, который содержит ClickableSpan
, в течение onSaveInstanceState()
в случае статического внутреннего класса SavedState
, Это происходит только при определенных условиях. В моем случае это был Selection
для кликабельной части, которая устанавливается LinkMovementMethod
после первого клика на пролете.
Далее, если есть сохраненное состояние, TextView
выполняет восстановление для поля mText
, включая все пролеты, из TextView.SavedState.text
в течение onRestoreInstanceState()
,
Вот забавная часть. когда onRestoreInstanceState()
называется? Это называется после onStart()
, Я установил новый объект ClickableSpan
в onCreateView()
но после onStart()
старый объект заменяет новый, что приводит к большим проблемам.
Итак, решение довольно простое, но не задокументировано - выполните настройку ClickableSpan
в течение onStart()
,
Вы можете прочитать полное расследование в моем блоге TextView, ClickableSpan и утечки памяти и поиграть с примером проекта.
С помощью ClickableSpan
может по-прежнему вызывать утечки даже на версиях выше, чем KitKat
, Если вы посмотрите на реализацию ClickableSpan
вы заметите, что это не распространяется NoCopySpan
таким образом, это просачивается в onSaveInstanceState()
как описано в @DmitryKorobeinikov и @ChrisHorner ответы. Таким образом, решение было бы создать пользовательский класс, который расширяет ClickableSpan
а также NoCopySpan
,
class NoCopyClickableSpan(
private val callback: () -> Unit
) : ClickableSpan(), NoCopySpan {
override fun onClick(view: View) {
callback()
}
}
Потратив несколько часов, пытаясь найти ответы на эти вопросы, я нашел свой, который, наконец, сработал.
Я не уверен, насколько это точно, и не понимаю, почему это так, но оказалось, что установка моего TextView
движение к нулю в onDestroy()
решил проблему.
Если кто-то знает почему, пожалуйста, скажите мне. Я так поражен, потому что это не похоже на LinkMovementMethod.getInstance()
имеет ссылку на TextView
или деятельность.
Вот код
override fun onStart() {
...
text_view.text = spanString
text_view.movementMethod = LinkMovementMethod
}
override fun onDestroy() {
text_view.text = ""
text_view.movementMethod = null
}
Работало без настройки text_view.text = ""
но я сохранил их из-за ответа @Chris Horner о том, что может быть проблема до KitKat.
Ваша проблема, скорее всего, вызвана NoCopySpan
, До KitKat TextView делал бы копию диапазона и помещал ее в Bundle в onSaveInstanceState()
используя SpannableString. SpannableString по какой-то причине не удаляет NoCopySpans, поэтому сохраненное состояние содержит ссылку на исходный TextView. Это было исправлено в последующих выпусках.
Установка текста в "" устраняет проблему, потому что исходный текст, содержащий NoCopySpan, правильно настроен для GC.
LeakCanary предлагает обойти это...
Hack: чтобы это исправить, вы можете переопределить TextView.onSaveInstanceState(), а затем использовать отражение для доступа к TextView.SavedState.mText и очистить интервалы NoCopySpan.
Запись об исключении LeakCanary для этой утечки может быть найдена здесь.
Попробуйте инициализировать ClickableSpan в onStart()
method.Like
onStart(){
super.onStart()
someTextView.setText(buildSpan());
}
Существует проблема с Span на некоторых версиях Android. Иногда это вызывает утечки памяти. Больше информации в этой статье TextView, ClickableSpan и утечка памяти
Надеюсь, это поможет.