読者です 読者をやめる 読者になる 読者になる

CustomViewを作るときのテンプレートをAndroid初心者が考えた

CustomViewを作り機会が多い今日このごろ。
そこで考えるのがAPI制限だったり、パフォーマンスの事だったりなんです。
API制限はどうしようもないからパスするとして、パフォーマンスは出来るだけ対策、良くしていきたいと考えています。

多発するinvalidate();

ValueAnimatorなど使っているとAnimatorUpdateListenerのonAnimationUpdateでinvalidate();をそのままサクッと書くパターンがあるかと思います。
こんな感じに。

ValueAnimator animator = ValueAnimator.ofFloat(0.f, 1.f);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    @Override
    public void onAnimationUpdate(ValueAnimator animation) {
        invalidate();
    }
});
animator.setDuration(10000);
animator.start();

この時、invalidateが呼ばれる間隔は大体17msで58FPSぐらいになっています。
60FPSぐらい出てる場合は滑らかに描画しているとされていますが、ViewによってはそこまでのFPSを必要としていない時が多いと感じます。

また、複雑なAnimationをする時、ValueAnimatorは一つとは限らず複数使います。
その際すべてのValueAnimatorでinvalidateを17ms間隔で呼ぶと無駄にonDrawが呼ばれ描画されない計算が走ることになります。
イケないことです。

画面を閉じても呼ばれるinvalidate();

ViewのライフサイクルであるonDetachedFromWindow()が呼ばれてもValueAnimatorにaddUpdateListenerしたAnimatorUpdateListenerは生き続けています。
onDetachedFromWindow()が呼ばれた時にはonDrawは呼ばれないのですが、AnimatorUpdateListenerのonAnimationUpdateでしている処理はDurationの間、呼ばれ続けます。
RepeatするようなAnimatorでは常に処理が走り続けてしまいます。
無駄な計算はパフォーマンスを下げる要因になるのでどうにかしたほうがいいです。

僕が考えた最強のTemplateCustomView

gist.github.com

基本的にはOverrideしたinvalidateの処理をゴニョゴニョしました。
今回は暫定で30ms間隔以上での更新しかsuper.invalidate();を呼ばないようにしています。
個人的な主観ですが、 30FPSくらいアレば違和感を感じないと思います。

また、一応onDetachedFromWindowが呼ばれた時には即効でreturnさせるようにしてほんの少しだけパフォーマンスに貢献したり。

onDetachedFromWindow()内の処理ではAnimatorのCancel処理を必須で入れるようにしています。
複雑な描画になればなるほど、onAnimationUpdateでする処理が重くなることもあるので必須と言えます。

と、まぁいろいろ考えて書いてはみたんですが、このViewが一番適してるのって、Handlerで回したinvalidateとどこかのAnimatorから呼ばれるinvalidate、その他なんか適当に呼ばれるinvalidateを制御するときに役に立ちます。
一つだけのAnimatorのinvalidateだと、30ms分の描画しないところが出てきたりするので、ViewCompat.postInvalidateOnAnimation(View)を使うのが良いかと。
ただ、postInvalidateOnAnimation(View)のためだけにsupport library導入するのが嫌だという人はもう少しいろいろ考えたほうがいいかなって思います。

ひとつのViewのパフォーマンスにこだわると

一つのViewが表示されている時間は一つの画面が表示されている時間より圧倒的に少ないです。
そのため、導入するときにパフォーマンスを見ることが少ないと思います。
ただ、もしかしたら使ってもらえるかもしれない、という状況に備えて今自分の持っている知識で最高のViewを作って公開することは非常に有意義だと思います。

最後に

結局 onDraw内でインスタンスを作成しないとかさらに当たり前のことをやれば確実に一定以上のパフォーマンスを担保出来といると思います。
そこでもう一歩踏み込んだメンテナンスをすることでイケメンなViewへと昇華できますね!!!!

おわり!!!!!!!!!