アライドアーキテクツ AdventCalendar18日目担当、2回目となりますちくりんです。
先日、再生機器がないのに購入した楽園追放の限定版が届きまして、サントラにテーマ曲のディンゴ版やFS版が収録されていることを期待してましたが、入ってませんでした…
残念でなりません、今後に期待します。
前置き
さて、今回はAndroidの勉強中に作ったアプリの修正について紹介したいと思います。
私がAndroidを始めたのは7月下旬でして、もうあれから5ヶ月経ってるわけなんですが、当初はAndroid端末に触るのすら初めてで、あれこれ試行錯誤しながら進めていた覚えがあります。
勉強中のお題として、CroudiaというSNSサービスのクライアントを作ったのですが、右も左も分からない状態でしたので、非常に残念な出来栄えとなっています。
今回はこのアプリを当時知らなかったAndroidの文化に従って、あれこれ修正しようと思います(もう作りなおした方がいいレベルなのですが、それは別の機会に)。
修正は公式のPerformance Tipsを基に進めます。
Androidアプリのパフォーマンス
AndroidではあまりにもJavaJavaした実装は、パフォーマンスを下げる原因に成り得ます。
現行のAndroid端末はスペックが高いので、あまり影響を感じないこともありますが、古い端末を使うユーザもある程度存在するので、このTipsがAndroidアプリを開発する上での一つのセオリーになっています。
さて、細かな所は前記したPerformance Tipsを参照してもらうとして、この中でもIDEを使ってパフォーマンスチェックが可能で、簡単に修正可能なViewのレイアウトについて触れたいと思います。
ようは必要以上にViewを使うな、ネストするなってことなんですが、LinearLayoutなどを使っているとその分だけViewの構造が深くなってしまいます。
他の方法を知らなかった事やウィジェットの構造が分かりやすくなる事から多用していたのですが、あまりよろしくないようなので今回修正して、パフォーマンスの確認をしてみます。
また、LinearLayoutとlayout_weightの組合せもよろしくないそうですが、今回のレイアウトでは使っていなかったので割愛します。
ここでは触れませんが、端末の「開発者向けオプション」という機能を使っても、画面周りのパフォーマンスが確認できます(最近まで知りませんでした、面白いですねこれ。境界表示が以外に便利です)。
Hierarchy Viewer
レイアウトのパフォーマンスを確認するツールとして役立つのがHierarchy Viewerです。このツールも最近存在を知りました。
このツールはアプリに使われているViewの構成をマッピングし、さらに描画に費やした時間を表示してくれます。
表示される時間は3種類存在し、Viewのサイズ決定に掛かる時間を表すMeasure、View配置時間のLayout、描画時間のDrawです。
Android SDKの中のtoolsに入っていますがAndroid Studioからの起動も可能で、下の画像の赤枠のボタンが相当します。
下の画像が実際にHierarchy Viewerを使って、左に表示されている画面をアタッチしたものです。
そして下の画像は今回修正するアプリのメイン画面をHierarchy Viewerで表示したときの全体図になります。
中央に深いネストがありますが、ViewPagerだったりSwipeRefreshだったりするので、こいつらはほっといて、赤で囲まれたListViewに相当するレイアウトを修正していきます。
ちなみに、1セル当たりで描画される画面とViewの構成はこのような状態になってます。
ちょいちょいインジケータが赤くなってますね…
これを解消していきましょう。
LinearLayoutの修正
まずは、RelativeLayoutを使って、LinearLayoutのネストをなくしていきます。
RelativeLayoutでは、親のウィジェットを基にして相対的な横軸を決めるlayout_alignParentLeftやlayout_alignParentRight、RelativeLayoutの子ウィジェットを基に相対的な横軸を決めるlayout_toLeftOfやlayout_toRightof、縦軸を決めるlayout_above、layout_belowなどが存在します。
各パラメータの詳細はここを参照してもらうとして、これらを用いてネストを浅くしていきます。
修正前のコードと修正後のコードの一部が以下になります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
<!--これとか--> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:padding="6dp"> <ImageView android:id="@+id/iconView" android:layout_width="@dimen/icon_image" android:layout_height="@dimen/icon_image" android:adjustViewBounds="true"/> <!--これとか--> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:layout_marginLeft="4dp"> <!--これも--> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal"> <TextView android:id="@+id/nameView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:ellipsize="end" android:scrollHorizontally="true" android:singleLine="true" /> </LinearLayout> </LinearLayout> |
Layout以外にもちょいちょい変わってますが、修正したものが下です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:padding="6dp"> <ImageView android:id="@+id/iconView" android:layout_width="@dimen/icon_image" android:layout_height="@dimen/icon_image" android:adjustViewBounds="true" /> <TextView android:id="@+id/nameView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_toRightOf="@+id/iconView" android:ellipsize="end" android:scrollHorizontally="true" android:singleLine="true" android:layout_marginLeft="@dimen/marginCell" /> <TextView android:id="@+id/idView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_toRightOf="@+id/nameView" android:textAppearance="?android:attr/textAppearanceSmall" android:textColor="@color/text_sub" android:layout_marginLeft="@dimen/marginCell" android:singleLine="true" /> <TextView android:id="@+id/textView" android:layout_toRightOf="@+id/iconView" android:layout_below="@+id/nameView" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginLeft="@dimen/marginCell" /> |
ざっくり解説すると、
TextView(nameView)はImageView(iconView)の右に有るので、
1 |
android:layout_toRightOf="@+id/iconView" |
TextView(idView)はTextView(nameView)の同じ行かつ右に有るので、
1 |
android:layout_toRightOf="@+id/nameView" |
TextView(textView)はImageView(iconView)の右、TextView(nameView)の下に位置するので、
1 2 |
android:layout_toRightOf="@+id/iconView" android:layout_below="@+id/nameView" |
の2つを設定します。
こんなかんじでセルのウィジェットに相対的な位置を設定します。
はい、正直やり過ぎました。
もともとLinearLayoutで設定していたマージンなども、それぞれのウィジェットに設定する必要があるため、とても面倒ですね。
ともあれ、上に掲載したComponent Treeや下のHierarchy Viewer見てもらえば分かる通り、見た目だいぶスッキリしました。
それでは、Hierarchy Viewerを見てみます。
セルに含まれるウィジェットのインジケータもだいぶ綺麗になってます。
さて、気になる処理時間の方です。
元々のListViewはこちら。
はい、ほぼ変わらないかものによっては時間増えてますね。
明らかに減ったのはListViewに含まれるViewの数くらいですか…
Hierarchy Viewerに表示される処理時間は確認するたびに多少変化するので、これくらいだと誤差の範疇でしょうか?
ともあれ、ListView内のView数が減ったの良いですね。セルは繰り返し描画されますし、セルの再利用を行うとはいえメモリの節約にも繋がります。
まとめ
今回は、Googleの公開するPreference Tipsからレイアウトについてピックアップしてみました。
結果的に見られた変化は誤差の範疇内でしたが、スペックの低い端末では影響があるかもしれません(残念ながら検証できる端末がNexus5しか手元にありませんでした)。
ともあれ、2、3ネストしたくらいでは大きな影響はないようですので、Listの中にListをネストするような特異なレイアウトでなければ問題なさそうです。
それよりもListViewの上位に存在するSwipeRefreshLayoutや、その更に上位に存在するViewPagerも気にかかります。
これらの実装を見てもらえば分かる通り、どちらもSupportLibraryによって提供される機能でViewGroupです。また、2つともListViewとセットで使う頻度が高いので、知らぬ間にViewのネストが深くなってしまいます。
最近の端末は高スペックですし、早々ないと思いますが、例えばTwitterライクなUIを実装する時はレイアウトの構造を見てみると良いかもしれません。
長くなりましたがこの辺りで
AdventCalendar18日目は、最近Nexus5が欲しいちくりんでした。
次回19日はPerfumeエンジニアの新井さんの出番です。
多少道草を食っていたため、3年遅れの2014年新卒ですが無害です。 はじめてのWeb系と思いきや、いつの間にかAndroidに関わることになりました。Android初心者ですがご容赦を。