这个问题属于老生常谈,使用SpanStringBuilder的ClickableSpan,有如下因素导致各种问题:
- google对于ClickableSpan,内部TextView创建了很多内部类和变量去持有XXXSpan会导致内存泄漏, google原生代码有缺陷;
- 过去很多年都通过实现继承ClickableSpan的同时,实现NoCopySpan来解决,但是会在某些场景即辅助服务的时候,crash;
- 第二条,又有人提出TextView设置 android:importantForAccessibility=“no”, 但是某些机型仍然不生效;
- 回归到不继承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)