Flash Player/Adobe AIRでのメモリリーク対策まとめ (Flash Builder/Scout編)

38

Adobe AIRアドベントカレンダーの一環として、「Flash Player/Adobe AIRのメモリリーク対策」を記事にまとめました。メモリリーク対策として弊社が取り組んでいる手法を紹介します。

この記事では次のソフトウェアを利用します。

  • Flash Builder 4.7
  • Adobe Scout

メモリリーク対策の重要性

Flashコンテンツ/AIRアプリを長時間起動してるとメモリの使用量が増大し動作が不安定になることがあります。メモリ使用量の増大によってフレームレートの維持が難しくなったり、Flashコンテンツ/AIRアプリのクラッシュへとつながります。

Flashコンテンツの制作でメモリリーク対策を必要としなかった開発者は多いと思います。2000年代のフルFlashサイト全盛期に需要のあった広告系コンテンツ。それらは訴求力重視・公開期間が短めということもあってメモリリーク対策が求められる機会が少なかったのです。

2010年代になるとFlashコンテンツの用途が広がり、スマホアプリやサイネージ、ゲームコンテンツなど長時間起動を前提としたコンテンツの需要が拡大しました。そのことによってメモリリーク対策が求められる場面が増えたといえるでしょう。

※Flashは世間ではメモリの使用量が大きいと言われますが、決してHTML5に比べてメモリの使用量が大きいわけではありません。設計次第でHTML5も同様にメモリの使用量が大きくなります(むしろHTML5のほうがメモリリーク対策のツールが揃っておらず対策が困難なケースがあります)。

方針

省メモリ対策のActionScriptの設計として「使い終わったら消す」ことを基本方針にしています。具体的には画面遷移したときに以前の画面が不要になります。その時に以前の画面で使われていたオブジェクトを取り除きます。

参照破棄のメソッドdispose()メソッドを用意する

ActionScriptではメンバー変数の参照が外れたり、DisplayListから外れるとガベージコレクションの対象となります。この「参照が外れた状態」を作るために一番確実な方法は明示的に破棄用のメソッドを実装することです。

弊社では、一般的な慣習にしたがってdispose()という名前のメソッドをカスタムクラスにはほぼ全部に用意し、不要になったタイミングで明示的に呼び出すようにしています。dispose()というメソッド名はビルトインのBitmapDataクラスにも存在しますし、ProgressionやStarlingなどのデファクトスタンダードなActionScriptのフレームワークに標準搭載されています。

dispose()メソッドで実装するポイント

dispose()メソッドでは主に次の処理を記述します。メンバー変数にnullを代入することで参照を断ち切ることができます。

  • メンバー変数にnullを代入する
  • メンバー変数で破棄可能なものは破棄する(子にdispose()を実行する)
  • 登録されたイベントを解除する

これを簡単に記述するためのツールを用意しているので([AS3 Coding Tools] Property Disposer)、よければご利用ください。

以下は実際のプロジェクトでのdispose()メソッドのコード例です。

画面やクラスはツリー上に構築しているのですが、頂点のインスタンスのdispose()メソッドを叩けば、子→孫→曾孫と階層構造的にdispose()メソッドが呼ばれます。こうすれば画面遷移のときに、1つのdispose()メソッドを呼び出すだけで、関連する画面要素の隅々まで参照を破棄できます。

メモリリークの確認方法

Flash Player/Adobe AIRのメモリリークの調査には二種類の方法があります。

それぞれ解析するのに長けた部分が異なっているため、両方を使って解析するのがオススメです。「Adobe Flash Builder 4.7プロファイラーー」でメモリリークを解析し、「Adobe Scout CC」で処理負荷を解析するという使い方がよいでしょう。以下で具体的なそれぞれの操作方法を紹介します。

Adobe Flash Builder 4.7プロファイラーでのメモリリーク存在有無の確認手順

Flash Builderのプロファイラーでは生成したインスタンスの残っている数を把握できます。メモリの使用量は基本的にインスタンスの残っている数と比例するので、インスタンスの残っている数を減らすことはメモリリーク対策に直結します。

  1. Flash Buidlerを起動し、対象のプロジェクトを開いた状態にする
  2. メニューバーから[ウインドウ]→[プロファイル]を実行
  3. [接続が確立しました]ウインドウにて、次の項目をチェックする(それ以外の項目は任意)
    • [メモリのプロファイリングを有効にする]
    • [ライブメモリデータを監視]
  4. [再開]ボタンをクリック
  5. Flashコンテンツ/AIRアプリを再生する(チェックしたい画面の行き来を繰り返す)
  6. [ライブオブジェクト]パネル内の[パッケージ]列を選択
    ※該当するクラスを発見しやすくするため
  7. 該当するクラスの[累計インスタンス]列の数値と[インスタンス]列の数値を比較
  8. [インスタンス]列の数値が蓄積されていないかどうかを確認し、画面遷移を繰り返すなどで蓄積されているようならメモリリークと認識する
  9. ActionScriptコードのdispose処理が実行されているか等を確認し、されていないようなら修正する
  10. 2〜9の手順を繰り返しメモリリークの対策を行う

参考記事

※上図のように0が縦一列にならぶ状態を作り出せるとメモリリーク対策は成功といえるでしょう。地味な作業ですが、Flash Builderのプロファイラーで残っているインスタンスの数を減らしていくのがメモリリーク対策に効果的です。

Adobe Scoutでのメモリリーク存在有無の確認

Adobe Scoutはメモリリーク対策よりも処理負荷調査に使えるのですが、実はFlashのGPUメモリの使用量をチェックできる数少ないツールだったりもします。Stage3Dを利用したコンテンツを開発している場合は、Scoutを用いてGPUメモリのリークが発生していないか検証するとよいでしょう。今回はScoutをGPUメモリの対策ツールとしての側面を紹介します。

  1. Flash Buidlerを起動し、対象のプロジェクトを開いた状態にする
  2. プロジェクトの[プロパティ]を開き[ActionScriptコンパイラー]を選択
  3. [詳細なTelemetryを有効にする]オプションをチェックし、[OK]ボタンをクリック
  4. Adobe Scoutを起動
  5. Scoutの左側の[メモリ割り当てのトラッキング]を選択する(それ以外は任意)
  6. Scoutの[フレームタイムライン]パネルにて[メモリ]を選択
  7. Flash Builderでプロジェクトを実行し、Flashコンテンツ/AIRアプリを再生する
  8. 画面遷移を繰り返すと、[メモリ]のグラフが増減するが、増加する場合はメモリリークと認識する
  9. ActionScriptコードのdispose処理が実行されているか等を確認し、されていないようなら修正する
  10. 2〜9の手順を繰り返しメモリリークの対策を行う

参考記事

▲ScoutでGPUメモリのグラフが意図したタイミングで下がれば効果があったといえます。GPUメモリの前後グラフを比較してメモリリークしていないか調査するとよいでしょう。

メモリリーク対策で効果的なもの

Flash Builderのプロファイラーを解析すればわかるのですが、メモリ使用量の大半はBitmapDataクラスやMovieClipクラス、Textureクラスなどの表示に関わるオブジェクトです。表示に関するオブジェクトの参照が残っていると、メモリ使用量が増えやすいので、それらの参照を断ち切ることを優先するといいでしょう。

他にもLoaderクラスやURLLoaderクラスで読み込んだ外部SWFXMLByteArrayなどもメモリにたまりやすいのでこまめに処理を確認するといいでしょう。

イベントの解除removeEventListerner()は必須

イベントの登録解除漏れはほぼ確実にメモリリークが発生します。addEventListener()メソッドで登録したものはremoveEventListener()メソッドを使って解除しましょう。ちなみに弱参照を使ってもいいかもしれませんが、明示的に解放するほうが確実であると考えているので、私は弱参照を使っていません。

静的変数はとくに注意が必要

静的変数やDictionaryクラスはとくに消し忘れてしまいメモリリークすることが多いので、注意が必要です。メンバー変数より静的変数は残っているかどうかを追いにくいので、メモリリークの調査をしていると苦労します。

配列内の要素もdispose()可能ならdispose()する

配列内の要素は、全要素に対してdispose()を実行し(必要であればイベント解除もし)、最後に配列の変数にnullを代入します。

for (var i = 0; i < arr.length; i++) {
  arr[i].dispose();
}
arr = null;

まとめ

最近はスマホアプリでAdobe AIRの魅力が再認識され採用事例は増えているので、メモリリーク対策はますます重要になってくるはずです。今回紹介した手法は入門ですが、実践的な手法をこの当サイトで今後紹介していければと思います。

今年も残り僅かとなりましたが、Adobe AIRアドベントカレンダーのほうもよろしくお願いします。

池田 泰延

ICS代表。筑波大学 非常勤講師。ICS MEDIA編集長。個人実験サイト「ClockMaker Labs」のようなビジュアルプログラミングとUIデザインが得意分野です。

この担当の記事一覧