Viewのネストはどのくらい遅いのか ー 12階層のレイアウトを5階層にして検証してみた


Android Developers > Best Practices for Performance > Performance Tips > Improving Layout Performance の中に、[Optimizing Layout Hierarchies](http://developer.android.com/training/improving-layouts/optimizing-layout.html)という項目があります。

簡単に要約すると

* Viewのネストを深くするな。narrow and deepよりshallow and wideなレイアウトであれ。
* LinearLayoutの`layout_weight`は重い。描画時に2回計算してしまう。（measure twice）

ということらしいです。

コードレビューの時も結構意識して、極力RelativeLayoutを使ってレイアウトを構成するようにしていたのですが、ふと「実際実機で触るとどれだけ違うんだろう」と疑問に思ったので検証してみることにしました。

#検証アプリ
検証用に、[Twitter公式アプリ](https://play.google.com/store/apps/details?id=com.twitter.android)と同じレイアウトのTwitterViewerを作ってみることにしました。
リポジトリは[こちら](https://github.com/konifar/android-layout-performance)。

![Skitch_Image.png](https://qiita-image-store.s3.amazonaws.com/0/15267/0b6d2cba-fc05-1454-9a4d-d494e3e424fd.png)

余談ですが、Twitter公式AndroidSDKとして2014/10に発表された[Twitter Kit](https://dev.twitter.com/twitter-kit/android)を使ってみました。また、appcompat v21を使って、少しマテリアルデザインに対応しています。

![Skitch_Image.png](https://qiita-image-store.s3.amazonaws.com/0/15267/54c61897-f846-3c5b-8b96-5bd410c89b1c.png)

メニューからレイアウトを変更できます。見た目はかわりませんが、ListViewのアイテムのxmlが変わります。

1. [浅いレイアウト](https://github.com/konifar/android-layout-performance/blob/master/app/src/main/res/layout/item_tweet_shallow.xml)
RelativeLayoutを使って最小限のネストに抑えたレイアウトです。

2. [深いレイアウト](https://github.com/konifar/android-layout-performance/blob/master/app/src/main/res/layout/item_tweet_narrow.xml)
LinearLayout + layout_weight使いまくりでネストを極力深くした糞みたいなレイアウトです。

3. [深くて画面描画の多いレイアウト](https://github.com/konifar/android-layout-performance/blob/master/app/src/main/res/layout/item_tweet_narrow_over_drawn.xml)
2にbackgroundを指定しまくって描画を多くしたレイアウトです。端末のGPUオーバードローデバッグを有効にすると、画面が真っ赤になります。


1と3で使い勝手を比較してみたら、1の方が断然ぬるぬるに動くはずだよね、という仮説があったわけです。

#LinearLayout と RelativeLayout

浅いレイアウトにするためには、LinearLayoutをやめてRelativeLayoutを使えという話は有名ですが、実際どう違うのかを簡単に説明します。
例えばこのレイアウト。大きく見ると2ペインに分かれています。

![Skitch_Image.png](https://qiita-image-store.s3.amazonaws.com/0/15267/d4628a05-3f89-cc47-7930-15199aac7a5f.png)

これをLinearLayout と RelativeLayoutで書くと、こんな感じになります。

##LinearLayoutの場合

```xml
<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal">

    <ImageView
        android:id="@+id/img_user"
        style="@style/UserImage" />

    <LinearLayout
        android:layout_width="0dp"
        android:layout_marginLeft="@dimen/spacing_large"
        android:layout_marginStart="@dimen/spacing_large"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:orientation="vertical">
        
        <LinearLayout
            android:id="@+id/container_user_name"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:orientation="horizontal">

            <TextView
                android:id="@+id/txt_user_name"
                style="@style/TextUserName"
                android:layout_width="wrap_content"               
                android:layout_height="wrap_content" />

            <TextView
                android:id="@+id/txt_user_screen_name"
                style="@style/TextSub"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginLeft="@dimen/spacing_small"
                android:layout_marginStart="@dimen/spacing_small" />
            
            <!-- 略 -->

        </LinearLayout>

    </LinearLayout>

</LinearLayout>
```

##RelativeLayoutの場合

```xml
<ImageView
    android:id="@+id/img_user"
    style="@style/UserImage"
    android:layout_below="@id/txt_retweeted_msg"
    android:layout_marginEnd="@dimen/spacing_large"
    android:layout_marginRight="@dimen/spacing_large" />

<TextView
    android:id="@+id/txt_user_name"
    style="@style/TextUserName"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" />

<TextView
    android:id="@+id/txt_user_screen_name"
    style="@style/TextSub"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginLeft="@dimen/spacing_small"
    android:layout_marginStart="@dimen/spacing_small"
    android:layout_toEndOf="@id/txt_user_name"
    android:layout_toRightOf="@id/txt_user_name" />

<!-- 略 -->
```

わかりやすくするために少し極端に書いていますが、RelativeLayoutの`layout_above`や`layout_toEndOf`などを使うとLinearLayoutよりネストを少なく書くことができます。

#Hierarchy Viewerでレイアウトの階層構造を確認

実際に作ったレイアウトを、[Hierarchy Viewer](http://developer.android.com/tools/help/hierarchy-viewer.html)で確認します。Hierachy Viewerの使い方は[こちら](http://qiita.com/konifar/items/d2b6aa133df688aa5c92)を参考にどうぞ。

##浅いレイアウト

![Android_Device_Monitor.png](https://qiita-image-store.s3.amazonaws.com/0/15267/b718259a-d6f1-b9cd-3f29-7cdfe0bd25eb.png "Android_Device_Monitor.png")

右上のTree Overviewを見ると、浅く広いレイアウトになっていることがわかります。
Layoutに10msかかっているようです。

##深いレイアウト
![Android_Device_Monitor.png](https://qiita-image-store.s3.amazonaws.com/0/15267/91b1d781-87b2-cd4c-2f0c-849b72f27425.png "Android_Device_Monitor.png")

階層が深いレイアウトになっていることがわかります。
Layoutに29msかかっているようです。
`layout_weight`使いまくってるんですが、遅くなるという話だったMeasureは0.2msしかかかっていませんでした。意外です。

ただ、LinearLayout + layout_weightのところはやはり重いようで、赤いアラートが表示されていました。

![Android_Device_Monitor.png](https://qiita-image-store.s3.amazonaws.com/0/15267/69b72df7-a1f0-28cf-2e5c-8ec683212bb3.png "Android_Device_Monitor.png")


#実機で確認してみる
自分の端末Android4.3のGalaxy Note3で確認してみましたが、結論から言うと **違いがまったくわかりませんでした。** 
思いっきりスクロールしてみたり、`android:hardwareAccelerated="true"`のオプションを外してみたりしましたが、気になるほどの劣化はありませんでした。
まぁもともと29msしかかかっていないのが10msになっただけなので当然と言えば当然なのですが。

ただ、Android2.3のGalaxyS端末で見てみたところ、少し違いは感じました。深いレイアウトで作った方は、スクロールする時にカクカク感が強かったです。低スペック端末であれば違いが体感できるようです。

#まとめ

* レイアウトのネストは浅い方がいい、というのは正しい。
* ただし、最近の高スペック端末だと体感ではほとんど違いがない。
* 低スペック端末だと若干スクロール時のカクつきに影響する。
* 基本的にRelativeLayoutでできるところはRelativeLayoutで作るべきだが、LinearLayoutで書いた方がスッキリする場合はあまり神経質になる必要はないかも。