安卓-布局性能:编程方式 vs XML

7
作为一名Android开发者,不知道通过编程声明布局还是使用XML声明布局哪种方法更优秀,这让我感到困扰。
我已经阅读了Stack Overflow上的这篇文章这篇文章,但它们都没有回答我的问题:
什么方式更加高效:以编程方式编写布局还是在XML文件中声明布局?
请注意,我只询问性能方面的答案,不需要基于其他因素的答案。
此外,我正在寻找非常技术性的答案。如果需要,请提供指向AOSP代码的链接来证明您的答案(可以假设Android版本为Marshmallow)。更好的方法是指出一个实验/论文/基准测试,在其中使用两种不同的方式比较加载一个巨大的布局的时间。

你想要达到什么样的性能?毕竟,布局最终都会编译成代码,所以并没有太大的区别。 - Tdorno
我会满意地比较Activity的加载时间,尽管还有其他一些基准可以衡量。 - FlyingPumba
@Tdorno这是否意味着XML存在劣势,因为它必须被处理成代码? - FlyingPumba
1个回答

14

对于大多数实际目的而言,两种方法都没有显著的性能影响。如果您需要填充大量特定布局的情况下可能会有所影响,此时您可以尝试自行进行基准测试以查看是否存在任何真正的差异,但除此之外,我很难想象出一个会导致显著影响的场景。

假设您有一个布局:

<LinearLayout xmlns:android="..."
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    ... >

    <Button
        android:id="@+id/some_button"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@drawable/my_button_bg"
        android:text="Hello World" />

    <!-- other views ... -->

</LinearLayout>
发生的事情是,Android将此文件编译为二进制格式并打包到APK中。当您在运行时使用 LayoutInflater 时,它会将此二进制格式的块加载到内存中并解析它,并从其中构建视图层次结构,非常类似于您手动在代码中完成的操作。所有这些解析都在本地代码中完成,因此它们很可能比Java中的典型XML解析要优化得多。 LayoutInflater 遇到标记(例如,<Button .../>)时,使用反射构造视图。第一次必须查找该特定视图的构造函数;之后,它将缓存构造函数以便以后更快地访问。
通常,您会调用修改器(如 button.setText(...)button.setBackground(...) 等),而视图通常在自己的膨胀期间调用这些方法。也就是说,通过视图的构造函数遍历的代码路径将根据从二进制 XML 格式解析出的属性执行这些变异。这是因为 LayoutInflater 使用接受 AttributeSet 的两个参数的构造函数。这里的含义是,当您手动构建视图时,某些方法可能会被调用两次。
例如,看一下上面示例布局中的按钮。按钮已经有一个默认的背景(如何提供它实际上很有趣,但在这里不是非常重要),因此即使只使用 Context 调用只有一个参数的构造函数,仍会得到带有默认背景的 Button。换句话说,代码路径包括调用 setBackground(...)(或一些等效方法)以获取默认背景图像。然后稍后您必须自己使用 XML 文件中命名的自定义可绘制资源调用 setBackground(...)。很难一口气说出这对影响的影响,因为它实际上取决于各个视图的实现和您所做的变异。
最后,我还想说:我曾经在专业环境中构建过一款应用程序,尽可能避免使用 XML(包括布局以外的其他内容)。我可以毫不犹豫地告诉您,这非常麻烦,会大大增加开发时间。即使我非常擅长手动操作视图和层次结构,它仍然比在 XML 中完成要花费更长的时间。
此外:
  • 代码往往非常冗长。
  • 您无法获得围绕 XML 布局构建的所有工具的好处。
  • 即使没有工具,仅 XML 文件本身就可以清楚地表示视图层次结构。
  • 您可能需要了解 UI 框架的某些特殊性(其中一些可能取决于 API 级别)。
  • 并非总是可以将 XML 属性映射到 Java 中的相应修改器方法(这也取决于 API 级别)。
  • 计算尺寸会更有趣。
  • 记住在哪里使用哪个 LayoutParams 是很大的精神体操。
可能还有其他原因,但这些只是我脑海中的一些。别误会,手动操作视图和层次结构的时候确实有很多时候很有价值,但我不会用膨胀来替

同意,用代码实现会更麻烦。 - JAAD
1
@Karakur 很棒的回答。我编写了一个小实验 https://github.com/FlyingPumba/AndroidLayoutsPerformanceBenchmark 并在我的手机上进行了测试(Moto G 1st gen)。通过XML加载包含500个TextView的LinerLayout的活动平均需要541毫秒。以编程方式执行相同操作的平均时间为380毫秒。这是相当大的差异。 您能否解释一下为什么会出现这种情况?根据您的说法,我希望从XML中加载的视图加载速度更快。如果我将视图数量增加到5000,则这种差距会扩大到1秒。 - FlyingPumba
我的想法是,即使Inflater使用了两个参数的构造函数且xml已经被优化,遍历整个树的过程仍比调用特殊构造函数并避免双重调用(例如setBackground())所节省的代价更高。 - FlyingPumba
2
@FlyingPumba 可能使用反射来调用构造函数(而不仅仅是使用 new)也会增加一些开销。然而,我认为这很无关紧要:如果你要填充 500 或 5000(或更多)的任何东西,你应该使用 RecyclerView 并回收视图。 - Karakuri
布局膨胀会导致在ListViewRecyclerView中使用时出现抖动。对于RecyclerView,仅在第一次膨胀布局文件时才会发生这种情况。 - UdaraWanasinghe
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接