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

How do we speedup Yandex on Android?

How do we speedup Yandex on Android?

Many Andrdoid developers know standard methods of profiling applications, tools for debugging and performance improvement. However for large applications they are not always sufficient enough. In this talk we will talk about non-standard approaches to performance improvements, we will touch various aspects of both UI, tools, and backend. I hope this talk will inspire you to look at the performance from a different angle and you will find something new applicable to your applications.

Artur Vasilov

November 27, 2018
Tweet

More Decks by Artur Vasilov

Other Decks in Programming

Transcript

  1. Джедайские трюки › Могут дать очень большой буст к скорости!

    › Усложняют ваш код (в каких-то случаях сильно и немного ломают привычную «архитектуру») › Нужны не всегда! 4
  2. 5

  3. Получение данных от сервера › Запуск приложения › Запуск сетевого

    запроса › Ожидание ответа › Чтение данных › Постобработка (парсинг, сохранение) › Передача в UI для отображения 7
  4. Запуск приложения → запуск сетевого запроса Все очевидно, чем быстрее

    ваше приложение выполнит начальную инициализацию, тем быстрее начнется сетевой запрос. Все стандартные способы ускорения запуска здесь применимы. 8
  5. Более ранее начало загрузки › Не раньше Activity, так как

    оптимизация обычно нужна для старта с UI › Абстракция на уровне репозитория / источника данных › Кэширование в памяти / на диске (если получается так, что запрос выполнится раньше, чем есть подписчики) › Например, в Rx – Subject 13
  6. MainActivity class MainActivity : AppCompatActivity() { @Inject lateinit var repository:

    CardsRepository override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) repository.preloadCards() } } 15
  7. Тип соединения › На Wi-Fi / 4G+ все хорошо, улучшения

    могут быть, но за счет других эффектов › 3G / EDGE – все плохо › 2G – все очень плохо › Мы никак не можем на это повлиять! Но наша задача сделать так, чтобы во всех условиях приложение работало хорошо. 17
  8. Меньше данных! › В идеале 0 › Формат (json vs

    protobuf vs …) › Кодирование – gzip, brotli, sdch › Но начнем с более простых идей 18
  9. 19

  10. 20

  11. Данные { "title": "Hello, World", "toolbar": { ... }, "informers":

    [ ... ], "cards": [ { "id": "card1", ... }, {}, {}, ... ] } 21 Много-много
  12. Данные { "title": "Hello, World", "toolbar": { ... }, "informers":

    [ ... ], "cards": [ { "id": "card1", ... }, {}, {}, ... ] } 22 Много-много
  13. Как такого достичь? ▌ «Мгновенная пагинация» › Двойной запрос (с

    основными данными и второй запрос за остальными) › Простое в реализации решение › Положим сервак! › Можно использовать только для 3G-, например ▌ Потоковый парсер json-а 23
  14. Потоковое чтение ответа val request = Request.Builder().url("https://myurl.my").build() val response =

    client.newCall(request).execute() val body = response.body() if (response.isSuccessful && body != null) { val source = body.source() // TODO : start stream parsing } else { // TODO : handle error (don't care here) } 24
  15. Потоковое чтение ответа val source = body.source() while (!source.exhausted()) {

    val byte = source.readByte() if (byte.toChar() == '{') { // TODO : find the moment when we need to stop reading using stream } } 27
  16. Потоковое чтение ответа val source = body.source() val outStream =

    Buffer() while (!source.exhausted()) { val byte = source.readByte() outStream.writeByte(byte.toInt()) if (byte.toChar() == '{') { // TODO : find the moment when we need to stop reading using stream } } 28
  17. Данные { "title": "Hello, World", "toolbar": { ... }, "informers":

    [ ... ], "cards": [ { "id": "card1", ... }, {}, {}, ... ] } 29
  18. Потоковое чтение ответа if (byte.toChar() == '{') { // TODO

    : find the moment when we need to stop reading using stream } 30
  19. Потоковое чтение ответа if (byte.toChar() == '{') { // TODO

    : find the moment when we need to stop reading using stream } outStream.writeByte('}'.toInt()) // TODO : parse out stream 31
  20. Потоковое чтение ответа if (byte.toChar() == '{') { // TODO

    : find the moment when we need to stop reading using stream } outStream.writeByte('}'.toInt()) // TODO : parse out stream outStream.writeAll(source) // TODO : parse remaining response 32
  21. Потоковый парсер › Не надо писать парсер json-а самому! Читаем

    в буфер ровно столько, сколько нужно (смотрим, например, по закрывающим скобкам) и потом парсим целиком часть ответа как обычно! › Не получится засунуть в Retrofit (а если захотеть, то хаков будет больше чем без него) › Но в итоге все скроется за Observable<…> в Repository › Зависимость от корректности данных 33
  22. Данные { "title": "Hello, World", "toolbar": { ... }, "informers":

    [ ... ], "cards": [ { "id": "card1", ... }, {}, {}, ... ] } 34
  23. Неправильные данные { "cards": [ { "id": "card1", ... },

    {}, {}, ... ], "title": "Hello, World", "toolbar": { ... }, "informers": [ ... ] } 35
  24. Исправляем неправильные данные [ { "title": "Hello, World", "toolbar": {

    ... } "informers": [ ... ] }, { "id": "card1", ... }, {}, ... ] 36
  25. Кодирование и форматы › gzip (вы очень плохой человек, если

    у вас его нет) › SDCH? › brotli (нет явной поддержки в Android/OkHttp) › … › protobuf 37
  26. Краш или не краш? MainActivity.kt override fun onCreate(savedInstanceState: Bundle?) {

    super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) Thread { val textView = findViewById<TextView>(R.id.text) textView.text = "Hello World" }.start() } 39
  27. 40 create / inflate measure Android: отобразить View layout draw

    Создание View из кода или xml Расчет размеров View относительно себя и родителя Расчет позиции View и всех детей (для ViewGroup) Рисование View на экране
  28. 41 create / inflate measure Android: отобразить View layout draw

    Создание View из кода или xml (долго) Расчет размеров View относительно себя и родителя (долго) Расчет позиции View и всех детей (для ViewGroup) (долго) Рисование View на экране
  29. 42

  30. Предсоздаем View val recyclerView = findViewById<RecyclerView>(R.id.recyclerView) recyclerView.layoutManager = LinearLayoutManager(this) val

    viewPool = AsyncViewPool.Factory.create(this, this) repeat(10) { viewPool.addView(R.layout.card, recyclerView) } 43
  31. Получаем заранее созданную View class CardAdapter(private val viewPool: AsyncViewPool) :

    RecyclerView.Adapter<CardHolder>() { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CardHolder { val view = viewPool.obtainView(R.layout.card, parent) return CardHolder(view) } // ... } 44
  32. Очень базовая реализация override fun addView(@LayoutRes layoutId: Int, parent: ViewGroup)

    { executorService.execute { val view = inflater.inflate(layoutId, parent, false) var viewList = views[layoutId] if (viewList == null) { viewList = ArrayList() views[layoutId] = viewList } viewList.add(view) } } 45
  33. Очень базовая реализация override fun obtainView(@LayoutRes layoutId: Int, parent: ViewGroup):

    View { val viewList = views[layoutId] if (viewList == null || viewList.isEmpty()) { return inflater.inflate(layoutId, parent, false) } return viewList.removeAt(0) } 46
  34. Возможные проблемы › Кастомная View может использовать вызовы, которые можно

    делать только в main thread › Нужно всегда явно передавать Looper.getMainLooper() › Надо обеспечить некий “happens-before” (см. Java Memory Model) › AsyncLayoutInflater 47 developer.android.com/reference/android/support/v4/view/AsyncLayoutInflater
  35. 48 create / inflate measure Android: отобразить View layout draw

    Создание View из кода или xml (долго) Расчет размеров View относительно себя и родителя (долго) Расчет позиции View и всех детей (для ViewGroup) (долго) Рисование View на экране
  36. 49 create / inflate measure Android: отобразить View layout draw

    Создание View из кода или xml (долго) Расчет размеров View относительно себя и родителя (долго) Расчет позиции View и всех детей (для ViewGroup) (долго) Рисование View на экране
  37. PrecomputedText override fun addView(@LayoutRes layoutId: Int, parent: ViewGroup) { executorService.execute

    { val view = inflater.inflate(layoutId, parent, false) // ... val textView = view.findViewById<TextView>(R.id.cardText) val params : PrecomputedTextCompat.Params = TextViewCompat.getTextMetricsParams(textView) val precomputedText = PrecomputedTextCompat.create("Hello World", params) TextViewCompat.setPrecomputedText(textView, precomputedText) } } 50
  38. PrecomputedText › Крутая штука! › Больше помогает на скролле списка,

    чем на первоначальном создании 51 medium.com/androiddevelopers/prefetch-text-layout-in-recyclerview-4acf9103f438
  39. Прозрачный сплеш › Может создать эффект мгновенного старта › Убедитесь,

    что у вас приложение стартует быстро везде, иначе вас разоблачат! 54
  40. Dagger – проблема public final class MainActivityModule_ProvideCardsRepositoryFactory implements Factory<CardsRepository> {

    private final MainActivityModule module; public MainActivityModule_ProvideCardsRepositoryFactory(MainActivityModule module) { this.module = module; } @Override public CardsRepository get() { return provideInstance(module); } 57
  41. Dagger – проблема public final class MainActivity_MembersInjector implements MembersInjector<MainActivity> {

    private final Provider<CardsRepository> repositoryProvider; public MainActivity_MembersInjector(Provider<CardsRepository> repositoryProvider) { this.repositoryProvider = repositoryProvider; } public static MembersInjector<MainActivity> create(Provider<CardsRepository> repositoryProvider) { return new MainActivity_MembersInjector(repositoryProvider); } @Override public void injectMembers(MainActivity instance) { injectRepository(instance, repositoryProvider.get()); } 58
  42. Dagger › Загрузка классов довольно медленная штука › Нужно грамотно

    разбивать на компоненты (конечно, не надо одну большую ApplicationComponent) › fastInit…? 59 youtube.com/watch?v=PBrhRvhF00k
  43. Redex / Interdex › Позволяет расставить классы в dex-файле в

    порядке, котором они начинают использоваться › Что-то похожее на то, что происходит в Android 9, но на этапе компиляции › Не очень легко › Коллеги пробовали и получили -10% к времени запуска 61 github.com/facebook/redex/blob/master/docs/Interdex.md