简体   繁体   中英

Android TextView leaks with setMovementMethod

I have a ListView and in it's adapter 's getView method, I return a RelativeLayout with MyButton inside it.

MyButton has a textView and I have clickable words inside it ( ClickableSpan ).

To make this work, I start with thew following line: textView.setMovementMethod(LinkMovementMethod.getInstance());

Everything works perfectly but MAT shows that MyButton leaks because of textView . When I comment out the line above, nothing leaks.

Shall I set movementMethod to null ? But even if so, I can't know the destruction moment of the button to set that to null as it is inside of many other views.

What am I doing wrong? How to prevent this leak?

在此处输入图片说明

update

Solved the leak by setting text to empty string inside onDetachedFromWindow , but I am still trying to find a documentation related to this behaviour. Why should I set the textview to "" ?

I faced another memory leak with TextView , ClickableSpan , and LinkMovementMethod while making hyperlinks inside a Fragment . After the first click on the hyperlink and rotation of the device, it was impossible to click it again due to NPE.

In order to figure out what's going on, I made an investigation and here is the result.

TextView saves a copy of the field mText , that contains ClickableSpan , during the onSaveInstanceState() into the instance of static inner class SavedState . It happens only under certain conditions. In my case, it was a Selection for the clickable part, which is set by LinkMovementMethod after the first click on the span.

Next, if there is a saved state, TextView performs restoration for the field mText , including all spans, from TextView.SavedState.text during onRestoreInstanceState() .

Here is a funny part. When onRestoreInstanceState() is called? It's called after onStart() . I set a new object of ClickableSpan in onCreateView() but after onStart() the old object replaces new one which leads to the big problems.

So, the solution is quite simple but is not documented – perform setup of ClickableSpan during onStart() .

You can read the full investigation on my blog TextView, ClickableSpan and memory leak and play with the sample project .

Using ClickableSpan may still cause leaks even on versions higher than KitKat . If you look into implementation of the ClickableSpan you will notice that it doesn't extend NoCopySpan , so it leaks in onSaveInstanceState() like described in @DmitryKorobeinikov and @ChrisHorner answers. So the solution would be to create a custom class that extends ClickableSpan and NoCopySpan .

class NoCopyClickableSpan(
    private val callback: () -> Unit
) : ClickableSpan(), NoCopySpan {

    override fun onClick(view: View) {
        callback()
    }
}

EDIT It turned out that this fix leads to crashes on some devices when Accessibility services are enabled.

Your issue is most likely caused by NoCopySpan . Prior to KitKat, TextView would make a copy of the span and place it in a Bundle in onSaveInstanceState() using a SpannableString. SpannableString does not drop NoCopySpans for some reason, so the saved state holds a reference to the original TextView. This was fixed for subsequent releases.

Setting the text to "" fixes the issue because the original text containing the NoCopySpan is GC'd properly.

LeakCanary 's suggested work around for this is...

Hack: to fix this, you could override TextView.onSaveInstanceState(), and then use reflection to access TextView.SavedState.mText and clear the NoCopySpan spans.

LeakCanary's exclusion entry for this leak can be found here .

Try to initialize the ClickableSpan in onStart() method.Like

onStart(){
super.onStart()
someTextView.setText(buildSpan());
}

There is problem with Span on some Android versions. Sometimes it causes memory leaks. More info in this article TextView, ClickableSpan and memory leak

I hope it will help.

After spending a few hours trying these answers out I came up with my own that finally worked.

I'm not sure how accurate this is and don't understand why this is but it turned out that setting my TextView 's movementMethod to null in onDestroy() solved the problem.

If anyone knows why please tell me. I'm so boggled because it doesn't seem like LinkMovementMethod.getInstance() has a reference to the TextView or the activity.

Here's the code

override fun onStart() {
    ...
    text_view.text = spanString
    text_view.movementMethod = LinkMovementMethod
} 

override fun onDestroy() {
    text_view.text = ""
    text_view.movementMethod = null
}

It worked without setting text_view.text = "" but I kept it their because of @Chris Horner answer that there might be a problem prior to KitKat.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM