• スポンサードリンク

SurfaceViewでCanvasをロックしているスレッドでの排他制御

Android SurfaceView 非同期

SurfaceViewの限界にチャレンジしてみようということで、つぶつぶをランダムに動かすアプリを作ってみました。その途中で予想外の挙動を示したので、色々検証してみた結果を書きます。

結論を先に書くと、

  1. SurfaceViewをロック(#lockCanvas)して描画しているスレッドA
  2. 別に処理しているスレッドB
  3. スレッドAとスレッドB内でそれぞれsynchronized(別オブジェクト)呼び出しで排他制御をしていて、スレッドAがキャンバスをロックしている最中に、スレッドBへのコンテキスト切り替えが頻繁に起こる

と、スレッドAもスレッドBも実行自体は正常に行われているように見えるにもかかわらず、画面が正しく更新されなくなります(logへ定期的に出力している更新頻度もddmsのプロファイリングで関数呼び出し回数を見ても正常なのに、画面表示がギクシャクした感じになる)。
解決策は・・・キャンバスをロックしている最中にコンテキストの切り替えが極力起こらないようにする(排他制御が必要になるような状況を作らない)っていう事なかれ主義的な方法だけみたいです。

まずは、排他制御なしの1スレッドで位置更新・描画を行う例から。
Activityは省略して、位置更新・描画用のSurfaceViewはこんな感じ。

Nexus7(2012)で、つぶつぶ2000個だと約20fps、3000個だと約14fpsぐらいでした。この時の処理時間の比率は、doDraw : update : unlockCanvasAndPost = 94 : 2.9 : 2.4ぐらいで、圧倒的に描画処理(特にdrawCircle)の処理が重たいことがわかります。unlockCanvasAndPostも意外と重い処理ですね。
ちなみに、処理の重いCanvas#drawCircleをCanvas#drawRectに代えると2000個で約40fps, 3000個でも30fps以上は出ました。
ただこのプログラム構成だと、描画処理の重さに位置更新処理が引きずられてしまう事がわかります。今回はユーザー操作が無いので、位置更新処理が遅れても大きな問題はないのですが、ゲームとかだと操作感にかなり響く可能性があります。

« »

  • スポンサードリンク