Kotlin Android Extensions for Views 背后的编辑器魔法是什么?
What is the editor magic behind Kotlin Android Extensions for Views?
当我将 kotlinx.android.synthetic.main.<layout-name>.view.*
形式的导入添加到 Kotlin 源时,它会改变 Android Studio 编辑器的行为。具体来说,它现在认为 View
类型的任何 属性 都有一个 属性 对应于分配了 id 的布局文件中的每个视图。我假设我是在 class 中执行此操作的 而不是 和 Activity
或 Fragment
.
例如,假设我在源文件中声明了一个 ViewHolder
,我向它添加了一个名为 x
的 View
类型的 属性。此外,假设我有一个名为 item_test
的布局,它具有三个视图声明,这些声明已分配了 id,a
、b
和 c
。当我使用 item_test
的 layout-name 添加上面表格的合成导入时,突然 x
具有三个新属性,a
,b
和 c
。或者,更准确地说,编辑器让它看起来好像 x
具有这些属性。
经过一番研究,我得出以下结论:
添加布局视图 ID 作为属性是盲目的。此类合成导入所暗示的任何视图 ID 都将添加到类型为 View
的 class 的任何 属性(或 View
的子 class ).这包括由 class.
继承的此类属性
由于属性是盲目添加的,因此开发人员有责任确保在访问合成属性之前分配与合成导入对应的运行时视图,否则将抛出异常。
如果在一个文件中指定了两个或多个这样的导入,那么它们的视图 ID 的并集将被盲目地添加到类型 View
的所有 class 属性中。
允许多次导入的目的是为了解决一个布局文件包含另一个布局文件的情况。
这些结论正确吗?
围绕此功能的实施还有其他有趣的细微之处吗?
我正在使用 kotlin-gradle-plugin 的 1.1.2-5 版。
如果我理解正确,您假设如下:
import kotlinx.android.synthetic.main.activity_main.item_test
import kotlinx.android.synthetic.main.activity_main.item_test_2
class MyClass {
lateinit var x: View
lateinit var y: View
fun foo() {
val foo1 = x.item_test // Compiles
val foo2 = y.item_test // Compiles as well
val bar1 = x.item_test_2 // Compiles too
val bar2 = y.item_test_2 // Compiles yet again
}
}
So it appears to me that any property that is of type View will have the synthetic properties associated with a synthetic import regardless of whether it is valid or not. So the extension implementation appears to be brute force and will not tip-off the developer if anything is wrong.
没错。
现在,item_test
导入本质上是 View
class 上的扩展 属性(简化示例[1]):
val View.item_test get() = findViewById(R.id.item_test)
对于布局中的每个视图,插件都会生成这些属性,并将它们放入它们的相关文件中。 Activity
和 Fragment
.
上也存在这些属性
因此,当您调用 x.item_test
时,您实际上是在调用 x.findViewById(R.id.item_test)
[1].
编译后,插件会将这些调用替换回 findViewById
。所以上面程序的简化反编译版本[1](在Java):
final class MyClass {
public View x;
public View y;
public final void foo() {
TextView foo1 = (TextView) x.findViewById(id.item_test);
TextView foo2 = (TextView) y.findViewById(id.item_test);
TextView bar1 = (TextView) x.findViewById(id.item_test_2);
TextView bar2 = (TextView) y.findViewById(id.item_test_2);
}
}
因为这只是 View
上的一个扩展函数,所以没有完整性检查来检查 item_test
是否存在于 x
或 y
的视图层次结构中,就像他们 findViewById
不在那里一样。如果 findViewById
调用 returns null
,则值为 null
或抛出 KotlinNullPointerException
。
- The addition of layout view ids as properties is done blindly. Any view id implied by a such a synthetic import is added to any property of the class that is of type View (or a subclass of View). This includes properties of such type inherited by the class.
是的,因为 属性 是 View
上的扩展 属性,如上所述。
- Because the properties are added blindly it is incumbent upon the developer to ensure that the runtime view corresponding to the synthetic import is assigned before the synthetic properties are accessed or an exception will be thrown.
我不确定你在这里问什么。如果您的意思是您必须在调用 x.item_test
之前初始化 x
,那是对的。此外,x
在其层次结构中应该有一个视图 item_test
。
- 如果在一个文件中指定了两个或多个这样的导入,那么它们的视图 ID 的并集将被盲目地添加到视图类型的所有 class 属性中。
允许多次导入的目的是考虑到一个布局文件包含另一个布局文件的情况。
是的。
[1]:实际上,这些查找在幕后被缓存了。请参阅文档中的 Under the hood。
当我将 kotlinx.android.synthetic.main.<layout-name>.view.*
形式的导入添加到 Kotlin 源时,它会改变 Android Studio 编辑器的行为。具体来说,它现在认为 View
类型的任何 属性 都有一个 属性 对应于分配了 id 的布局文件中的每个视图。我假设我是在 class 中执行此操作的 而不是 和 Activity
或 Fragment
.
例如,假设我在源文件中声明了一个 ViewHolder
,我向它添加了一个名为 x
的 View
类型的 属性。此外,假设我有一个名为 item_test
的布局,它具有三个视图声明,这些声明已分配了 id,a
、b
和 c
。当我使用 item_test
的 layout-name 添加上面表格的合成导入时,突然 x
具有三个新属性,a
,b
和 c
。或者,更准确地说,编辑器让它看起来好像 x
具有这些属性。
经过一番研究,我得出以下结论:
添加布局视图 ID 作为属性是盲目的。此类合成导入所暗示的任何视图 ID 都将添加到类型为
View
的 class 的任何 属性(或View
的子 class ).这包括由 class. 继承的此类属性
由于属性是盲目添加的,因此开发人员有责任确保在访问合成属性之前分配与合成导入对应的运行时视图,否则将抛出异常。
如果在一个文件中指定了两个或多个这样的导入,那么它们的视图 ID 的并集将被盲目地添加到类型
View
的所有 class 属性中。允许多次导入的目的是为了解决一个布局文件包含另一个布局文件的情况。
这些结论正确吗?
围绕此功能的实施还有其他有趣的细微之处吗?
我正在使用 kotlin-gradle-plugin 的 1.1.2-5 版。
如果我理解正确,您假设如下:
import kotlinx.android.synthetic.main.activity_main.item_test
import kotlinx.android.synthetic.main.activity_main.item_test_2
class MyClass {
lateinit var x: View
lateinit var y: View
fun foo() {
val foo1 = x.item_test // Compiles
val foo2 = y.item_test // Compiles as well
val bar1 = x.item_test_2 // Compiles too
val bar2 = y.item_test_2 // Compiles yet again
}
}
So it appears to me that any property that is of type View will have the synthetic properties associated with a synthetic import regardless of whether it is valid or not. So the extension implementation appears to be brute force and will not tip-off the developer if anything is wrong.
没错。
现在,item_test
导入本质上是 View
class 上的扩展 属性(简化示例[1]):
val View.item_test get() = findViewById(R.id.item_test)
对于布局中的每个视图,插件都会生成这些属性,并将它们放入它们的相关文件中。 Activity
和 Fragment
.
上也存在这些属性
因此,当您调用 x.item_test
时,您实际上是在调用 x.findViewById(R.id.item_test)
[1].
编译后,插件会将这些调用替换回 findViewById
。所以上面程序的简化反编译版本[1](在Java):
final class MyClass {
public View x;
public View y;
public final void foo() {
TextView foo1 = (TextView) x.findViewById(id.item_test);
TextView foo2 = (TextView) y.findViewById(id.item_test);
TextView bar1 = (TextView) x.findViewById(id.item_test_2);
TextView bar2 = (TextView) y.findViewById(id.item_test_2);
}
}
因为这只是 View
上的一个扩展函数,所以没有完整性检查来检查 item_test
是否存在于 x
或 y
的视图层次结构中,就像他们 findViewById
不在那里一样。如果 findViewById
调用 returns null
,则值为 null
或抛出 KotlinNullPointerException
。
- The addition of layout view ids as properties is done blindly. Any view id implied by a such a synthetic import is added to any property of the class that is of type View (or a subclass of View). This includes properties of such type inherited by the class.
是的,因为 属性 是 View
上的扩展 属性,如上所述。
- Because the properties are added blindly it is incumbent upon the developer to ensure that the runtime view corresponding to the synthetic import is assigned before the synthetic properties are accessed or an exception will be thrown.
我不确定你在这里问什么。如果您的意思是您必须在调用 x.item_test
之前初始化 x
,那是对的。此外,x
在其层次结构中应该有一个视图 item_test
。
- 如果在一个文件中指定了两个或多个这样的导入,那么它们的视图 ID 的并集将被盲目地添加到视图类型的所有 class 属性中。 允许多次导入的目的是考虑到一个布局文件包含另一个布局文件的情况。
是的。
[1]:实际上,这些查找在幕后被缓存了。请参阅文档中的 Under the hood。