RecyclerViewのパフォーマンスチューニング

パフォーマンスチューニングと書くと大げさかもしれない。 最近何度か RecyclerViewのパフォーマンス面の修正をする機会があったので、最低限これくらい見ておくと良さそう、という項目についてまとめました。

各アイテムのstable ID

Adapterの各アイテムが固有のIDを持つ場合、stable IDを利用するとnotifyDataSetChanged() 呼び出し時の各アイテム再描画など様々な場面で有利になります。

利用するためには、AdapterのgetItemId(int position)を実装した上でsetHasStableIds(true)を呼び出します。

https://developer.android.com/reference/android/support/v7/widget/RecyclerView.Adapter.html#setHasStableIds(boolean)

notify〜系を正しく呼び出す

Adapterの内容を更新する際、Adapter#notifyDataSetChanged()を呼んでしまいがちですがnotifyItemInserted()notifyItemRemoved()を適切に呼び出すことで不要なViewの再描画を防ぐことができます。 この修正もstable IDを実装しておくとより効果が高くなりそうです。

なお、Support Library 24.2.0 からは DiffUtilが追加され、notify〜を個別に呼ばなくてもリストの比較処理を行うメソッドをいくつか実装するだけでadapterの更新ができるようになりました。

https://developer.android.com/reference/android/support/v7/util/DiffUtil.html

アイテムのキャッシュ

LinearLayoutManagerの場合、getExtraLayoutSpace()をOverrideし、大きな値を返すことによってアイテムをプリキャッシュさせることができます。 これにより、プリキャッシュのためのコストと引き換えにスクロール時のパフォーマンスを改善させることができます。 どのような値が適しているかは状況次第ですが、LinearLayoutManagerのorientationにあわせて画面の縦横サイズをそのまま返す実装が一般的なようです(多分)。

https://developer.android.com/reference/android/support/v7/widget/LinearLayoutManager.html#getExtraLayoutSpace(android.support.v7.widget.RecyclerView.State)

また、RecyclerView自体もsetItemViewCacheSize()というメソッドによりViewキャッシュの個数を増やすことができ、こちらもスクロール時のパフォーマンスに影響します。

https://developer.android.com/reference/android/support/v7/widget/RecyclerView.html#setItemViewCacheSize(int)

onViewRecycled を活用する

onBindViewHolder()で時間のかかる処理を行っている場合、onViewRecycled()でキャンセルする処理を入れましょう。

RecyclerViewサイズの固定化

Adapterの内容がRecyclerViewのサイズに影響しない場合、setHasFixedSize (true)を設定することでパフォーマンスが向上します。

https://developer.android.com/reference/android/support/v7/widget/RecyclerView.html#setHasFixedSize(boolean)

ネストされたRecyclerViewのプリフェッチ

縦方向のRecyclerViewの中に横方向のRecyclerViewをネストする、いわゆるカルーセル表示のUIの場合、setInitialPrefetchItemCountで初期表示されるアイテムの個数を設定しておくことで親RecyclerViewのスクロール時に有利になるようです。 (Support Library 25.1.0で追加されましたが、まだ十分検証していません)

https://developer.android.com/reference/android/support/v7/widget/LinearLayoutManager.html#setInitialPrefetchItemCount(int)

Adapter差し替え処理の最適化

何らかの理由でRecyclerViewに同一クラス/別インスタスなAdapterを設定する場合、setAdapter()の代わりにswapAdapter()を利用することができます。 swapAdapter()はRecycledViewPoolをクリアしないためViewが再利用されパフォーマンス面で有利となります。 先述の stable ID を設定している場合はより有用です。

https://developer.android.com/reference/android/support/v7/widget/RecyclerView.html#swapAdapter(android.support.v7.widget.RecyclerView.Adapter,%20boolean)

LayoutParamsを動的に生成しない

RecyclerViewnに限った話ではないですが、LayoutParamsを動的に生成してsetするのは避けたほうが良いです。 稀にStaggeredGridLayoutManager.LayoutParamssetFullSpan()を呼び出すためだけに動的に生成しているコードを見かけますが、通常は アイテムのroot viewからgetLayoutParams()するとStaggeredGridLayoutManager.LayoutParamsになっているので、これを再利用してください。

これはパフォーマンスへの影響ももちろんですが、レイアウトxmlに記述したlayout_widthやlayout_height、marginの設定消失を避ける意味でも重要です。

2016年発売Android端末のdp解像度まとめ

はじめに

一昨年、昨年とその年に発売された端末のdp解像度について(わかる範囲で)まとめてQiitaに投稿していたのですが、今年はブログに書きます。

理由としては、Androidディスプレイサイズの変更機能マルチウィンドウのサポートが追加された結果、Androidアプリを作る上で端末dp解像度の重要性が下がり、アプリを作る際の情報としてあまり役に立たなくなったと考えたためです。

一応、個人的にAndroid端末スペックの傾向が知りたくて集計を取ったのでその結果をブログで共有します。

各端末の情報は以下のサイトを参考にまとめました。

www.smph.info

www.tblt.info

www.nttdocomo.co.jp

www.au.kddi.com

www.support.softbankmobile.co.jp

スマートフォン

スマートフォンの端末傾向として、320dp幅をもつ端末が激減したことが挙げられます(※)。 もともと320dp端末はほぼ4インチ台の端末にしか存在しないため、スマートフォン全体の大型化が320dp端末の減少につながっていると思われます。 ただし、前述のディスプレイサイズ変更機能により320dp解像度に変更することができるようになったため、引き続き320dpへの対応は必要です。

また、昨年のSAMURAI KIWAMIのようなppiとdpiが極端に異なる端末はやはり今年も存在していて、特に6インチ台に集中していることから、大型端末における幅360dpの維持には若干無理が出てきているように思えます。 一方で、Zenfone 3 Ultraのように端末サイズにあわせて表示領域を拡大してXperia Z Ultraと同じ540dpの表示領域を持つ端末も再登場しています。 どちらもNexus系の端末のようにデフォルト横幅411dp(+dp解像度変更機能アリ)になっているとある程度解消されると思うので、来年はNexus/Pixel系以外の端末でもこれらのdp解像度機種が出ることに期待したいです。

※正確にはauなどが320dp幅をもつAndroid端末を発売したりもしているのですが、これはGooglePlay非対応だったため集計に含めていません。

dp解像度 量子化密度 端末数
320x534 hdpi 1
360x640 xhdpi 23
360x640 xxhdpi 31
360x640 xxxhdpi 7
540x960 xxhdpi 1

スマートフォンの一覧

機種名 発売日 対角線帳 W(解像度) H(解像度) 量子化密度 W(dp) H(dp) 備考
MUSASHI 2016/03/26 4 480 800 hdpi 320 534
ZenFone Go (ZB551KL) 2016/04/02 5.5 720 1280 xhdpi 360 640
ZenFone Max (ZC550KL) 2016/03/18 5.5 720 1280 xhdpi 360 640
SHINE LITE 2016/12/16 5 720 1280 xhdpi 360 640
URBANO (V03) 2016/12/09 5 720 1280 xhdpi 360 640
AQUOS L 2016/12/08 5 720 1280 xhdpi 360 640
AQUOS U (SHV37) 2016/11/18 5 720 1280 xhdpi 360 640
AQUOS EVER (SH-02J) 2016/11/04 5 720 1280 xhdpi 360 640
シンプルスマホ3 2016/09/09 5 720 1280 xhdpi 360 640
BASIO2 (SHV36) 2016/08/05 5 720 1280 xhdpi 360 640
507SH (Android One) 2016/07/29 5 720 1280 xhdpi 360 640
arrows M03 2016/07/28 5 720 1280 xhdpi 360 640
BLADE E01 2016/07/20 5 720 1280 xhdpi 360 640
BLADE V7 Lite 2016/07/20 5 720 1280 xhdpi 360 640
arrows SV (F-03H) 2016/07/06 5 720 1280 xhdpi 360 640
AQUOS U (SHV35) 2016/06/24 5 720 1280 xhdpi 360 640
DIGNO F 2016/06/24 5 720 1280 xhdpi 360 640
DIGNO E (503KC) 2016/06/10 5 720 1280 xhdpi 360 640
mode1 (MD-01P) 2016/04/20 5 720 1280 xhdpi 360 640
Y6 2016/04/15 5 720 1280 xhdpi 360 640
Priori 3S LTE 2016/02/12 5 720 1280 xhdpi 360 640
Qua phone 2016/02/05 5 720 1280 xhdpi 360 640
MONO (MO-01J) 2016/12/09 4.7 720 1280 xhdpi 360 640
Xperia X Compact (SO-02J) 2016/11/02 4.6 720 1280 xhdpi 360 640
ZenFone 3 Ultra (ZU680KL) 2016/12/09 6.8 1080 1920 xxhdpi 540 960 横幅540dp
Mate 9 2016/12/16 5.9 1080 1920 xxhdpi 360 640
ZenFone 3 Deluxe (ZS570KL) 2016/10/07 5.7 1080 1920 xxhdpi 360 640
arrows NX (F-01J) 2016/12/02 5.5 1080 1920 xxhdpi 360 640
ZenFone 3 Laser (ZC551KL) 2016/11/26 5.5 1080 1920 xxhdpi 360 640
Moto Z Play 2016/10/15 5.5 1080 1920 xxhdpi 360 640
ZenFone 3 Deluxe (ZS550KL) 2016/10/07 5.5 1080 1920 xxhdpi 360 640
Moto G4 Plus 2016/07/22 5.5 1080 1920 xxhdpi 360 640
Blade V580 2016/03/28 5.5 1080 1920 xxhdpi 360 640
GR5 2016/02/12 5.5 1080 1920 xxhdpi 360 640
ZenFone Zoom (128GB) (ZX551ML) 2016/02/05 5.5 1080 1920 xxhdpi 360 640
ZenFone Zoom (32/64GB) (ZX551ML) 2016/02/05 5.5 1080 1920 xxhdpi 360 640
AQUOS SERIE (SHV34) 2016/06/10 5.3 1080 1920 xxhdpi 360 640
AQUOS Xx3 2016/06/10 5.3 1080 1920 xxhdpi 360 640
AQUOS ZETA (SH-04H) 2016/06/10 5.3 1080 1920 xxhdpi 360 640
P9 lite PREMIUM 2016/11/25 5.2 1080 1920 xxhdpi 360 640
IDOL 4 2016/11/22 5.2 1080 1920 xxhdpi 360 640
Xperia XZ 2016/11/02 5.2 1080 1920 xxhdpi 360 640
Xperia XZ (SO-01J) 2016/11/02 5.2 1080 1920 xxhdpi 360 640
Xperia XZ (SOV34) 2016/11/02 5.2 1080 1920 xxhdpi 360 640
AXON 7 mini 2016/10/21 5.2 1080 1920 xxhdpi 360 640
ZenFone 3 (ZE520KL) 2016/10/07 5.2 1080 1920 xxhdpi 360 640
honor 8 2016/09/28 5.2 1080 1920 xxhdpi 360 640
Disney Mobile on docomo DM-02H 2016/07/08 5.2 1080 1920 xxhdpi 360 640
Qua phone PX 2016/07/01 5.2 1080 1920 xxhdpi 360 640
P9 2016/06/17 5.2 1080 1920 xxhdpi 360 640
P9 lite 2016/06/17 5.2 1080 1920 xxhdpi 360 640
SAMURAI REI 2016/05/27 5.2 1080 1920 xxhdpi 360 640
Xperia X Performance 2016/06/24 5 1080 1920 xxhdpi 360 640
Xperia X Performance (SOV33) 2016/06/24 5 1080 1920 xxhdpi 360 640
Phab 2 Pro 2016/12/02 6.4 1440 2560 xxxhdpi 360 640 ppiとdpiが大きく違う
AQUOS SERIE mini (SHV33) 2016/01/23 4.7 1080 1920 xxhdpi 360 640
KIWAMI 2 2016/12/22 5.7 1440 2560 xxxhdpi 360 640
AXON 7 2016/10/21 5.5 1440 2560 xxxhdpi 360 640
Moto Z 2016/10/15 5.5 1440 2560 xxxhdpi 360 640
Galaxy S7 edge (SC-02H) 2016/05/19 5.5 1440 2560 xxxhdpi 360 640
isai Beat (LGV34) 2016/11/18 5.2 1440 2560 xxxhdpi 360 640
HTC 10 (HTV32) 2016/06/10 5.2 1440 2560 xxxhdpi 360 640

タブレット

SIMが刺さらないいわゆる格安タブレットは山ほど出ているのですが、効率よく端末情報を収集できなかったので分かる範囲でまとめています。

今年はアスペクト比4:3の端末が数機種でているのですが、これらのdp解像度は少し特殊なものになっていて注意が必要です。 これまでに発売された4:3タブレットが持つdp解像度は主に800x600と1024x768の2種類ですが、前者の幅800dpというのは他のタブレットと比べてかなり狭く、Android Studioが生成する新規プロジェクトが持っているw820dpの代替レイアウトリソースから漏れてしまいます。 この事故を避けるためか、800x600のdp解像度をもつタブレットはこれまでにほとんど発売されていません。

dp解像度820x615dpを持つ4:3タブレットであればこの問題は回避可能なので、ぜひ来年はDENSITY_400を持つ実解像度2048x1536、dp解像度820x615を持つタブレットが出てほしいですね。 (DENSITY_400はAPI 19から定義されているので、実はすでに存在しているかもしれませんが…)

(追記) コメントで MediaPad T2 7.0 Pro という機種の量子化密度が実際には400dpになっていてdp解像度は768x480という指摘を受けました。ありがとうございます。 DENSITY_400 が実際に使われているのは嬉しいんですが、横幅480dp…。 該当機種ではsw600dpもw820dpも満たさないので、一般的なアプリでは完全にスマートフォン扱いになってしまいますね。

dp解像度 量子化密度 端末数
768x400 DENSITY_400 1
1024x600 mdpi 1
960x600 tvdpi 3
960x600 xhdpi 3
1280x800 mdpi 1
1280x800 hdpi 2
1280x800 xhdpi 1
1024x768 xhdpi 2

タブレットの一覧

機種名 発売日 対角線帳 W(解像度) H(解像度) 量子化密度 W(dp) H(dp) 備考
ZenPad C 7.0 Z170C 2016/07/08 7 1024 600 mdpi 1024 600
MediaPad T2 7.0 Pro 2016/07/08 7 1920 1200 DENSITY_400 768 480
ZenPad 3 8.0 Z581KL 2016/09/16 7.9 2048 1536 xhdpi 1024 768
ZenPad 8.0 Z380KNL 2016/07/08 8 1280 800 tvdpi 960 600
Iconia One 8 2016/03/18 8 1280 800 tvdpi 960 600
Lenovo TAB3 2016/12/2 8 1280 800 tvdpi 960 600
Qua tab PX 2016/07/01 8 1920 1200 xhdpi 960 600
Predator 8 GT-810 2016/02/19 8 1920 1200 xhdpi 960 600
dtab Compact d-02H 2016/01/20 8 1920 1200 xhdpi 960 600
ZenPad 3S 10 Z500KL 2016/12/09 9.7 2048 1536 xhdpi 1024 768
MediaPad T2 10.0 Pro 2016/06/24 10 1920 1200 hdpi 1280 800
ZenPad 10 Z300CNL 2016/07/08 10.1 1280 800 mdpi 1280 800
Qua tab 02 2016/02/11 10.1 1920 1200 hdpi 1280 800
arrows Tab F-04H 2016/07/29 10.5 2560 1600 xhdpi 1280 800

関連リンク

集計に使ったスプレッドシート

docs.google.com

過去のdp解像度まとめ記事を貼っておきます

qiita.com

qiita.com

プロジェクタを買いました。

エプソンのホームプロジェクタ、EH-TW5350を買いました。

といってもプロジェクタを買うのは初めてではなく二代目。 これまでは2008年に購入したソニーVPL-AW15という機種を使っていました。 さすがに最新のプロジェクタと比べると微妙なスペックですが、これまで使ってきた愛着もあり、年末年始もこいつで映画を見まくるぞ!と思っていたのです。

昼間から遮光カーテンを締め切り、スクリーンを立ち上げ、いざ!と思ってプロジェクタを起動するとジジジ…とノイズ音がするだけで映像が出ません。 プロジェクタの宿命、ランプ切れでした。

ほとんどのプロジェクタは光源として水銀ランプを使用していて、およそ1500〜2000時間ほど利用すると寿命が来ます。 交換用ランプは機種ごとに価格が異なりますが、VPL-AW15の場合は安い互換ランプで15,000円、純正で20,000円くらいでしょうか。 大体2,3年に一度くらいの頻度でこのランプ交換が必要になる上、電気代もTVよりかかるのでプロジェクタのランニングコストは最悪ですね。

ランプ切れを起こしたVPL-AW15は僕が北九州で新卒社員をだったときに少ない給料から毎月少しずつ積み立ててやっと買ったもので、とても愛着がありました。 (当時スクリーンまでは買えなかったので)壁に60インチくらいで映画を投写できたときの喜び。 気休めに買ったペーパースクリーンで壁とは比べ物にならないくらい綺麗に映った時の喜び。 その後ちゃんとしたスクリーンを買ってさらに画質があがったときの喜び。 そういった感動がVPL-AW15のスペック以上に僕を楽しませてくれていたと思います。

2年半前にランプが切れた時は愛着(と当時のフルHDプロジェクタの価格)から買い換えることができませんでしたが、 さすがに2015年になってハーフHDでHDMI端子1個という制限は辛く、フルHDのプロジェクタの低価格化もあって今回は買い換えることなりました。 VPL-AW15への愛着が〜といいながら新機種チェックは怠らなかったので、買い替えの候補はエントリー機として評判の良かったEH-TW5200かその後継機、EH-TW5350の二択。 店舗で実機の映像を見たところ、EH-TW5350から搭載されたフレーム補間機能が気持ち悪いくらいヌルヌル動いて見えるのでEH-TW5350に即決してそのまま購入しました。 その後自宅で色々設定をいじってるうちにフレーム補間はOFFにしましたが、欲しいときにONにできるということが大事なのであったほうが良いです。

自宅でEH-TW5350を設置してまず驚いたのはその明るさです。 スペックでVPL-AW15の2倍の明るさがあることはわかっていましたが、実際に体験すると完全に別物でした。 VPL-AW15が部屋を暗室にして蛍光灯も消さなければほぼスクリーンが見えないのに対して、EH-TW5350は明るさを落とせば蛍光灯がついていてもスクリーンが見えます。 解像度もやっとフルHDになって、80インチのスクリーン本来の性能を活かせるのが嬉しいです。 このスペックのプロジェクタが10万ちょっとで手に入るようになったのは本当に、本当に素晴らしいです。 VPL-AW15も大体同じくらいの価格で買ったので、技術の進歩に驚きました。

もちろん良くなったところだけではなく、レンズシフトがないことからくる設置制限の辛さもあります。 特に打ち上げ角の違いは大きく、同じ場所に置くためには角度を付けて設置する必要がありました。 (プロジェクタに角度をつけて設置すると映像が台形になるため補正作業が必要になり画質が低下するのです) また、投写距離自体は短くなったもののVPL-AW15より調整幅が狭く、いままでよりスクリーンとプロジェクタを近づける必要もありました。

EH-TW5350は高画質の大画面を最短距離で投写することに全力を注いでいて、その思い切りの良さがコスパに繋がっているのだなと感じました。 使い勝手の良かったVPL-AW15とは少し方向性の違うプロジェクタですが、どうにかやっていけそうです。 財布には大ダメージでしたが年末年始の楽しみが増えました。

ランプ切れのVPL-AW15ですが、どうにも捨てることができないので実家に送りつけて保管してもらおうと思っています。 僕の父もCABINのスライドプロジェクタを後生大事に仕舞い込んでいるような人なので、きっとわかってくれるでしょう。 父がスライドプロジェクタで写真を壁に映して見せてくれた時のような感動を、僕もいつかビデオプロジェクタで自分の子供に伝えたいなと思っています。

今年発売されたAndroid端末のdp解像度をまとめた記事を書きました。

「最近は320dpの端末も少なくなっているので…」という話を自分でしながら本当に少なくなっているのかイマイチ確証がなかったので今年も調べて書きました。

ソフトバンクの開発者向けサイトに情報が全然なかったり一部の端末のdpiを調べようとググっても「dpiを変更したら快適になった!」という話しか出てこなかったり心が濁るようなこともあったけど320dp端末は元気です。対応は必須でした。

Android Nで画面分割が来たら300dp対応もしないといけないのかな…。 もうレイアウトをまるごとswXXXdpで分ける時代じゃなくてStaggeredGridLayoutManager#setSpanCount()とかで吸収していく時代なんだなーと思いました。

来年はシームレスに多端末対応していく術も学んでいきたい。

qiita.com

Androidソースコードレビューで指摘する事が多い項目まとめ2

去年Androidソースコードレビューで指摘する事が多い項目まとめという記事を書いた時はアプリ全体を一度に見るような機会が多かったため、内容も大きめのものばかり書いていましたが、最近はプルリクエスト単位でレビューする機会が増えたので細かい項目についてまとめてみようと思います。

ミリ秒で時間を指定する時に自前で計算している

1000L * 60L * 60L * 24Lのようなコード。

TimeUnitを使いましょう。

24時間の場合は以下のように書けます。

TimeUnit.DAYS.toMillis(1L)

ある文字列がhttp/httpsで始まるかチェック

URLUtil.isNetworkUrl()を使いましょう。

ただしequalsIgnoreCaseで判定してます。

ベースURLにパラメータを付与していってURLを生成したい

StringBuilder#append("&key=").append(value);のようなコードをいっぱい書いているようなところ。

Uri.Builderを使って以下のように書けます。

Uri.Builder#appendQueryParameter("key", value);

?&=を意識しなくてよくなり見やすくなります。

LayoutInflater#inflate()のrootがnull

LayoutInflater#inflate()rootを指定しないと適当なLayoutParamsを用意されて見た目が変わる可能性があるので指定しましょう。

nullにすると警告が出るのですぐわかると思います。

ListViewのHeader/FooterのLayoutInflater#inflate()

Header/FooterはItemのViewと同じようにHeaderViewListAdapter#getViewで返されてListViewに追加されるのでListViewをrootとして渡せばOKです。

Viewの間隔を調整するために透明なViewを使っている

できるだけmarginやpaddingで調整しましょう。

どうしてもViewを置いて調整したいときはSpaceを使いましょう。 このViewは描画処理で何もしないので背景色などが指定出来ない代わりに低負荷です。

ListViewの上下端の要素にだけmarginを入れている

大抵はListViewにpaddingTop/paddingBottomを設定してclipToPaddingをfalseにすると解決します。

文字列のベタ書き

文字列リソースにできるものは文字列リソースにしましょう。

ただし、以下に当てはまるものは文字列リソースに向かないのでベタ書きで良いと思っています。

  • ローカライズしてはいけない文字列
    • ログや成果計上に使う文字列など
  • レイアウトファイルのtools:textなどアプリ内では使われない文字列
  • private static final Stringな文字列

AndroidTVでDeployGateを使う

2015/12/18 14:23 追記:

DeployGateからダウンロードできるAPKLEANBACK_LAUNCHERに対応したので以下の内容は不要になりました!


AndroidTVでDeployGate使うためのアプリ作りました。

nein37/deploygate-android-tv · GitHub

AndroidTVでDeployGateアプリが利用できない理由は2つあります。

  1. Playストアのフィルタに引っかかっている
  2. DeployGateアプリがLEANBACK_LAUNCHERに対応するActivityを実装していない

今回作ったアプリではDeployGateが公開しているAPKダウンロードURLから直接APKをダウンロードしてインストールすることで1.を回避し、このアプリが起動された時にDeployGateのLAUNCHERActivityを呼ぶことで2.を回避しています。 その後は全てDeployGateアプリでの動作となりますが、試した限りではD-Padでもほぼ問題ありませんでした。

以上です。