fresh digitable

めんどくさかったなってことを振り返ったり振り返らなかったりするための記録

たくさんのTextViewをアニメーションさせる

画面に20個ぐらいアニメーションするビューがあってなんとかして60fps出すためにいろいろやったことをメモしておく。

エミュレーターでやってたのが全部悪い(実環境で動かしたらわりとすんなり出た)というオチなので参考になるかはわからない。

前提

  • ValueAnimatorAnimatableDrawableKeyFrameで波紋のようなアニメーションを周期的に動かす。これはせいぜい数個
  • ValueAnimatorTextViewのtextColor(白と黒の間のグレースケール)を周期的に変えていく。これは20個ぐらいある

方針

Debug.startMethodTracing()で数フレーム分観測し、いらない処理を見つけてこれが実行されないように削っていく。この資料の38Pから数ページが参考になる。

AndroidVitals徹底活用 - Speaker Deck

最初のほうはGCも頻繁に起きていたのでAndroid Studioのメモリアナライザも使ったりした。

ポイント

ValueAnimatorでアニメーションをやるときは次のメソッドを実装することになると思うが、この中の処理を軽くしたり、中で新しいオブジェクトを作らないようにする。

  • TypeEvaluator.evaluate()
    • SDKのクラスを使うときは中で何をやっているか確認する。例えばArgbEvaluatorは最初にセットしたColor Intがevaluate()に渡される時にはIntegerのオブジェクトになってしまう。
  • AnimatorUpdateListener.onAnimationUpdate()
  • Drawable.draw()

やったこと

オブジェクト生成の抑制

  • listOf()とかarrayOf()とかで作っていたKeyFramevalueの配列をfloatArrayOf()にする。これによりTypeEvaluatorに値を渡すときのFloatオブジェクトの生成を抑制できる
  • FloatTypeEvaluatorにキャッシュ用の配列を渡して初期化する。これをしないとフレームごとにfloat[]が作られてしまう
  • 配列操作の処理でmapとかfilterを使っているところをfor文に置き換える。FloatArrayでmapとかを呼ぶとIteratorがフレームごとに作られてしまうしnext()がちょっと気になるくらい遅い。また、Javaコードにデコンパイルしてコードを見ると、mapに渡した関数がクラスになっていてこれのオブジェクトも毎フレーム生成されていた。

描画系

  • すべてのTextViewの背景にAnimatableDrawableをセットして、動かすときになったら表示させるようにしていたがこれをやめて、アニメーションさせるときにはじめてセットする。Drawableがセットされていたら更新しようとするのでこれをやめさせる。そこそこ効いた。
  • WRAP_CONTENTTextViewGravity.CENTERを指定していたのをはずす。テキストアライメントがNORMAL以外だとBolingLayout.draw()の中で毎回描画領域の再計算が行われてしまうのでこれをやめさせる。NORMALになっていると即座にCanvas.drawText()が実行される。そこそこ効いた。
  • ヒントテキストもリンクも必要ないので、hintTextColorlinkTextColorに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()を使うようにした。そこそこ効いた。