Android:抽象自定义视图和通用布局膨胀

问题描述 投票:0回答:2

所以,我做了一些研究,似乎在抽象基类的

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
成为稍后在抽象类中设置的可空/可变变量。

还有其他人觉得这有点令人沮丧或有什么建议吗? 想要从基类膨胀相同的布局似乎并不罕见。

android kotlin android-view android-custom-view
2个回答
6
投票
在构造函数中泄漏

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")


0
投票
为时已晚,但也许有人发现这很有用。

您可以在构造函数调用后简单地初始化视图或绑定。只需使用

lazy 初始化,或在 onFinishInflate 方法中初始化。

© www.soinside.com 2019 - 2024. All rights reserved.