fresh digitable

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

lottie-androidの大雑把な処理の流れ

処理時間の性能改善をするとき、いつもはまずどこに伸びしろがありそうかあたりを付けるためにメソッドトレースのFlame Chartを見ている。LottieSampleの場合は大体setProgress()drawかそれ以外(プログレスバーとかログ出力)に分かれていて*1、これを見る限りだとsetProgress()の方が若干多いように見えたので、手始めにsetProgress()の処理時間を短縮する手立てを考えることにした。その過程で処理の流れがふんわりとわかるようになってきたので、今の段階で理解している内容を一旦まとめてみる。全体的な処理の流れは次の通り。

  • まずはJSONをパースするなどしてLottieComposeをつくる
  • LottieComposeLottieAnimationViewLottieDrawableに渡すと、LayerKeyframe, KeyframeAnimationなどのアニメーションに関係するオブジェクトが作られる
  • アニメーションが開始されたら基本的には以下の繰り返し
    • setProgress
    • draw

setProgress

レイヤーやグループといった単位に対するアニメーション可能な属性値、そしてそれらの子要素であるアニメーション部品の数だけsetProgressがよばれる。レイヤーのアニメーション可能な属性は今のところ9個ある。全部nullable。

  • 透過度
  • 開始の透過度
  • 終了の透過度
  • アンカーポイント
  • 位置
  • 倍率
  • 回転
  • 歪み
  • 歪み角度

また、マスクやマット(つや消し)の効果があるときにもsetProgressが呼ばれる。一つのsetProgressはそこまで重くないのだけど、数がとにかく多い。

このフェーズの中でDrawable.invalidateSelf()がよばれると、drawフェーズに進むことになる。逆に言うと、誰もinvalidateしないということになればdrawフェーズには行かないのでここのチェックを正しく行うことは性能に大きく寄与する。また、描画に影響しないパーツや属性値はdrawフェーズでほぼ何もせず次に処理を回すことがあるので、全体的な見かけの消費時間がdrawフェーズより多くなることもある。仕方のない部分もあるが部分的にでもカットできればよさそう。

draw

それぞれのパーツがレイヤーごとに順番に描画される。ver. 3.1.0でオフスクリーンレンダリング(Canvas.saveLayer())に対応したので、透過度がAfter Effectsの見た目通りに効くようになった模様。性能面に影響があるので、オフスクリーンレンダリングはデフォルトではoff(従来の挙動)になっている。

このフェーズの処理を大雑把に言うと、移動や変形させるために値を集めて行列を作ったり、色、透過度の値を集めてきてまとめてパスに渡すという感じ。ここの性能はサンプルアプリでもいろんな形で見ることができる。そういう事情もあってか、チューニングがかなり進んでいる印象。行列がいつも同じ値なら予め計算しておいたものを使い続けるとか、パスに掛けておくみたいな事ができると短縮できるかもしれない。

実際にはViewなりDrawableが表示される時にはonDraw()が呼ばれるので、setProgress()より先にdrawが呼ばれることになる。ただ、アニメーションの最中はsetProgress()で与えられた進捗の値を使って図形や文字の位置や大きさ、色を計算してdrawCanvasに書くというのが基本的な流れになっているものと認識している。

*1:そういう風に分かれて見えるように修正した話はまた別の機会に