4 回答
メモリリークを防ぐには、まずオブジェクトのライフサイクル管理を徹底するのが基本だね。Bitmapを扱う際はサンプリングやリサイクルを活用して、必要以上にメモリを圧迫しないようにする。最近のプロジェクトで、Glideを使った画像読み込みの最適化を試みた時、キャッシュサイズの調整だけでパフォーマンスが30%向上したことがある。
ActivityやFragmentのコンテキストを長期間保持すると、思わぬメモリリークを引き起こすから要注意。WeakReferenceを使うか、ApplicationContextに切り替える選択肢もある。プロファイリングツールでヒープダンプを定期的に確認すると、どこでメモリが無駄に使われているかが見えてくるよ。
アプリがクラッシュする前にメモリ不足の警告が出たら、即座に軽量化処理を走らせる手がある。onTrimMemoryをオーバーライドして、バックグラウンドのキャッシュをクリアしたり、非表示のViewを解放したりする仕組みを作っておくと効果的。『Fate/Grand Order』のアプリが重くなった時にキャッシュ削除機能を実装した話を思い出す。
Kotlinならば、applyやalsoスコープ関数を使って、オブジェクト使用後に確実にリソースを解放する書き方も良い。ライブデータのobserve処理でcontextを渡さず、ライフサイクルオーナーを正しく設定するだけでも違いがでる。
メモリマッピングされたファイルを使う方法はどうだろう?巨大なデータを扱う際、FileChannelとMappedByteBufferを組み合わせれば、物理メモリを直接圧迫せずに済む。『モンスターハンター』の地形データ読み込みのように、必要な部分だけを逐次ロードする仕組みに近いね。
RecyclerViewのViewHolderパターンは、メモリ効率化の典型例だ。アイテム数が1000を超えるリストでも、画面上に表示される分だけインスタンスを保持するから無駄がない。カスタムビューでonDraw連発するとメモリ食いつぶすから、Canvas操作は最小限に抑えるテクニックも大切。
ネイティブコード(JNI)側でのメモリ管理も見過ごせない。C++でnewした領域をJava側で解放し忘れると、デバッグが難しいリークが発生する。Android NDKのプロジェクトで、スマートポインタを使うように修正したら安定性が格段に向上した経験がある。
ProGuardの設定で未使用コードを削除するだけでもAPKサイズが減り、結果的にメモリ使用量も改善される。マルチモジュール構成にして、必要時だけ機能をロードするdynamic featureも考慮の価値ありだ。