Upgrade to Pro — share decks privately, control downloads, hide ads and more …

マンガアプリのメモリ改善と解析方法

 マンガアプリのメモリ改善と解析方法

「DroidKaigi2022」の登壇資料です。
https://2022.droidkaigi.jp/

LINE Digital Frontier - TECH

October 12, 2022
Tweet

More Decks by LINE Digital Frontier - TECH

Other Decks in Technology

Transcript

  1. USER BASE ※2022年9⽉時点 MAU 作品数 取引額 作家数 WEBTOON Entertainment 最新実績

    WEBTOON Entertainment が運営する代表的なサービスは 「LINEマンガ (⽇本)」「NAVER WEBTOON (韓国)」「WEBTOON (北南⽶・欧州)」「LINE WEBTOON (東南アジア)」など 8900 万 1000 億円 600 万⼈ 10 億
  2. LINEマンガで Out of memoryが全体crashの⽐率40% -> 5% OOMが40% 他 Crash 他

    Crash 他 Crash 他 Crash 他 Crash 他 Crash 他 Crash 他 Crash 他 Crash 他 Crash 他 Crash 他 Crash 他 Crash OOM Crash OOM crash割合<5% 他 Crash 他 Crash 他 Crash 他 Crash 他 Crash 他 Crash 他 Crash 他 Crash 他 Crash 他 Crash 他 Crash OOM Crash こちらはあくまでイメージ図であります。 メモリ改善
  3. 解析⽅法 & tools • ログ • Java JVM基礎知識 • Android

    studio メモリprofiler • Android メモリ監視メソッド • Bitmap画像のメモリ計算⽅法
  4. • GC Object参照は⾊々絡んでいる。 • Depth: The shortest number of hops

    from any GC root to the selected instance. GC treeとdepth
  5. Two important JVM tuning points in the java world •

    Heap size • -Xms, -Xmx • android:largeHeap="true” • Garbage Collection(GC) • こちらはAndroid OSバージョンとともに進化 https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html - jvms-2.5 https://www.oracle.com/webfolder/technetwork/tutorials/obe/java/gc01/index.html Java memory tuning
  6. • Android 5,6,7で起こりやすい • RAM 2GB, 3GBで発⽣; 4GB以上は少ない • Android

    8以上の端末ではOOMが少ない • ⽐較的にRAMが>=3GBのものが多いのでHeapサイズも⼤きい • Android 8以上ではNative heapにbitmapを保存 • Android 8ではConcurrent compacting garbage collectorなど メモリ回収処理が最適化されている LINEマンガ OOMの発⽣のデバイス統計
  7. • どんなタイミングでOOM するかを可視化したい • totalFreeMemory が0だ とOOMが発⽣する • 要はVMのmax Memoryを

    超えるとメモリが⾜りな いのでOOM https://stackoverflow.com/a/18375641 MemoryUtil
  8. totalFreeMemoryで 残りのメモリを数値化したい private const val MEGA_BYTE = 1048576 // 1024

    * 1024 @JvmStatic fun printCurrentApplicationVMMemory() { val runtimeVersion = System.getProperty("java.vm.version") val runtime = Runtime.getRuntime() val maxMemory = runtime.maxMemory() / MEGA_BYTE val freeMemory = runtime.freeMemory() / MEGA_BYTE val totalMemory = runtime.totalMemory() / MEGA_BYTE val totalFreeMemory = maxMemory - totalMemory + freeMemory Timber.d( "#Memory CurrentApplicationVMMemory(MB) %s, %s, %s, %s, %s", " totalFreeMemory: $totalFreeMemory", " runtimeVersion: $runtimeVersion", " maxMemory: $maxMemory", " freeMemory: $freeMemory", " totalMemory: $totalMemory", ) } MemoryUtil
  9. 33 🤔2048 x 1536のjpeg画像のファイルサイズが1MBぐらいなのにAndroid bitmapサイズは12MB になる。なぜ︖ • Bimap画像サイズ(memory) > Jpeg/PNG/webP(圧縮アルゴリズム)ファイルサイズ(dis

    k) Bitmap画像: ARGB_8888⽅式 (LINEマンガでは画像のクオリティを維持するためRGB_565を採 ⽤しない) で保存する場合のBitmapメモリサイズ(標準保存⽅式) Each pixel is stored on 4 bytes Bitmap image memory size = height x width x 4byte 例: 2048 x 1536 => 2048 x 1536 x 4 = 12582912bytes ≒ 12MB 1024 x 1600 => 1024 x 1600 x 4 = 65536000bytes ≒ 6.8MB 512 x 384 => 512 x 384 x 4 = 786432bytes ≒ 0.75MB • OutOfMemory: Faild to allocate 12582912bytes…の場合は驚かないでください。12MB/4=3MBより ⼩さいファイルサイズ(普段は1MBぐらい)の画像かもしれません。 • 質問: 最新のWebPフォーマットの画像だったらBitmapメモリサイズが⼩さくなる︖ Bitmap画像のメモリサイズ計算⽅法
  10. • Picasso .resize(600, 200) • Glideのは基本的にImageViewのサイズを⾃動判別できるが必要に応じ て.override(targetWidth, targetHeight)、 CustomTarget<Drawable>(targetImageWidth, targetImageHeight)でサ

    イズを設定 画像ライブラリー使う場合: オリジナル画像をロードしない ImageView ImageView サーバーから必要以上に⼤きい画像が送られる場合がありませんか? ImageView サイズ取得
  11. 画像分割ロード 問題 • 縦⻑い画像のままだと⼆つの問題が発⽣する • GL_MAX_TEXTURE_SIZEとかCanvas: trying to draw too

    large で古い端末では描画できない可能性がある • RecyclerViewで画⾯に表⽰されていない部分がrecyclerできない 解決⽅法 • meta dataを参考に事前に分割する適切なサイズを計算する • Android 8.0以下: BitmapRegionDecoderで分割読み込み 、 読み込みスピードを犠牲 • Android 8.0以上: オリジナルBitmap読み込んでからsub bitmapに分割して表⽰、 読み込みスピード優先
  12. • fast scrollで画⾯上に表⽰する必要のない 画像はロードのcallbackが来たら処理しない • NO_POSITIONのholderの画像はもうロードする必要がない。 public void onBitmapLoaded(Bitmap bitmap,

    … int requestPosition) { …. int adapterPosition = holder. getAbsoluteAdapterPosition(); // check whether it is valid position. // - avoid calling unnecessary logic such as createBitmap. // - this also fix when fast scroll wrong position bitmap set. if (adapterPosition == RecyclerView.NO_POSITION || requestPosition != adapterPosition) { return; } …. Create bitmap … Viewer: RecyclerViewのfast scroll対応 NO_POSITION … … NO_POSITION
  13. private var binding: xxxxBinding? = null …. override fun onDestroyView()

    { super.onDestroyView() binding = null } 参照: Release Binding/Adapter
  14. • Adapter release when onDestroy • or not use field

    adapter, just cast for use adapter = null or recyclerView.setAdapter(null); (adapter as xxxxAdapter).get….. 参照: Release Adapter
  15. class xxxxFragment { private val myView: View? = null //

    Bad practice private val myDialogFragment MyDialogFragment? = null // Bad practice } • Filedに保持しない、必要な時はbinding/findFragmentByTag/byId で参照できる 参照: view/dialogを保持しない
  16. • Handler(Looper.getMainLooper()).postDelayed(…..). // Bad practice • 実際画像バナー⾃動横scrollでこれを使って無限ループで⾃⼰参照しているところがあ りました。バナー画像は⼤きいですからメモリリークされてメモリにずっと残っていま した。 •

    rootView.postDelayed(…..) // Good practice • Runnableのstop処理を⾃前で書いてもいいですがこの場合viewのpost/postDelayed が適切 • Viewに紐ついてPostDelayedするとattatch状態もちゃんとチェックしてくれます。 View.java 参照: 遅延処理はViewのpostDelayedを使う
  17. • Google I/O 2022でもLarge screen(tablet/foldable)が話題 • Large screen対応アプリはGoogle playの該当デバイス種類検索ラン キング上位に現れるなど優遇される

    • Play Media Experience Programに申請して対応すると⼿数料 が30% -> 15%になります。 • ConfigutationChange/rotationなどで発⽣する可能性が⾼いメモリ リークなど問題ないか解析が必要になっています • LINEマンガは既にLarge Screen対応済み おまけ: Google large screen対応