简体   繁体   English

Android TextView 使用 setMovementMethod 泄漏

[英]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.我有一个ListView并且在它的adaptergetView方法中,我返回一个带有MyButtonRelativeLayout

MyButton has a textView and I have clickable words inside it ( ClickableSpan ). MyButton有一个textView ,里面有可点击的词( ClickableSpan )。

To make this work, I start with thew following line: textView.setMovementMethod(LinkMovementMethod.getInstance());为了完成这项工作,我从以下行开始: textView.setMovementMethod(LinkMovementMethod.getInstance());

Everything works perfectly but MAT shows that MyButton leaks because of textView .一切完美,但MAT显示, MyButton泄漏,因为textView When I comment out the line above, nothing leaks.当我注释掉上面的行时,没有任何泄漏。

Shall I set movementMethod to null ?我应当设置movementMethodnull 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.但即使是这样,我也不知道按钮的销毁时刻将其设置为null因为它在许多其他视图中。

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.通过在onDetachedFromWindow文本设置为空字符串解决了泄漏onDetachedFromWindow ,但我仍在尝试查找与此行为相关的文档。 Why should I set the textview to "" ?为什么我应该将textview设置为""

I faced another memory leak with TextView , ClickableSpan , and LinkMovementMethod while making hyperlinks inside a Fragment .Fragment创建超链接时,我遇到了TextViewClickableSpanLinkMovementMethod另一个内存泄漏。 After the first click on the hyperlink and rotation of the device, it was impossible to click it again due to NPE.第一次点击设备的超链接和旋转后,由于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 . TextViewonSaveInstanceState()期间将包含ClickableSpan的字段mText的副本保存到静态内部类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.在我的例子中,它是可点击部分的一个Selection ,它是在第一次点击跨度后由LinkMovementMethod设置的。

Next, if there is a saved state, TextView performs restoration for the field mText , including all spans, from TextView.SavedState.text during onRestoreInstanceState() .接着,如果存在已保存的状态中, TextView执行恢复为字段mText ,包括所有的跨度,从TextView.SavedState.text期间onRestoreInstanceState()

Here is a funny part.这是一个有趣的部分。 When onRestoreInstanceState() is called?何时onRestoreInstanceState() It's called after onStart() .它在onStart()之后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.我在onCreateView()设置了一个ClickableSpan的新对象,但是在onStart() ,旧对象替换了新对象,这导致了大问题。

So, the solution is quite simple but is not documented – perform setup of ClickableSpan during onStart() .因此,解决方案非常简单,但没有记录 - 在onStart()期间执行ClickableSpan设置。

You can read the full investigation on my blog TextView, ClickableSpan and memory leak and play with the sample project .您可以在我的博客TextView、ClickableSpan 和内存泄漏上阅读完整调查 并使用示例项目

Using ClickableSpan may still cause leaks even on versions higher than KitKat .即使在高于KitKat版本上,使用ClickableSpan仍可能导致泄漏。 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.如果您查看ClickableSpan实现,您会注意到它没有扩展NoCopySpan ,因此它在onSaveInstanceState()泄漏,如@DmitryKorobeinikov 和@ChrisHorner 的答案中所述。 So the solution would be to create a custom class that extends ClickableSpan and NoCopySpan .因此,解决方案是创建一个扩展ClickableSpanNoCopySpan的自定义类。

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 .您的问题很可能是由NoCopySpan引起的。 Prior to KitKat, TextView would make a copy of the span and place it in a Bundle in onSaveInstanceState() using a SpannableString.在 KitKat 之前,TextView 会复制 span 并使用 SpannableString 将其放置在onSaveInstanceState()中的 Bundle 中。 SpannableString does not drop NoCopySpans for some reason, so the saved state holds a reference to the original TextView.由于某种原因,SpannableString 不会删除 NoCopySpans,因此保存的状态包含对原始 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.将文本设置为 "" 可以解决此问题,因为包含 NoCopySpan 的原始文本已正确 GC。

LeakCanary 's suggested work around for this is... LeakCanary建议的解决方法是......

Hack: to fix this, you could override TextView.onSaveInstanceState(), and then use reflection to access TextView.SavedState.mText and clear the NoCopySpan spans. Hack:要解决此问题,您可以覆盖 TextView.onSaveInstanceState(),然后使用反射访问 TextView.SavedState.mText 并清除 NoCopySpan 跨度。

LeakCanary's exclusion entry for this leak can be found here .可以在此处找到LeakCanary 对此泄漏的排除条目。

Try to initialize the ClickableSpan in onStart() method.Like尝试在onStart()方法中初始化 ClickableSpan。喜欢

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

There is problem with Span on some Android versions.某些 Android 版本上的 Span 存在问题。 Sometimes it causes memory leaks.有时它会导致内存泄漏。 More info in this article TextView, ClickableSpan and memory leak本文中的更多信息TextView、ClickableSpan 和内存泄漏

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.我不确定这是多么准确,也不明白为什么会这样,但结果证明在onDestroy()中将我的TextView的运动方法设置为 null 解决了这个问题。

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.我很困惑,因为LinkMovementMethod.getInstance()似乎没有对TextView或活动的引用。

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.它在没有设置text_view.text = ""情况下工作,但我保留了它,因为@Chris Horner 回答说在 KitKat 之前可能存在问题。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 在ListView内的TextView上安装Android setMovementMethod - Android setMovementMethod on a TextView inside a ListView setMovementMethod 在 android textview 中不起作用 - setMovementMethod doesn't work in android textview 是否可以使TextView autoLink和setMovementMethod都起作用 - Can TextView autoLink and setMovementMethod both be made to work 无法在TextView上使用setOnClickListener()和setMovementMethod(new ScrollingMovementMethod()) - Cannot use setOnClickListener() and setMovementMethod(new ScrollingMovementMethod()) on TextView setMovementMethod(滚动)会干扰textView中的scaleGestureDetector(缩放) - setMovementMethod (Scrolling) interferes with scaleGestureDetector (zooming) in a textView 在单行为 true 的文本视图上调用 setmovementmethod 时,文本不可见 - Text is not visible when setmovementmethod is called on a textview with singleline is true 在 android 上禁用 setMovementMethod(LinkMovementMethod.getInstance()) - disable setMovementMethod(LinkMovementMethod.getInstance()) on android 为可点击跨度设置 setMovementMethod 后如何防止 textview 滚动? - How to prevent scroll of textview after set setMovementMethod for clickable span? Android AsyncTask内存泄漏 - Android AsyncTask memory leaks 检测Android内存泄漏 - Detect Android Memory Leaks
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM