Android Developers > Best Practices for Performance > Performance Tips > Improving Layout Performance の中に、Optimizing Layout Hierarchiesという項目があります。
簡単に要約すると
- Viewのネストを深くするな。narrow and deepよりshallow and wideなレイアウトであれ。
- LinearLayoutの
layout_weight
は重い。描画時に2回計算してしまう。(measure twice)
ということらしいです。
コードレビューの時も結構意識して、極力RelativeLayoutを使ってレイアウトを構成するようにしていたのですが、ふと「実際実機で触るとどれだけ違うんだろう」と疑問に思ったので検証してみることにしました。
#検証アプリ
検証用に、Twitter公式アプリと同じレイアウトのTwitterViewerを作ってみることにしました。
リポジトリはこちら。
余談ですが、Twitter公式AndroidSDKとして2014/10に発表されたTwitter Kitを使ってみました。また、appcompat v21を使って、少しマテリアルデザインに対応しています。
メニューからレイアウトを変更できます。見た目はかわりませんが、ListViewのアイテムのxmlが変わります。
-
浅いレイアウト
RelativeLayoutを使って最小限のネストに抑えたレイアウトです。 -
深いレイアウト
LinearLayout + layout_weight使いまくりでネストを極力深くした糞みたいなレイアウトです。 -
深くて画面描画の多いレイアウト
2にbackgroundを指定しまくって描画を多くしたレイアウトです。端末のGPUオーバードローデバッグを有効にすると、画面が真っ赤になります。
1と3で使い勝手を比較してみたら、1の方が断然ぬるぬるに動くはずだよね、という仮説があったわけです。
#LinearLayout と RelativeLayout
浅いレイアウトにするためには、LinearLayoutをやめてRelativeLayoutを使えという話は有名ですが、実際どう違うのかを簡単に説明します。
例えばこのレイアウト。大きく見ると2ペインに分かれています。
これをLinearLayout と RelativeLayoutで書くと、こんな感じになります。
##LinearLayoutの場合
<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の場合
<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で確認します。Hierachy Viewerの使い方はこちらを参考にどうぞ。
##浅いレイアウト
右上のTree Overviewを見ると、浅く広いレイアウトになっていることがわかります。
Layoutに10msかかっているようです。
階層が深いレイアウトになっていることがわかります。
Layoutに29msかかっているようです。
layout_weight
使いまくってるんですが、遅くなるという話だったMeasureは0.2msしかかかっていませんでした。意外です。
ただ、LinearLayout + layout_weightのところはやはり重いようで、赤いアラートが表示されていました。
#実機で確認してみる
自分の端末Android4.3のGalaxy Note3で確認してみましたが、結論から言うと 違いがまったくわかりませんでした。
思いっきりスクロールしてみたり、android:hardwareAccelerated="true"
のオプションを外してみたりしましたが、気になるほどの劣化はありませんでした。
まぁもともと29msしかかかっていないのが10msになっただけなので当然と言えば当然なのですが。
ただ、Android2.3のGalaxyS端末で見てみたところ、少し違いは感じました。深いレイアウトで作った方は、スクロールする時にカクカク感が強かったです。低スペック端末であれば違いが体感できるようです。
#まとめ
- レイアウトのネストは浅い方がいい、というのは正しい。
- ただし、最近の高スペック端末だと体感ではほとんど違いがない。
- 低スペック端末だと若干スクロール時のカクつきに影響する。
- 基本的にRelativeLayoutでできるところはRelativeLayoutで作るべきだが、LinearLayoutで書いた方がスッキリする場合はあまり神経質になる必要はないかも。