Android SpanStringBuilder ClickableSpan内存泄漏和crash的解决方案

  • Post author:
  • Post category:其他


这个问题属于老生常谈,使用SpanStringBuilder的ClickableSpan,有如下因素导致各种问题:

  1. google对于ClickableSpan,内部TextView创建了很多内部类和变量去持有XXXSpan会导致

    内存泄漏

    , google原生代码有缺陷;
  2. 过去很多年都通过实现继承ClickableSpan的同时,实现NoCopySpan来解决,但是会在某些场景即辅助服务的时候,crash;
  3. 第二条,又有人提出TextView设置 android:importantForAccessibility=“no”, 但是某些机型仍然不生效;
  4. 回归到不继承NoCopySpan,来解决内存泄漏问题。

根本原因是匿名内部类:

比较java或者kotlin的lamda或者object:或者直接new XXXListener,参考我之前的帖子分析过内部类是啥情况(https://blog.csdn.net/jzlhll123/article/details/126593235)。

因此,这里给出建议:

第一条:

可以使用匿名内部类,必须满足不引用外部;参考我之前的帖子结论,这样的话,不论kotlin或者java都会优化编译不会持有外部对象,进而不会持有context。

第二条:这里设置的时候也定义一个static的class来实例化对象传入。也避免了对象传递;

 movementMethod = LinkMovementMethod.getInstance()
 textView.setSpan(CustomClickableSpan(click,
     underLineColor = it.underlineColor,
     textColor = it.textColor,
     isUnderlineText = it.isUnderlineText)
     , it)

class CustomClickableSpan(private val click:((View) -> Unit)?,
                          private val underLineColor:Int?,
                          private val textColor:Int?,
                          private val isUnderlineText:Boolean?) : ClickableSpan() {
    override fun onClick(widget: View) {
        click?.invoke(widget)
    }

    override fun updateDrawState(ds: TextPaint) {
        super.updateDrawState(ds)
        (underLineColor ?: textColor)?.let { color ->
            ds.color = color
        }
        isUnderlineText?.let {
            ds.isUnderlineText = it
        }
    }
}

第三条:

如果有引用则必须自定义weakRef。

比如requireContext(),则不能使用了,能用view.getContext()替换则替换。能用App的单例就用。

错误实例:

requireContext().startActivity(xxx) //Error: requireContext则会使用外部的this

改进:

private class OnListener(fragment: XXXFragment) : Function1<View, Unit> {
      private val fragmentRef:WeakReference<XXXFragment> = WeakReference(fragment)

      override fun invoke(p1: View) {
         fragmentRef.get()?.onClick(p1)       
         }
  }

private val onClick by unsafeLazy { OnListener(this) }

movementMethod = LinkMovementMethod.getInstance()
  sbText.setSpan(CustomClickableSpan(click,
      underLineColor = it.underlineColor,
      textColor = it.textColor,
      isUnderlineText = it.isUnderlineText)
      , it)



版权声明:本文为jzlhll123原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。