【问题标题】:Efficiency of findViewByIdfindViewById 的效率
【发布时间】:2014-09-25 11:41:59
【问题描述】:

可能大多数 Android 开发人员都知道findViewById 并不是一个便宜的操作。我们大多数人都知道的另一件事是,您可以通过使用视图层次结构的最小子树来通过 id 查找视图来提高性能,例如:

<LinearLayout
    android:id="@+id/some_id_0">
    <LinearLayout
        android:id="@+id/some_id_1">
        <LinearLayout
            android:id="@+id/some_id_2">
            <TextView android:id="@+id/textview" />
        </LinearLayout>
    </LinearLayout>
</LinearLayout>

在这种情况下,您可能希望使用id == @id/textviewLinearLayout 中进行搜索

但是,如果层次结构不是级联的,而是在每个级别上都有分支,并且您想在“叶子”上找到视图,假设是什么?您是否执行findViewById 以通过查找父级到达分支的底部,或者您是否在更大的子集上执行findViewById?我认为一个简单的答案是这取决于具体情况,但也许我们可以概括一下它真正取决于什么?

谢谢

编辑:

我所说的更大的子集是这样的:

<RelativeLayout android:id="@+id/r0">    
    <RelativeLayout
        android:id="@+id/r1">
        <LinearLayout
            android:id="@+id/some_id_0">
            <LinearLayout
                android:id="@+id/some_id_1">
                <LinearLayout
                    android:id="@+id/some_id_2">
                    <TextView android:id="@+id/textview0" />
                </LinearLayout>
            </LinearLayout>
        </LinearLayout>
        <LinearLayout
            android:id="@+id/some_id_3">
            <LinearLayout
                android:id="@+id/some_id_4">
                <LinearLayout
                    android:id="@+id/some_id_5">
                    <TextView android:id="@+id/textview1" />
                </LinearLayout>
            </LinearLayout>
        </LinearLayout>
    </RelativeLayout>
    <LinearLayout
        android:id="@+id/some_id_6">
        <LinearLayout
            android:id="@+id/some_id_7">
            <LinearLayout
                android:id="@+id/some_id_8">
                <TextView android:id="@+id/textview2" />
                <TextView android:id="@+id/textview3" />
                <TextView android:id="@+id/textview4" />
            </LinearLayout>
        </LinearLayout>
    </LinearLayout>
</RelativeLayout>

所以问题是如果我想在LinearLayout@+id/some_id_8 中查看findViewById TextView 视图,我应该在整个容器上执行此操作,还是应该在findViewById LinearLayout@+id/some_id_8 和在这个视图上findViewById 所有TextViews

【问题讨论】:

  • 您能否解释一下您的 2 个备选方案,尤其是“更大子集上的 findViewById”?
  • 请检查我的编辑。
  • default traversal 是深度优先。首先查找子树听起来像是微优化,它使代码更难维护。衡量一下是否真的有影响
  • 正如 laalto 已经提到的 findViewById() 将遍历整个视图层次结构,因此使用两个 findViewById() 调用不会有任何区别(除了您将有一行额外的代码)。如果您对视图层次结构有深入的了解,则可以使用 getChild() 和 findViewById() 的组合,这在某些情况下应该更快(这并不重要),但会使代码对任何未来的更改都非常严格。最后,视图层次结构的深度和大小对于性能而言将比搜索这些视图更重要。

标签: android performance android-layout findviewbyid


【解决方案1】:

如果您直接查找View,或者您先查找父母然后查找孩子,这完全没有区别。但是,例如,如果您想检索LinearLayout 中的三个TextViews,ID 为some_id_8,那么首先查找LinearLayout 然后查找TextViews 会更好地提高性能。但差别微乎其微。真正的问题是布局本身(下面会详细介绍)。

通常findViewById() 并不是万恶之源。如果您必须在每次getView() 调用期间调用findViewById() 甚至可能多次调用ListView,这可能是一个问题,但这就是视图持有者模式的用途。

当性能至关重要时,请尽可能少致电findViewById()。在FragmentActivity 中,您可以在onCreateView()onCreate() 中查找您将需要的所有Views。如果您将引用保存在几个成员变量中,您将永远不必再次调用它。


现在要解释为什么findViewById() 可能是一个性能问题,我们必须看看它的实现,this link leads to the Android 4.4.4 View source code

public final View findViewById(int id) {
    if (id < 0) {
        return null;
    }
    return findViewTraversal(id);
}

所以findViewById() 只是检查 id 是否有效,如果是,则调用受保护的方法 findViewTraversal()。在View 中,它是这样实现的:

protected View findViewTraversal(int id) {
    if (id == mID) {
        return this;
    }
    return null;
}

它只是检查传入的 id 是否等于 View 的 id,如果是则返回 this,否则返回 null。有趣的部分是ViewGroupthis links leads to the Android 4.4.4 ViewGroup source codefindViewTraversal()实现:

protected View findViewTraversal(int id) {
    if (id == mID) {
        return this;
    }

    final View[] where = mChildren;
    final int len = mChildrenCount;

    for (int i = 0; i < len; i++) {
        View v = where[i];

        if ((v.mPrivateFlags & PFLAG_IS_ROOT_NAMESPACE) == 0) {
            v = v.findViewById(id);

            if (v != null) {
                return v;
            }
        }
    }

    return null;
}

此方法顶部的第一个 if 与 View 实现中的相同,它只是检查传入的 id 是否等于 ViewGroup 的 id,如果是则返回自身。之后,它循环遍历所有子节点并在每个子节点上调用findViewById(),如果此调用的返回值不是null,那么我们正在寻找的View 已经找到并将返回。

如果您想了解更多有关ViewsViewGroups 工作原理的详细信息,我建议您自己研究源代码!


所以这一切看起来都很简单。视图层次结构本质上是像树一样遍历的。根据您的布局中有多少Views,这可能会使它变得非常昂贵或非常快。您的布局是否如下所示并不重要:

<LinearLayout android:id="@+id/some_id_0">
    <LinearLayout android:id="@+id/some_id_1">
        <LinearLayout android:id="@+id/some_id_2">
            <TextView android:id="@+id/textview0" />
        </LinearLayout>
    </LinearLayout>
</LinearLayout>

或者如果它看起来像这样:

<LinearLayout android:id="@+id/some_id_0">
    <LinearLayout android:id="@+id/some_id_1" />
    <LinearLayout android:id="@+id/some_id_2" />
    <TextView android:id="@+id/textview0" />
</LinearLayout>

因为Views 的数量在两种情况下都相同,并且findViewById() 的性能与Views 的数量成比例。

但是一般规则是您应该尝试降低布局的复杂性以提高性能,并且您应该经常使用RelativeLayout。这之所以有效,是因为如果你降低了复杂性,你也会减少布局中Views 的数量,而RelativeLayouts 非常擅长降低复杂性。让我来说明一下,图像你有这样的布局:

<LinearLayout android:id="@+id/some_id_0">
    <RelativeLayout android:id="@+id/some_id_5">
        <LinearLayout android:id="@+id/some_id_1" />
        <LinearLayout android:id="@+id/some_id_2" />
    </RelativeLayout>
    <RelativeLayout android:id="@+id/some_id_6">
        <LinearLayout android:id="@+id/some_id_3" />
        <LinearLayout android:id="@+id/some_id_4" />
    </RelativeLayout>
</LinearLayout>

想象一下,在这种情况下,上面的两个RelativeLayouts 都只是以某种特殊方式定位内部LinearLayouts,而外部LinearLayout 只是在那里将RelativeLayouts 定位在彼此下方。您可以非常轻松地构建相同的布局,只需一个 RelativeLayout 作为根,四个 LinearLayouts 作为子:

<RelativeLayout android:id="@+id/some_id_0">
    <LinearLayout android:id="@+id/some_id_1" />
    <LinearLayout android:id="@+id/some_id_2" />
    <LinearLayout android:id="@+id/some_id_3" />
    <LinearLayout android:id="@+id/some_id_4" />
</RelativeLayout>

并且该布局的性能会比上面的布局更好,不是因为RelativeLayout在性能方面比LinearLayout更好,也不是因为布局更平坦,而仅仅是因为Views的数量在布局中较低。这同样适用于几乎所有其他与视图相关的过程,如绘图、布局、测量。一切都会更快,因为布局中Views 的数量更少。


回到你原来的问题:如果你想提高性能而不是降低布局的复杂性。绝对没有理由有这么多嵌套的LinearLayouts。您的“大子集”几乎可以肯定地简化为:

<RelativeLayout android:id="@+id/r0">  
    <TextView android:id="@+id/textview0" />
    <TextView android:id="@+id/textview1" />
    <TextView android:id="@+id/textview2" />
    <TextView android:id="@+id/textview3" />
    <TextView android:id="@+id/textview4" />
</RelativeLayout>

这样的布局肯定会大大提升性能。

【讨论】:

  • “可能大多数 Android 开发人员都知道 findViewById 不是一个便宜的操作”的问题是,似乎很少有人认为它实际上做了什么。甚至谷歌自己的喉舌似乎根本没有停下来考虑它。例如,如果您在布局中像通常用于列表视图项目的那样有单个数字数量的视图并且您使用转换视图,则列表视图的广受吹捧的“视图持有者”模式根本不会改善任何事情。单个可绘制或文本更改将比视图搜索慢。我希望有人能对其进行基准测试..
  • RelativeLayout is somehow performance-wise better than a LinearLayout 这对我来说似乎有 90% 的错误。
  • @mvai 不是LinearLayout、许多LinearLayouts 或任何复杂的布局。当然,RelativeLayout 在性能方面会比LinearLayout 更差,原因有很多,例如需要在RelativeLayouts 中进行两次布局传递等。但是,如果您将RelativeLayout 用于它的用途 - 创建复杂的相对布局,而不必嵌套许多布局,那么RelativeLayout 真的很出色。当然,今天一切都不同了。 CoordinatorLayout 及其 Behaviors 非常灵活,可以快速实现非常复杂的布局。
  • @mvai 所以现在大多数布局都是基于CoordinatorLayout 构建的,但RelativeLayout 仍然适用于它的目的:简化布局。我的意思是看看问题中的许多嵌套布局。你真的认为所有这些LinearLayouts 在性能方面都比只使用一个RelativeLayout 更好吗?这正是我的答案。
  • 好的,现在听起来好多了 :-) 抱歉,如果我的评论显得粗鲁。我不知道RelativeLayout 在什么时候开始比嵌套的LinearLayout 表现得更好,但是是的,它肯定在某个时候确实如此。只是想指出它永远不会比 single LinearLayout 更好(由于两个布局通道)。
【解决方案2】:

查看那个 android 源代码,我根本不知道 findViewById 是多么昂贵。它基本上是一个简单的循环,我知道它是递归的,所以你会付出不断切换堆栈的代价,但即使是最复杂的屏幕也将是几十个视图而不是几千个,除了循环列表。我想我们需要在低端设备上进行基准测试才能知道这是否真的是一个问题。

【讨论】:

  • 我想是的。在 listview 等循环中 findViewById 之前,它的成本很高。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-02-12
  • 1970-01-01
  • 2023-03-03
  • 1970-01-01
相关资源
最近更新 更多