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 中执行此操作的 而不是 ActivityFragment.

例如,假设我在源文件中声明了一个 ViewHolder,我向它添加了一个名为 xView 类型的 属性。此外,假设我有一个名为 item_test 的布局,它具有三个视图声明,这些声明已分配了 id,abc。当我使用 item_testlayout-name 添加上面表格的合成导入时,突然 x 具有三个新属性,abc。或者,更准确地说,编辑器让它看起来好像 x 具有这些属性。

经过一番研究,我得出以下结论:

这些结论正确吗?

围绕此功能的实施还有其他有趣的细微之处吗?

我正在使用 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)

对于布局中的每个视图,插件都会生成这些属性,并将它们放入它们的相关文件中。 ActivityFragment.
上也存在这些属性 因此,当您调用 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 是否存在于 xy 的视图层次结构中,就像他们 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