所以,我做了一些研究,似乎在抽象基类的
init
/构造函数中查看膨胀并不是真正的最佳实践。 我理解这是因为基类初始值设定项发生在派生类的 init
/构造函数之前。 由于抽象类是非最终类,所以有一个关于 this
被泄露在 init
块中的不错的 IDE 消息。
这就是我所追求的:
abstract class Foo @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : FrameLayout(context, attrs, defStyleAttr) {
private val myView: View
init {
// todo@patches fix leaking "this"
View.inflate(context, R.layout.view_foo, this)
myView = requireNotNull(findViewById(R.id.my_view))
}
}
class Bar @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : Foo(context, attrs, defStyleAttr)
我真的不想在派生类的 init 中添加任何内容,或者使
myView
成为稍后在抽象类中设置的可空/可变变量。
还有其他人觉得这有点令人沮丧或有什么建议吗? 想要从基类膨胀相同的布局似乎并不罕见。
this
是危险的,因为您泄漏它的对象可能会在构造函数完成之前开始访问其成员,因此它可能还没有准备好。即使对于不可为 null 的 Kotlin 属性或其他奇怪的行为,您也可能会获得 NPE。对于
LayoutInflator.inflate
来说,这似乎不是问题,因为 Android 的内置视图经常将
this
作为父级传递给
inflate()
方法。例如,DatePicker 的构造函数实例化一个 DatePickerSpinnerDelegate,它将该 DatePicker 实例传递给
inflate()
,所有这些都发生在 DatePicker 的构造函数返回之前。当您将视图作为父级传递给
inflate()
时,通过遵循调用链,我看到父级发生了两件事。它在该父级上调用
getContext()
,如果
addView()
为 true,则在该父级上调用
addToRoot
。因此,我认为泄漏
this
是安全的,只要您不重写
addView()
来执行依赖于您在调用
inflate(). But
addView()
also internally calls
requestLayout()
and
后设置的成员的额外工作invalidate()`,所以同样的问题也适用于这些。在大多数情况下,您的自定义 ViewGroup 将是现有 Android ViewGroup 类的子类,因此您不需要重写这些方法。
不幸的是,我们只能通过检查代码来推断这种行为。文档不能保证它的安全性,这并不能令人非常放心,但据我所知,我们只能接受它可能是安全的。也许应该在 AOSP 上提出一个问题。如果您用 Java 编写相同的代码,该警告甚至不会出现,但风险是相同的。
抑制警告并不意味着您忽略警告或只是破坏您的代码。这意味着,“我承认故障模式并已检查我的代码不会以这种方式失败。”如果不是这种情况,则将是编译器错误,而不是警告。 在 Kotlin 中,您可以使用的抑制注释是
@Suppress("LeakingThis")
。
您可以在构造函数调用后简单地初始化视图或绑定。只需使用
lazy 初始化,或在 onFinishInflate 方法中初始化。