画面に20個ぐらいアニメーションするビューがあってなんとかして60fps出すためにいろいろやったことをメモしておく。
エミュレーターでやってたのが全部悪い(実環境で動かしたらわりとすんなり出た)というオチなので参考になるかはわからない。
前提
ValueAnimator
でAnimatable
なDrawable
をKeyFrame
で波紋のようなアニメーションを周期的に動かす。これはせいぜい数個ValueAnimator
でTextView
のtextColor(白と黒の間のグレースケール)を周期的に変えていく。これは20個ぐらいある
方針
Debug.startMethodTracing()
で数フレーム分観測し、いらない処理を見つけてこれが実行されないように削っていく。この資料の38Pから数ページが参考になる。
AndroidVitals徹底活用 - Speaker Deck
最初のほうはGCも頻繁に起きていたのでAndroid Studioのメモリアナライザも使ったりした。
ポイント
ValueAnimator
でアニメーションをやるときは次のメソッドを実装することになると思うが、この中の処理を軽くしたり、中で新しいオブジェクトを作らないようにする。
TypeEvaluator.evaluate()
- SDKのクラスを使うときは中で何をやっているか確認する。例えば
ArgbEvaluator
は最初にセットしたColor Intがevaluate()
に渡される時にはInteger
のオブジェクトになってしまう。
- SDKのクラスを使うときは中で何をやっているか確認する。例えば
AnimatorUpdateListener.onAnimationUpdate()
Drawable.draw()
やったこと
オブジェクト生成の抑制
listOf()
とかarrayOf()
とかで作っていたKeyFrame
のvalueの配列をfloatArrayOf()
にする。これによりTypeEvaluator
に値を渡すときのFloat
オブジェクトの生成を抑制できるFloatTypeEvaluator
にキャッシュ用の配列を渡して初期化する。これをしないとフレームごとにfloat[]
が作られてしまう- 配列操作の処理でmapとかfilterを使っているところをfor文に置き換える。
FloatArray
でmapとかを呼ぶとIterator
がフレームごとに作られてしまうしnext()
がちょっと気になるくらい遅い。また、Javaコードにデコンパイルしてコードを見ると、mapに渡した関数がクラスになっていてこれのオブジェクトも毎フレーム生成されていた。
描画系
- すべての
TextView
の背景にAnimatable
なDrawable
をセットして、動かすときになったら表示させるようにしていたがこれをやめて、アニメーションさせるときにはじめてセットする。Drawable
がセットされていたら更新しようとするのでこれをやめさせる。そこそこ効いた。 WRAP_CONTENT
なTextView
にGravity.CENTER
を指定していたのをはずす。テキストアライメントがNORMAL以外だとBolingLayout.draw()
の中で毎回描画領域の再計算が行われてしまうのでこれをやめさせる。NORMALになっていると即座にCanvas.drawText()
が実行される。そこそこ効いた。- ヒントテキストもリンクも必要ないので、
hintTextColor
とlinkTextColor
にnullをセットしておく。これをやっておくとTextView.setTextColor()
をやった時にhintTextColor
とかlinkTextColor
も更新しようとするのを防げる。超細かい。 TextView.setTextColor(int)
ではなくTextView.setTextColor(ColorStateList)
のほうを使う。ColorIntを渡すほうは内部的にColorStateList.valueOf(int)
を呼んでColorStateList
に変換しているのだが、内部的にsynchronized
で囲われているせいか時々めっちゃ遅いことがある。グレースケールなら長さ256の配列に収まるのでアプリ起動時に作ってこれを使うようにする。超細かい。- 同じタイミングで動く複数の
Drawable
を別々のValueAnimator
で制御していたが、これをやめて一つのValueAnimator
のなかで全部のDrawable
のinvalidateをやることにした。結構効いた。 Drawable.invalidateSelf()
をやめてView.postInvalidateInAnimation()
を使うようにした。そこそこ効いた。