2012/08/02

[Android]OutOfMemoryError(メモリリーク)対策

AndroidアプリのOutOfMemoryError(メモリリーク)の対策としてやったことのまとめ。

Androidアプリを作っている人ならOutOfMemoryErrorに悩まされた事のある人も多いと思います。

自分も悩まされて色々試行錯誤したので、そのへんをまとめておきます。

まず、OutOfMemoryErrorに関してですが、エラーレポートには大抵、画像の描画、ロード時に発生したと書かれていることが多いですが、画像が直接の原因じゃないことも結構多いです。(もちろん、単純に画像が大きすぎる、使用する画像の数が多すぎることもありますが、その場合はサイズの縮小などを行なってください。)

自分も最初は画像が原因だと思い込んでいたので、BitmapのConfigを変更してアルファ情報を抜いたりとかして使用するメモリ容量を減らして、「対策できた!」とか思ってました。

でも、あんまりエラーレポート減りませんでした。

で、色々試行錯誤していくうちに画像が原因じゃないなとわかりました。

アプリを起動すると、様々なオブジェクトが生成されます。色々動かしたあと、アプリを終了します。アプリを終了したあとに、端末のメモリが少なくなってきたらGCが走って使っていないオブジェクトを削除してくれるのですが、アプリを終了した時に色々オブジェクトの参照が残っていたりすると、GCで開放されません。

そして、次回にアプリを起動した時に、残っているオブジェクトが再利用されるかというと、全く再利用されずに新しいオブジェクトが次々に生成されます。

これの繰り返しで、ごみオブジェクトがどんどん溜まっていき、それと比例して、アプリが使用するメモリ領域を圧迫していきます。

そして、何回目かの起動時にメモリを大きく使用する処理(画像のロードとか)がきっかけでOutOfMemoryErrorが発生します。

なので開発時のデバッグとかでは、ほとんど見つからなく(実機にインストールする段階でアプリが使用するメモリ領域が初期化されるので)、リリース後にエラーレポートが送られてくることになります。やっかいですね。

で、今回はごみオブジェクトをなるべく残さないようにするためにごみオブジェクトの調べ方と参照の消し方とかを書いておきます。

まず、調べ方についてですが、Memory Analyzerを使います。

インストールについては次のページを参照してください
Eclipseで使うMemory Analyzerのインストール

まず、調べたいアプリを起動して、色々動かしてから戻るボタンなどでアプリを終了させます。

そしてDDMSを開いて、Memory Analyzerを起動します。

「Dump HPROF file」をクリックすると初回起動時はダイアログが表示されます。とりあえず、一番上の「Leak Suspects Report」を押します。

すると、次のような画面が表示されるので、図の部分をクリックして「Histogram」を表示します。



Histogramでは、アプリが保持しているオブジェクトの一覧が次の図のような感じで表示されます。


<Regex>のところにアプリのパッケージ名を入れると、アプリのオブジェクトがどのくらい開放されずに残っているかが表示されます。


アプリが終了したのに、オブジェクトの参照が残っているとこんなかんじになります。


起動と終了を繰り返すとどんどん溜まっていきます。


このようにどんどん、ごみオブジェクトを保持し続けていき、メモリを圧迫していきます。

アプリの終了時に参照が残っていなければ、何回起動と終了を繰りかえしても、ごみオブジェクトは残りません。

なので、アプリが終了した時に参照が残らないようにします。

メモリリークの原因として有名なのはActivityの参照が残り続けることですが、Activity単体が残るだけであれば、そこまで大きな問題ではありません。
問題はActivityが参照しているクラスのインスタンスが残り続けることです。複雑な作りのアプリほど残りやすいです。

自分が行ったのはActivity#onDestroy()やFragment#onDestroyView()で次のように明示的に参照を切りました。
自分が使ってたのが、Fragment, ViewPager, ListViewぐらいなので、それらの参照の切り方だけ書いています。

/**
 * onDestroy()やonDestroyView()で
 */

//Fragmentのとき
fragment = null;

//ViewPagerのとき
viewPager.setAdapter(null);
adapter = null;

//ListViewのとき
listView.setAdapter(null);
adapter = null;

//各種リスナー
button.setOnClickListener(null);

特に、カスタムしたView,Adapter,Listenerなどは明示的に参照を切らないと残り続けるようです。

自分の場合、このように明示的に参照を切ってあげる事でほとんどオブジェクトが残らなくなりました。

検索かけてもあまりこの辺の具体的な情報はあまり見つからなかったので(見つけられなかっただけかもしれない...)、OutOfMemoryError(メモリリーク)で困っている人の手助けになれば幸いです。

ちなみに今回対策したアプリはこちらです。「顔文字BANK」
良ければ、DLして使ってください。