この記事はデベロッパー プログラム エンジニア、Takeshi Hagikura による Android Developers Blog の記事 "Understanding the performance benefits of using ConstraintLayout " を元に翻訳・加筆したものです。詳しくは元記事をご覧ください。
昨年の Google I/O で発表されてからも、ConstraintLayout のレイアウトの安定性やレイアウト エディタでのサポートの改善は続いています。Chain の導入 や Ratio によるサイズ設定 など、ConstraintLayout ならではの新機能も追加され、さまざまな種類のレイアウトが作りやすくなっています。こういった機能に加え、ConstraintLayout には大きなパフォーマンス上のメリットがあります。本投稿では、このパフォーマンスの改善がもたらすメリットについて説明します。
Android がビューを描画する方法
ConstraintLayout のパフォーマンスを深く理解するため、まずは Android がどのようにビューを描画しているのかについて見てみましょう。
ユーザーが Android のビューにフォーカスを当てると、Android フレームワークの指示でビューが描画されます。この描画プロセスは、次の 3 つのフェーズで構成されます。
Measure システムが上から順にビューツリーをたどり、各 ViewGroup とビュー要素の大きさを決定します。ViewGroup を測定する際は、その子要素も測定します。
Layout 再度、上から順にビューツリーをたどり、各 ViewGroup について測定フェーズで求めたサイズから子要素の位置を決定します。
Draw システムはもう一度上から順にビューツリーをたどります。ビューツリーの各オブジェクトに対して Canvas オブジェクトが作成され、GPU に描画コマンドのリストが送られます。このコマンドには、最初の 2 つのフェーズで求めた ViewGroup および View オブジェクトのサイズと位置が含まれます。
図 1. Measure フェーズでビューツリーをたどる例
描画プロセス内の各フェーズでは、上から順にビューツリーをたどる処理が必要です。そのため、ビュー階層に別のビューが埋め込まれている(ネストしている)ビューが多いほど、端末がビューを描画する際に必要となる時間と計算能力が増加します。つまり、Android アプリのレイアウトでフラットな階層を維持すれば、高速で反応の早いユーザー インターフェースを実現できます。
従来型のレイアウト階層にかかるコスト
以上の説明に留意した上で、LinearLayout オブジェクトと RelativeLayout オブジェクトを使って従来型のレイアウト階層を作成してみましょう。
ここでは、上のイメージのようなレイアウトを作成することを考えてみます。従来型のレイアウトを使って作成する場合、要素の階層を含む XML ファイルは次のようになります(この例では、属性は省いています)。
<RelativeLayout>
<ImageView />
<ImageView />
<RelativeLayout>
<TextView />
<LinearLayout>
<TextView />
<RelativeLayout>
<EditText />
</RelativeLayout>
</LinearLayout>
<LinearLayout>
<TextView />
<RelativeLayout>
<EditText />
</RelativeLayout>
</LinearLayout>
<TextView />
</RelativeLayout>
<LinearLayout >
<Button />
<Button />
</LinearLayout>
</RelativeLayout>
通常、このような種類のビュー階層には改善の余地がありますが、どのような形であっても、いくつかのネストしたビューを作成する必要があります。
先ほど説明したように、ネストした階層はパフォーマンス悪化の原因となる場合があります。では、ネストしたビューが実際にどのように UI のパフォーマンスに影響するかについて、Android Studio の Systrace ツールを使って見てみましょう。各 ViewGroup(ConstraintLayout と RelativeLayout)についてプログラムから Measure フェーズと Layout フェーズを呼び出し、それぞれが実行されている間に Systrace が呼び出されるようにします。次のコマンドを実行すると、時間がかかっているイベントが記録された、HTML ファイルが生成されます。これには、20 秒間に発生した標準より大幅に時間がかかっている Measure や Layout のパスなどが含まれます。
python $ANDROID_HOME/platform-tools/systrace/systrace.py --time=20 -o ~/trace.html gfx view res
Systrace の使用方法の詳細は、Systrace で UI のパフォーマンスを分析する ためのガイドをご覧ください。
Systrace は、このレイアウトに関する(たくさんの)パフォーマンスの問題を自動的に報告するだけでなく、それを修正する方法も提案してくれます。[Alerts] タブをクリックすると、このビュー階層の描画には、 Measure フェーズと Layout フェーズで 80 回ものパスで標準より大幅に時間がかかっていることがわかります。
Measure フェーズと Layout フェーズでコストがかかる多くの処理が呼び出されるというのは、理想からはほど遠いものです。描画に必要な作業が多すぎると、ユーザーが気づくようなフレーム スキップが発生する可能性もあります。結論として、このレイアウトはパフォーマンスが悪いことがわかります。これは、ビュー階層がネストされていることと、各子要素について 2 回測定を行う必要があるという RelativeLayout の特性が原因となっています。
図 3. Systrace で RelativeLayout を利用したレイアウトについてのアラートを確認
この測定に使ったコードは GitHub レポジトリ から確認できます。
ConstraintLayout オブジェクトがもたらすメリット
ConstraintLayout を使ってレイアウトを作成すると、XML ファイルには次のような要素が含まれることになります(今回も属性は省略します)。
<android.support.constraint.ConstraintLayout>
<ImageView />
<ImageView />
<TextView />
<EditText />
<TextView />
<TextView />
<EditText />
<Button />
<Button />
<TextView />
</android.support.constraint.ConstraintLayout>
この例からわかるように、レイアウトの階層は完全にフラットになります。ConstraintLayout を使うと、View 要素や ViewGroup 要素をネストさせずに複雑なレイアウトを作成できます。
たとえば、レイアウトの中ほどにある TextView と EditText に注目してみましょう。
RelativeLayout を使う場合は、新しい ViewGroup を作成して EditText と TextView の縦方向の位置を合わせる必要があります。
<LinearLayout
android:id="@+id/camera_area"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_below="@id/title" >
<TextView
android:text="@string/camera"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:id="@+id/cameraLabel"
android:labelFor="@+id/cameraType"
android:layout_marginStart="16dp" />
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<EditText
android:id="@+id/cameraType"
android:ems="10"
android:inputType="textPersonName"
android:text="@string/camera_value"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginTop="8dp"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp" />
</RelativeLayout>
</LinearLayout>
しかし、ConstraintLayout を使う場合は、TextView のベースラインを EditText と合わせるという制約を追加するだけで同じ効果が得られます。別の ViewGroup を作成する必要はありません。
図 4. EditText と TextView の制約
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintLeft_creator="1"
app:layout_constraintBaseline_creator="1"
app:layout_constraintLeft_toLeftOf="@+id/activity_main_done"
app:layout_constraintBaseline_toBaselineOf="@+id/cameraType" />
ConstraintLayout を使ったレイアウトに対して Systrace ツールを実行すると、同じ 20 秒間でコストがかかる Measure および Layout での標準より時間がかかっているパスがかなり少なくなっていることがわかります。このパフォーマンスの向上は当然です。ビュー階層がフラットになっているからです。
図 5. Systrace で ConstraintLayout を利用したレイアウトについてのアラートを確認
なお、先ほどのレイアウトの ConstraintLayout 版は、レイアウト エディタ だけを使い、XML を直接編集することなく作りました。
パフォーマンスの違いを測定する
ConstraintLayout と RelativeLayout の 2 種類のレイアウトについて、Android 7.0(API レベル 24)で導入された OnFrameMetricsAvailableListener を使ってすべての Measure と Layout のパスにかかる時間を分析してみました。このクラスを使うと、アプリの UI レンダリングについてフレームごとのタイミング情報を収集できます。
次のコードを呼び出すと、フレームごとの UI アクションの記録を開始します。
window.addOnFrameMetricsAvailableListener(
frameMetricsAvailableListener, frameMetricsHandler);
タイミング情報が利用できるようになると、frameMetricsAvailableListener() コールバックが呼び出されます。今回は、Measure と Layout のパフォーマンスを取得したいので、実際のフレームの所要時間を取得する際に FrameMetrics.LAYOUT_MEASURE_DURATION を呼び出します。
Window.OnFrameMetricsAvailableListener {
_, frameMetrics, _ ->
val frameMetricsCopy = FrameMetrics(frameMetrics);
// Layout measure duration in nanoseconds
val layoutMeasureDurationNs =
frameMetricsCopy.getMetric(FrameMetrics.LAYOUT_MEASURE_DURATION);
FrameMetrics で取得できるその他のタイプの所要時間情報の詳細については、FrameMetrics API リファレンスをご覧ください。
測定結果: 高速なのは ConstraintLayout
パフォーマンスの比較から、ConstraintLayout は RelativeLayout より計測フェーズと配置フェーズが約 40% 早くなっていることがわかります。
図 6. 計測と配置(単位: ミリ秒、100 フレームの平均)
この結果からわかるように、ConstraintLayout の方が従来型のレイアウトよりも高速である可能性が高いと言えます。さらに、ConstraintLayout オブジェクトがもたらすメリット のセクションで説明したように、ConstraintLayout には複雑でパフォーマンスのよいレイアウトを作成するための他の機能も備わっています。詳しくは、ConstraintLayout による応答性が高い UI の作成 ガイドをご覧ください。アプリのレイアウトをデザインする際には、ConstraintLayout を使うことをお勧めします。ConstraintLayout を使うと、以前は深くネストさせなければならなかったほとんどすべての場合で、使いやすく最適なパフォーマンスを提供できるレイアウトを実現できます。
付録: 測定環境
上記の測定は、すべて以下の環境で実施しました。
端末
Nexus 5X
Android バージョン
8.0
ConstraintLayout バージョン
1.0.2
次のステップ
デベロッパー ガイド 、API リファレンス ドキュメント 、Medium の記事 を確認し、ConstraintLayout ができることを理解しましょう。また、ConstraintLayout のアルファ リリース以降の数か月間で、フィードバックや問題をお送りくださった皆さん、どうもありがとうございました。おかげさまで今年初め、本番環境で利用できる バージョン 1.0 の ConstraintLayout をリリースできました。ConstraintLayout の改善はこれからも続きますので引き続き Android Issue Tracker からフィードバックをお送りください。
Reviewed by Takeshi Hagikura - Developer Relations Team