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 и утечка памяти

Надеюсь, это поможет.

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