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

«Сбор и анализ неординарных данных Android-приложения», Дмитрий Васильев, FunCorp

5d08ba0cd07942f2ddbf82e5b21ba5e7?s=47 FunCorp
August 03, 2019

«Сбор и анализ неординарных данных Android-приложения», Дмитрий Васильев, FunCorp

5d08ba0cd07942f2ddbf82e5b21ba5e7?s=128

FunCorp

August 03, 2019
Tweet

Transcript

  1. 1 Сбор и анализ неординарных данных Android-приложения Дмитрий Васильев, FunCorp

  2. Немного о нас 2 • FunCorp основана в 2004 году

    в Пензе • начали с смс-сервисов и развлекательных приложений на кнопочных телефонах • апрель 2011 г - запуск iFunny • iFunny сейчас - более 50 млн установок в США • топ 10 стора в категории развлекательные приложения • Московский офис появился в 2018 году • 80+ сотрудников в Москве • офисы компании: Лимасcол, Лос-Анджелес, Москва, Пенза
  3. Что предстоит? • Существующие инструменты сбора данных • Наше решение

    для сбора данных • Технические данные для аналитики • Как их получать • Как мы их анализируем 3
  4. Цель —максимальное понимание работы приложения у пользователей с технической точки

    зрения 4
  5. Почему это важно? • Техническая аналитика дает возможность узнать о

    проблемных местах в приложении 5
  6. Почему это важно? • Техническая аналитика дает возможность узнать о

    проблемных местах в приложении • Устранение проблем улучшает UX 6
  7. Почему это важно? • Техническая аналитика дает возможность узнать о

    проблемных местах в приложении • Устранение проблем улучшает UX • Улучшенный UX увеличивает retention 7
  8. Почему это важно? • Техническая аналитика дает возможность узнать о

    проблемных местах в приложении • Устранение проблем улучшает UX • Улучшенный UX увеличивает retention • С увеличением retention’а увеличивается профит 8
  9. Существующие инструменты 1 9

  10. Сбор и анализ данных. Существующие решения 10 • Android Vitals

    (из коробки)
  11. Сбор и анализ данных. Существующие решения 11 • Android Vitals

    (из коробки) • Firebase (самое популярное)
  12. Сбор и анализ данных. Существующие решения 12 • Android Vitals

    (из коробки) • Firebase (самое популярное) • Fabric ( -> ) =
  13. Сбор и анализ данных. Существующие решения 13 • Android Vitals

    (из коробки) • Firebase (самое популярное) • Fabric • Flurry (устарело, неудобно) ( -> ) =
  14. Сбор и анализ данных. Существующие решения 14 • Android Vitals

    (из коробки) • Firebase (самое популярное) • Fabric • Flurry (устарело, неудобно) • Mixpanel (клево, но платно) ( -> ) =
  15. Сбор и анализ данных. Существующие решения 15 • Android Vitals

    (из коробки) • Firebase (самое популярное) • Fabric • Flurry (устарело, неудобно) • Mixpanel (клево, но платно) • Amplitude (клево, но платно) ( -> ) =
  16. Наша реализация 2 16

  17. Почему мы сделали свою реализацию? 17 • Нужно было найти

    причину нативных крешей и ООМ
  18. Нативные креши 18 • SIGTRAP 0x00000000d4cb31b6 • signal 11 (SIGSEGV),

    code 1 (SEGV_MAPERR) • SIGABRT 0x00000000000062b8 • SIGSEGV 0x0000007f640d7800
  19. Нативные креши 19 backtrace: #00 pc 000000000004ad20 /system/lib/libc.so (tgkill+12) #01

    pc 00000000000484b3 /system/lib/libc.so (pthread_kill+34) #02 pc 000000000001dd89 /system/lib/libc.so (raise+10) #03 pc 0000000000019511 /system/lib/libc.so (__libc_android_abort+34) #04 pc 0000000000017150 /system/lib/libc.so (abort+4) #05 pc 000000000000c6b3 /system/lib/libcutils.so (__android_log_assert+114) #06 pc 000000000003cf15 /system/lib/libhwui.so #07 pc 0000000000027183 /system/lib/libhwui.so #08 pc 0000000000025761 /system/lib/libhwui.so #09 pc 00000000000281d1 /system/lib/libhwui.so #10 pc 0000000000028a73 /system/lib/libhwui.so #11 pc 0000000000029aa1 /system/lib/libhwui.so (_ZN7android10uirenderer12renderthread12RenderThread10threadLoopEv+80) #12 pc 000000000000e369 /system/lib/libutils.so (_ZN7android6Thread11_threadLoopEPv+144) #13 pc 000000000006a345 /system/lib/libandroid_runtime.so (_ZN7android14AndroidRuntime15javaThreadShellEPv+80) #14 pc 0000000000047f83 /system/lib/libc.so (_ZL15__pthread_startPv+22) #15 pc 000000000001a151 /system/lib/libc.so (__start_thread+6)
  20. Почему мы сделали свою реализацию? 20 • Нужно было найти

    причину нативных крешей и ООМ • Нужно было свободно комбинировать данные из разных событий
  21. Почему мы сделали свою реализацию? 21 • Нужно было найти

    причину нативных крешей и ООМ • Нужно было свободно комбинировать данные из разных событий • Хотели проверить корректность показателей уже интегрированных аналитических инструментов (Android Vitals, Firebase, Fabric)
  22. Почему мы сделали свою реализацию? 22 • Нужно было найти

    причину нативных крешей и ООМ • Нужно было свободно комбинировать данные из разных событий • Хотели проверить корректность показателей уже интегрированных аналитических инструментов (Android Vitals, Firebase, Fabric) • У нас уже была реализована своя продуктовая аналитика
  23. Наша реализация. Backend 23

  24. Наша реализация. Backend 24

  25. Наша реализация. Backend 25

  26. Наша реализация. Backend 26

  27. Наша реализация. Backend 27

  28. Наша реализация. Backend 28

  29. Наша реализация. Backend 29

  30. Наша реализация. Backend 30

  31. Наша реализация. Backend 31

  32. Наша реализация. Backend 32

  33. Наша реализация. Backend 33

  34. Наша реализация. Backend 34

  35. Наша реализация. Client 35

  36. Наша реализация. Client 36

  37. Наша реализация. Client 37

  38. Наша реализация. Client 38

  39. Наша реализация. Client 39

  40. Наша реализация. Client 40

  41. Наша реализация. Client 41

  42. Наша реализация. Client 42

  43. Наша реализация. Client 43

  44. Наша реализация. Client 44

  45. Наша реализация. Client 45

  46. Наша реализация. Client 46

  47. Наша реализация. Client 47

  48. Наша реализация. Client 48

  49. Наша реализация. Client 49

  50. Наша реализация. Client 50

  51. Наша реализация. Client 51

  52. Наша реализация. Client 52

  53. Наша реализация. Client 53

  54. Наша реализация. Client 54

  55. Наша реализация. Client 55

  56. Наша реализация. Client 56

  57. Наша реализация. Client 57

  58. Наша реализация. Client 58

  59. Наша реализация. Client 59

  60. Наша реализация. Client 60

  61. Технические данные для аналитики 3 61

  62. Технические данные для аналитики • VM креши 62

  63. Технические данные для аналитики • VM креши • Нативные креши

    63
  64. Технические данные для аналитики • VM креши • Нативные креши

    • Потоки 64
  65. Технические данные для аналитики • VM креши • Нативные креши

    • Потоки • Оперативная память 65
  66. Технические данные для аналитики • VM креши • Нативные креши

    • Потоки • Оперативная память • Дисковая память 66
  67. Технические данные для аналитики • VM креши • Нативные креши

    • Потоки • Оперативная память • Дисковая память • Батарея 67
  68. Технические данные для аналитики • VM креши • Нативные креши

    • Потоки • Оперативная память • Дисковая память • Батарея 68 • Frame rate
  69. Технические данные для аналитики • VM креши • Нативные креши

    • Потоки • Оперативная память • Дисковая память • Батарея 69 • Frame rate • Bundle
  70. Технические данные для аналитики • VM креши • Нативные креши

    • Потоки • Оперативная память • Дисковая память • Батарея 70 • Frame rate • Bundle • Safety Net
  71. Технические данные для аналитики • VM креши • Нативные креши

    • Потоки • Оперативная память • Дисковая память • Батарея 71 • Frame rate • Bundle • Safety Net • Fabric init
  72. 4 72 Как получать данные

  73. 4.1 73 VM креши

  74. VM креши. Зачем? 74 • Привязать креш к действиям пользователя

  75. VM креши. Зачем? 75 • Привязать креш к действиям пользователя

    • Узнать, есть ли расхождения с другими инструментами
  76. VM креши 76 class OwnExceptionHandler : Thread.UncaughtExceptionHandler { private var

    previousHandler: Thread.UncaughtExceptionHandler? = null fun setup() { previousHandler = Thread.getDefaultUncaughtExceptionHandler() Thread.setDefaultUncaughtExceptionHandler(this) } override fun uncaughtException(t: Thread, e: Throwable) { if (e is OutOfMemoryError) { collectOutOfMemoryEvent() } val crashLogEvent = crashInfoCollector.collectCrashInfo(Log.getStackTraceString(e), CrashType.VM) collectCrashedEvent(crashLogEvent) previousHandler?.uncaughtException(t, e) } }
  77. 4.2 77 Нативные креши

  78. Нативные креши. Зачем? 78 • Привязать креш к другим тех.

    метриками
  79. Нативные креши. Зачем? 79 • Привязать креш к другим тех.

    метриками • Привязать креш к действиям пользователя
  80. Нативные креши. Зачем? 80 • Привязать креш к другим тех.

    метриками • Привязать креш к действиям пользователя • Узнать, есть ли расхождения с другими инструментами
  81. Нативные креши 81 https://github.com/google/breakpad

  82. Нативные креши 82 https://github.com/google/breakpad https://github.com/yinyinnie/breakpad-for-android

  83. Нативные креши 83 https://github.com/google/breakpad https://github.com/yinyinnie/breakpad-for-android Breakpad-for-android 这是库简单封装了了Google Breakpad, 加上jni层,让你⼀一键集成Breakpad到项⽬目中

  84. Нативные креши 84 https://github.com/google/breakpad https://github.com/yinyinnie/breakpad-for-android Breakpad-for-android 这是库简单封装了了Google Breakpad, 加上jni层,让你⼀一键集成Breakpad到项⽬目中 Breakpad-for-android

    This is a library that simply wraps Google Breakpad, plus the jni layer, allowing you to integrate Breakpad into your project with one click.
  85. Нативные креши. Инициализация 85 NativeBreakpad.init(dir.getAbsolutePath());

  86. Нативные креши. Сбор данных 86 @WorkerThread fun doWork(dirName: String): CoworkResult

    { val dir = File(dirName) val files = dir.listFiles() if (files == null || files.isEmpty()) { return CoworkResult.Success } for (file in files) { var deleted = false try { deleted = file.delete() } catch (e: SecurityException) { Assert.fail(e) } //Some logic to ignore this file next time } val crashLogEvent = crashInfoCollector.collectCrashInfo(null, CrashType.Native) GlobalScope.launch { delay(COLLECT_DELAY) jobRunnerProxy.jobRunner.runAppCrashedEvent(crashLogEvent) } return CoworkResult.Success }
  87. Нативные креши 87

  88. Нативные креши 88

  89. Нативные креши 89

  90. Нативные креши 90

  91. Нативные креши 91 …/breakpad-for-android/third_party/breakpad/src/client/linux/handler/exception_handler.cc // This function runs in a

    compromised context: see the top of the file. // Runs on the crashing thread. // static void ExceptionHandler::SignalHandler(int sig, siginfo_t* info, void* uc) { . . . bool handled = false; for (int i = g_handler_stack_->size() - 1; !handled && i >= 0; --i) { handled = (*g_handler_stack_)[i]->HandleSignal(sig, info, uc); } // Upon returning from this signal handler, sig will become unmasked and then // it will be retriggered. If one of the ExceptionHandlers handled it // successfully, restore the default handler. Otherwise, restore the // previously installed handler. Then, when the signal is retriggered, it will // be delivered to the appropriate handler. if (handled) { InstallDefaultHandler(sig); } else { RestoreHandlersLocked(); } . . . }
  92. Нативные креши 92 …/breakpad-for-android/third_party/breakpad/src/client/linux/handler/exception_handler.cc // This function runs in a

    compromised context: see the top of the file. // Runs on the crashing thread. // static void ExceptionHandler::SignalHandler(int sig, siginfo_t* info, void* uc) { . . . bool handled = false; for (int i = g_handler_stack_->size() - 1; !handled && i >= 0; --i) { handled = (*g_handler_stack_)[i]->HandleSignal(sig, info, uc); } RestoreHandlersLocked(); . . . }
  93. Нативные креши 93 …/breakpad-for-android/third_party/breakpad/src/client/linux/handler/exception_handler.cc // Runs before crashing: normal context.

    // static bool ExceptionHandler::InstallHandlersLocked() { . . . struct sigaction sa; memset(&sa, 0, sizeof(sa)); . . . sa.sa_sigaction = SignalHandler; sa.sa_flags = SA_ONSTACK | SA_SIGINFO; . . . }
  94. Нативные креши 94 …/breakpad-for-android/third_party/breakpad/src/client/linux/handler/exception_handler.cc // Runs before crashing: normal context.

    // static bool ExceptionHandler::InstallHandlersLocked() { . . . struct sigaction sa; memset(&sa, 0, sizeof(sa)); . . . sa.sa_sigaction = SignalHandler; sa.sa_flags = SA_ONSTACK | SA_SIGINFO | SA_RESETHAND; . . . }
  95. Нативные креши 95

  96. 4.3 96 Потоки

  97. Потоки. Зачем? 97 • Определить сторонние зависимости, которые генерируют большое

    кол-во потоков (и забывают из останавливать)
  98. Потоки. Зачем? 98 • Определить сторонние зависимости, которые генерируют большое

    кол-во потоков (и забывают из останавливать) • Проверить гипотезу с OOM
  99. Потоки. Зачем? 99 • Определить сторонние зависимости, которые генерируют большое

    кол-во потоков (и забывают из останавливать) • Проверить гипотезу с OOM • Проверить гипотезу с наивными крешами
  100. Потоки 100 val threads : Map<Thread, Array<StackTraceElement>> = Thread.getAllStackTraces()

  101. Потоки 101 • Количество потоков val threads : Map<Thread, Array<StackTraceElement>>

    = Thread.getAllStackTraces()
  102. Потоки 102 • Количество потоков • Имена потоков (thread.name) val

    threads : Map<Thread, Array<StackTraceElement>> = Thread.getAllStackTraces()
  103. Потоки 103 • Количество потоков • Имена потоков (thread.name) •

    Состояние потоков (thread.state) val threads : Map<Thread, Array<StackTraceElement>> = Thread.getAllStackTraces()
  104. Потоки 104 public enum State { NEW, RUNNABLE, BLOCKED, WAITING,

    TIMED_WAITING, TERMINATED; }
  105. 4.4 105 Оперативная память

  106. Оперативная память. Зачем? 106 • Определить места для оптимизации

  107. Оперативная память. Зачем? 107 • Определить места для оптимизации •

    Мониторинг влияния изменений на оперативную память
  108. Оперативная память 108 fun Context.getMemorySnapshot(): MemorySnapshot { val memoryInfo =

    ActivityManager.MemoryInfo() val activityManager = this.applicationContext.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager activityManager.getMemoryInfo(memoryInfo) val totalSystemMemory = memoryInfo.totalMem //bytes val freeSystemMemory = memoryInfo.availMem //bytes val processInfo = activityManager.getProcessMemoryInfo(arrayOf(Process.myPid()).toIntArray()) val usedProcessMemory = processInfo.map { it.totalPss }.sum().toLong() // KBytes return MemorySnapshot(freeSystemMemory.toInt(), totalSystemMemory.toInt(), usedProcessMemory.toInt()) }
  109. Оперативная память 109

  110. 4.5 110 Дисковая память

  111. Дисковая память. Зачем? 111 • Определить места для оптимизации •

    Мониторинг влияния изменений на физическую память
  112. Дисковая память 112 val stat = StatFs(Environment.getDataDirectory().path)

  113. Дисковая память 113 public class StatFs { private StructStatVfs mStat;

    . . @Deprecated public int getBlockSize() { return (int) mStat.f_frsize; } public long getBlockSizeLong() { return mStat.f_frsize; } @Deprecated public int getBlockCount() { return (int) mStat.f_blocks; } public long getBlockCountLong() { return mStat.f_blocks; } @Deprecated public int getFreeBlocks() { return (int) mStat.f_bfree; } public long getFreeBlocksLong() { return mStat.f_bfree; } public long getFreeBytes() { return mStat.f_bfree * mStat.f_frsize; } @Deprecated public int getAvailableBlocks() { return (int) mStat.f_bavail; } public long getAvailableBlocksLong() { return mStat.f_bavail; } public long getAvailableBytes() { return mStat.f_bavail * mStat.f_frsize; } public long getTotalBytes() { return mStat.f_blocks * mStat.f_frsize; } }
  114. Дисковая память 114 public class StatFs { private StructStatVfs mStat;

    . . @Deprecated public int getBlockSize() { return (int) mStat.f_frsize; } public long getBlockSizeLong() { return mStat.f_frsize; } @Deprecated public int getBlockCount() { return (int) mStat.f_blocks; } public long getBlockCountLong() { return mStat.f_blocks; } @Deprecated public int getFreeBlocks() { return (int) mStat.f_bfree; } public long getFreeBlocksLong() { return mStat.f_bfree; } public long getFreeBytes() { return mStat.f_bfree * mStat.f_frsize; } @Deprecated public int getAvailableBlocks() { return (int) mStat.f_bavail; } public long getAvailableBlocksLong() { return mStat.f_bavail; } public long getAvailableBytes() { return mStat.f_bavail * mStat.f_frsize; } public long getTotalBytes() { return mStat.f_blocks * mStat.f_frsize; } }
  115. Дисковая память 115 val storageStatsManager = context.getSystemService(Context.STORAGE_STATS_SERVICE) as StorageStatsManager? if

    (storageStatsManager == null) { return } val storageStats = storageStatsManager.queryStatsForPackage(StorageManager.UUID_DEFAULT, context.packageName, Binder.getCallingUserHandle())
  116. Дисковая память 116 /** * Storage statistics for a UID,

    package, or {@link UserHandle} on a single * storage volume. * * @see StorageStatsManager */ public final class StorageStats implements Parcelable { /** {@hide} */ public long codeBytes; /** {@hide} */ public long dataBytes; /** {@hide} */ public long cacheBytes; public @BytesLong long getAppBytes() { return codeBytes; } public @BytesLong long getDataBytes() { return dataBytes; } public @BytesLong long getCacheBytes() { return cacheBytes; } . . . }
  117. Дисковая память 117 <uses-permission android:name="android.permission.GET_PACKAGE_SIZE" />

  118. Дисковая память 118 Added in API level 26

  119. Дисковая память 119

  120. Дисковая память 120 Greylist reflection Target API 28 ~9300 methods

  121. Дисковая память 121 Landroid/content/pm/PackageManager;-> getPackageSizeInfo(Ljava/lang/String;Landroid/content/pm/ IPackageStatsObserver;)V

  122. Дисковая память 122 class PackageStatsCollector(private val context: Context) : PackageStatsCollector

    { override fun collectStats(): Observable<PackageStats> { return Observable.create { emitter -> val packageManager = context.packageManager val getPackageSizeInfo = packageManager::class.java.getMethod("getPackageSizeInfo", String::class.java, IPackageStatsObserver::class.java) getPackageSizeInfo.invoke(packageManager, context.packageName, PackageStatsListener(emitter)) } } }
  123. Дисковая память 123 class PackageStatsListener(private val emitter: ObservableEmitter<PackageStats>) : IPackageStatsObserver.Stub()

    { override fun onGetStatsCompleted(pStats: android.content.pm.PackageStats?, succeeded: Boolean) { if (emitter.isDisposed) { return } if (succeeded && pStats != null) { emitter.onNext(PackageStats(pStats.codeSize, pStats.cacheSize, pStats.dataSize)) emitter.onComplete() } else { emitter.tryOnError(Throwable()) } } }
  124. Дисковая память 124 class PackageStatsListener(private val emitter: ObservableEmitter<PackageStats>) : IPackageStatsObserver.Stub()

    { override fun onGetStatsCompleted(pStats: android.content.pm.PackageStats?, succeeded: Boolean) { if (emitter.isDisposed) { return } if (succeeded && pStats != null) { emitter.onNext(PackageStats(pStats.codeSize, pStats.cacheSize, pStats.dataSize)) emitter.onComplete() } else { emitter.tryOnError(Throwable()) } } } IPackageStatsObserver.Stub()
  125. Дисковая память 125

  126. Дисковая память 126 /** * API for package data change

    related callbacks from the Package Manager. * Some usage scenarios include deletion of cache directory, generate * statistics related to code, data, cache usage * {@hide} */ oneway interface IPackageStatsObserver { void onGetStatsCompleted(in PackageStats pStats, boolean succeeded); }
  127. Дисковая память 127 -keep class android.content.pm.IPackageStatsObserver { *; }

  128. Дисковая память 128

  129. 4.6 129 Батарея

  130. Батарея. Зачем? 130 • Мониторинг влияния изменений на потребление батареи

  131. Батарея 131 private class BatteryChangeReceiver(private val batteryWatcher: BatteryWatcher) : BroadcastReceiver()

    { override fun onReceive(context: Context, intent: Intent) { batteryWatcher.onBatteryEvent(intent) } }
  132. Батарея 132 context.registerReceiver(batteryChangeReceiver, IntentFilter(Intent.ACTION_BATTERY_CHANGED)) context.unregisterReceiver(batteryChangeReceiver)

  133. Батарея 133 public class BatteryManager { public static final String

    EXTRA_STATUS = "status"; public static final String EXTRA_HEALTH = "health"; public static final String EXTRA_PRESENT = "present"; public static final String EXTRA_LEVEL = "level"; public static final String EXTRA_BATTERY_LOW = "battery_low"; public static final String EXTRA_SCALE = "scale"; public static final String EXTRA_ICON_SMALL = "icon-small"; public static final String EXTRA_PLUGGED = "plugged"; public static final String EXTRA_VOLTAGE = "voltage"; public static final String EXTRA_TEMPERATURE = "temperature"; public static final String EXTRA_TECHNOLOGY = "technology"; public static final String EXTRA_INVALID_CHARGER = "invalid_charger"; public static final String EXTRA_MAX_CHARGING_CURRENT = "max_charging_current"; public static final String EXTRA_MAX_CHARGING_VOLTAGE = "max_charging_voltage"; public static final String EXTRA_CHARGE_COUNTER = "charge_counter"; public static final String EXTRA_SEQUENCE = "seq"; . . . }
  134. Батарея 134 public class BatteryManager { public static final String

    EXTRA_STATUS = "status"; public static final String EXTRA_HEALTH = "health"; public static final String EXTRA_PRESENT = "present"; public static final String EXTRA_LEVEL = "level"; public static final String EXTRA_BATTERY_LOW = "battery_low"; public static final String EXTRA_SCALE = "scale"; public static final String EXTRA_ICON_SMALL = "icon-small"; public static final String EXTRA_PLUGGED = "plugged"; public static final String EXTRA_VOLTAGE = "voltage"; public static final String EXTRA_TEMPERATURE = "temperature"; public static final String EXTRA_TECHNOLOGY = "technology"; public static final String EXTRA_INVALID_CHARGER = "invalid_charger"; public static final String EXTRA_MAX_CHARGING_CURRENT = "max_charging_current"; public static final String EXTRA_MAX_CHARGING_VOLTAGE = "max_charging_voltage"; public static final String EXTRA_CHARGE_COUNTER = "charge_counter"; public static final String EXTRA_SEQUENCE = "seq"; . . . } val level = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, Int.MIN_VALUE)
  135. 4.7 135 Frame rate

  136. Frame rate. Зачем? 136 • Мониторинг плавности работы приложения

  137. Frame rate 137 class FrameRateCalculator constructor(windowManager: WindowManager) : Choreographer.FrameCallback {

    private val deviceRefreshRateMillis = DateUtils.SECOND_IN_MILLIS / windowManager.defaultDisplay.refreshRate private var lastFrameTimeNanos: Long = -1 override fun doFrame(frameTimeNanos: Long) { if (lastFrameTimeNanos == -1) { Choreographer.getInstane().postFrameCallback(this) lastFrameTimeNanos = frameTimeNanos return } val droppedFramesCount = droppedCount(lastFrameTimeNanos, frameTimeNanos, deviceRefreshRateMillis) . . . lastFrameTimeNanos = frameTimeNanos Choreographer.getInstance().postFrameCallback(this) } }
  138. Frame rate 138 fun droppedCount(start: Long, end: Long, deviceRefreshRate: Float):

    Int { var count = 0 val diffNs = end - start val diffMs = TimeUnit.MILLISECONDS.convert(diffNs, TimeUnit.NANOSECONDS) val dev = deviceRefreshRate.roundToLong() if (diffMs > dev) { val droppedCount = diffMs / dev count = droppedCount.toInt() } return count }
  139. 4.8 139 Bundle

  140. Bundle. Зачем? 140 • Оптимизация сохранения с помощью Bundle для

    предотвращения TransactionTooLargeException
  141. Bundle 141 fun Bundle.getSizeInBytes(): Int { val parcel = Parcel.obtain()

    return try { parcel.writeValue(this) parcel.dataSize() } finally { parcel.recycle() } }
  142. Bundle 142 final class FragmentManagerState implements Parcelable { FragmentState[] mActive;

    int[] mAdded; BackStackState[] mBackStack; int mPrimaryNavActiveIndex = -1; int mNextFragmentIndex; . . . }
  143. Bundle 143 final class FragmentState implements Parcelable { final String

    mClassName; final int mIndex; final boolean mFromLayout; final int mFragmentId; final int mContainerId; final String mTag; final boolean mRetainInstance; final boolean mDetached; final Bundle mArguments; final boolean mHidden; Bundle mSavedFragmentState; Fragment mInstance; . . . }
  144. Bundle 144

  145. Bundle 145 data class FragmentBundle(val name: String, val bundle: Bundle)

    fun Bundle.getFragmentsStateList(): List<FragmentBundle>? { try { val fragmentManagerState: FragmentManagerState? = getParcelable("android:support:fragments") val active = fragmentManagerState?.mActive ?: return emptyList() return active.filter { it.mSavedFragmentState != null }.map { fragmentState -> FragmentBundle(fragmentState.mClassName, fragmentState.mSavedFragmentState) } } catch (throwable: Throwable) { Assert.fail(throwable) return null } }
  146. 5 146 Как мы анализируем полученные данные

  147. Grafana 147

  148. Grafana 148

  149. Grafana 149

  150. Grafana 150

  151. Grafana 151

  152. Grafana 152

  153. Grafana 153

  154. Результаты 154 • Native crashes - уменишили в 6-10 раз

  155. Результаты 155 • Native crashes - уменишили в 6-10 раз

    • Threads - уменьшили в 3 раза
  156. Результаты 156 • Native crashes - уменишили в 6-10 раз

    • Threads - уменьшили в 3 раза • OOM - уменишились в 10-15 раз
  157. Результаты 157 • Native crashes - уменишили в 6-10 раз

    • Threads - уменьшили в 3 раза • OOM - уменишились в 10-15 раз • Нашли места для оптимизации памяти
  158. Результаты 158 • Native crashes - уменишили в 6-10 раз

    • Threads - уменьшили в 3 раза • OOM - уменишились в 10-15 раз • Нашли места для оптимизации памяти • Узнали, что почти не лагаем
  159. Результаты 159 • Native crashes - уменишили в 6-10 раз

    • Threads - уменьшили в 3 раза • OOM - уменишились в 10-15 раз • Нашли места для оптимизации памяти • Узнали, что почти не лагаем • Узнали, что не сильно жрём батарею
  160. Результаты 160 • Native crashes - уменишили в 6-10 раз

    • Threads - уменьшили в 3 раза • OOM - уменишились в 10-15 раз • Нашли места для оптимизации памяти • Узнали, что почти не лагаем • Узнали, что не сильно жрём батарею • Избавились от TransactionTooLargeException
  161. Выводы 161 • Прикручивайте техническую аналитику и комбинируйте её с

    продуктовой
  162. Выводы 162 • Прикручивайте техническую аналитику и комбинируйте её с

    продуктовой • Анализируйте
  163. Выводы 163 • Прикручивайте техническую аналитику и комбинируйте её с

    продуктовой • Анализируйте • Исправляйте/оптимизируйте/рефакторите
  164. Выводы 164 • Прикручивайте техническую аналитику и комбинируйте её с

    продуктовой • Анализируйте • Исправляйте/оптимизируйте/рефакторите • Тогда будет счастье
  165. smartdev.vdd@gmail.com @smartdev @fraking_vdg 165

  166. Полезные материалы 166 Статья про backend Sample project (Github) Greylist

  167. 167