Android Interview Review
3.54K members
232 links
Популярные вопросы и ответы с собеседований на Android-разработчика.

Канал для Java-разработчиков @JavaSobes

Связаться с нами @SobesAdmin

https://itsobes.ru
Download Telegram
to view and join the conversation
Как и когда вызывается метод Application.onCreate()?

Следующий шаг после старта процесса с java-приложением – это вызов коллбэка Application.onCreate(). Инициализация приложения происходит в методе ActivityThread.main() до зацикливания очереди сообщений вызовом Looper.loop():

1. При создании объекта ActivityThread инициализируется приватное поле типа ApplicationThread;

2. На ActivityThread вызывается метод attach(), в котором на ActivityManagerService вызывается метод attachApplication(). Этот метод получает объект ApplicationThread как параметр.

3. В ActivityManagerService.attachApplication() происходит инициализация параметров, необходимых для старта приложения, и вызывается метод ApplicationThread.bindApplication().

4. В методе bindApplication() составляется объект AppBindData, содержащий информацию о стартующем приложении, и отправляется сообщение через Handler c кодом BIND_APPLICATION в поле what и объектом AppBindData в поле data. Хэндлер добавляет сообщение в MessageQueue.

5. После окончания отработки ActivityThread.main() и вызова метода Looper.loop() сообщение из очереди обрабатывается в методе handleMessage() хэндлера, созданного в ActivityThread.

6. По what-коду сообщение передается в метод handleBindApplication(), который вызывает LoadedApk.makeApplication(), где создается объект класса Application методом Instrumentation.newApplication();

7. Далее с созданным объектом Application в качестве параметра вызывается метод Instrumentation.callApplicationOnCreate(app), который и вызывает метод Application.onCreate().

#OS
В каких случаях возникает ANR?

Application Not Responding – это системный диалог, который показывается пользователю, когда андроид-приложение зависает.

Система показывает ANR-диалог если:
• Не поступает ответа на UI-событие, например нажатие на экран, в течение 5 секунд;
BroadcastReceiver не завершил свое выполнение в течение 10 секунд.

Частая причина возникновения ANR – это выполнение I/O операций, таких как чтение и запись в базу данных, в UI потоке.
Для избежания ANR следует выполнять любые потенциально длительные операции асинхронно в бэкграунд потоке.

#System
Как работать с Backpressure в RxJava?

Backpressure – это ситуация, когда Rx-продюсер производит больше элементов, чем может обработать консьюмер.
Например Observable создает 1000 элементов в секунду, а Observer обрабатывает 1 элемент в секунду. Observable имеет бесконечный буфер, в который будут добавляться элементы до тех пор, пока не случится OutOfMemoryError.

В случаях когда возможна ситуация backpressure, следует использовать Flowable в качестве Rx-стрима.
Flowable можно создать из Observable методом toFlowable(), который принимает объект типа BackpressureStrategy как параметр.
BackpressureStrategy – это enum, который задает стратегию обработки backpressure. Значения BackpressureStrategy:

MISSING. Продюсер не имеет стратегию работы с backpressure. В этом случае консьюмер должен решать проблему переполнения. Эта конфигурация полезна в случае, когда обработка backpressure задается операторами onBackpressure...().
Если backpressure не обрабатывается, то будет брошено исключение MissingBackpressureException.

ERROR. В случае backpressure бросается исключение MissingBackpressureException.

BUFFER. Все элементы добавляются в буфер, пока не будут обработаны консьюмером.

DROP. Все новые элементы, которые консьюмер не успевает обработать, удаляются и не доставляются консьюмеру.

LATEST. Стратегия противоположная Drop. Консьюмер получает только самый последний элемент, созданный продюсером, когда освобождается.

Подробнее про backpressure.

#RxJava
Как изменилось поведение onResume() и onPause() в Android 10?

В Android 10 добавлена поддержка foldables и девайсов с большим экраном. В связи с этим было изменено поведение коллбэков onResume() и onPause() в режиме multi-window.

В Android 9 только активити, с которой взаимодействует пользователь, находилась в состоянии resumed, а все остальные активити на экране имели состояние paused.

Начиная с Android 10, все видимые активити в режиме multi-window находятся в состоянии resumed. Это поведение называется multi-resume.

Активити, с которой взаимодействует пользователь, называется topmost resumed.
Для того, чтобы различать resumed и topmost resumed активити, в Android 10 добавлен коллбэк onTopResumedActivityChanged(isTopResumed: Boolean). Этот метод вызывается, когда активити получает или теряет состояние topmost.

#Activity
Как изменился location permission в Android 10?

В Android 10 добавлена возможность запретить получение данных локации, когда приложение работает в бэкграунде.

Для реализации этой функциональности в Android API v29 добавлен новый permission ACCESS_BACKGROUND_LOCATION, который влияет только на запросы локации в бэкграунде. Запрос локации считается сделанным не из бэкраунда, в двух случаях:

1. Активити приложения видна пользователю, т.е. находится в состоянии started;

2. Запущен foreground service, у которого атрибут foregroundServiceType имеет значение location.

ACCESS_BACKGROUND_LOCATION – это runtime permission. При запросе этого разрешения пользователь видит диалог, изображенный на скриншоте.

Если запрашивать только стандартные разрешения ACCESS_FINE_LOCATION или ACCESS_COARSE_LOCATION, то диалог не будет иметь опцию «Allow all the time».

#Permissions
Что такое и для чего нужен foreground service type?

В Android 10 добавили атрибут foregroundServiceType для элемента <service> в AndroidManifest. Этот атрибут дает понять системе к какой категории отнести сервис.

В API level 29 существует 6 типов сервисов.
Тип location влияет на список необходимых пермишенов для запроса локации. Получение локации из сервиса с типом location считается запросом из фореграунда и пермишн ACCESS_BACKGROUND_LOCATION не требуется.

Остальные типы пока не имеют прямого влияния на работу приложения, но это может измениться в будущих версиях API.

#Service
Какие ограничения на старт активити введены в Android 10?

В Android 10 введены ограничения на старт активити из бэкграунда. Эти ограничения уменьшают случаи прерывания UX без активного действия со стороны пользователя.

Вместо старта активити из бэкграунда предлагается использовать нотификации. Этот способ взаимодействия дает пользователю больше контроля над тем, какие приложения и когда запускаются.

В случае со стартом активити приложение с foreground-сервисом рассматривается как работающее в бэкграунде. Чтобы приложение считалось запущенным в foreground, оно должно иметь активити в состоянии started. Помимо этого правила, существует ряд исключений.

#Activity
Как трансформировать cold observable в hot?

Существует два способа трансформировать cold observable в hot.

Первый – это использование методов publish() и connect().
Метод publish() создает из observable объект типа ConnectableObservable. ConnectableObservable не начинает рассылать элементы, когда на него подписываются. Рассылка запускается после вызова метода connect(). Когда вызван метод connect(), начинается эмитинг элементов независимо от того, есть ли подписчики.

Второй способ – обернуть observable в subject, как показано на картинке. В этом случае эмитинг элементов оригинального observable стартует, когда на него подписывается subject. А subject, являясь hot observable, рассылает элементы независимо от наличия подписчиков.

#RxJava
Как трансформировать hot observable в cold?

Это можно сделать комбинацией методов replay() и autoConnect(0).

Метод replay() создает объект ConnectableObservable, который кэширует все элементы, отправляемые оригинальным hot observable.

Как было описано ранее, ConnectableObservable начинает эмитить элементы, когда на нем вызывается метод connect().

Чтобы получить поведение обычного cold observable, на ConnectableObservable вызывается метод autoConnect(numberOfSubscribers: Int). Этот метод принимает параметром переменную типа Int, которая задает количество подписчиков, необходимых для вызова connect(). Если параметром передано неположительное число, то connect() вызывается сразу.

В результате последовательного вызова replay() и autoConnect(0) создается observable, который эмитит все закэшированные элементы при вызове на нем subscribe(), т.е. ведет себя как cold observable.

#RxJava
В чем разница между flatMap(), concatMap() и switchMap() в RxJava?

flatMap() разделяет rx-стрим на несколько промежуточных стримов, назовем их [A, B, C], и затем соединяет результат в один стрим. Элементы промежуточных стримов передаются напрямую в конечный стрим без гарантии сохранения порядка, в котором созданы A, B и C. Конечный результат может быть таким: [C1, A1, A2, B1, C2, A3, B2, B3, C3].

concatMap() работает похожим на flatMap() образом, но сохраняет порядок промежуточных стримов. Результат будет: [A1, A2, A3, B1, B2, B3, C1, C2, C3].

switchMap() похож на flatMap() и также как concatMap() сохраняет порядок. Но при использовании switchMap() каждый предыдущий промежуточный стрим останавливается в тот момент, когда стартует следующий. Результат может выглядеть так: [A1, A2, B1 C1, C2, C3].

Подробнее

#RxJava
Что такое Window?

Window – это абстрактный класс, который не является наследником Activity, Fragment или View. Класс Window контролирует что и как рисуется на экране.

Активити имеет один инстанс Window, который можно получить методом getWindow(). Window, в свою очередь, имеет объект Surface и единственную иерархию View.

Android-приложение использует WindowManager для создания объектов типа Window и Surface, на котором рисуется контент Window.

Когда UI должен обновиться, на объекте Surface вызывается метод lockCanvas(), который возвращает объект типа Canvas. Canvas передается вниз по иерархии View, ассоциированной с Window, и каждая view рисует себя на канвасе.

#View
Как работает метод dispatchTouchEvent()? (1/2)

В Андроиде метод dispatchTouchEvent(event: MotionEvent) с одинаковой сигнатурой есть в двух классах: Activity и View. Этот метод помогает доставить объект MotionEvent до View, которой он предназначен.

Когда пользователь дотрагивается до экрана в области запущенного приложения, система доставляет объект MotionEvent в активити. Вызывается метод Activity.dispatchTouchEvent().

Activity.dispatchTouchEvent() делегирует вызов в класс Window, на котором вызывается superDispatchTouchEvent(). Window, в свою очередь, вызывает dispatchTouchEvent() на decor view, которая является первой view в иерархии, связанной с Window.

#View
Как работает метод dispatchTouchEvent()? (2/2)

Decor View – это верхний ViewGroup в иерархии view, ассоциированной с Window.

В классе ViewGroup метод dispatchTouchEvent() переопределен и имеет отличную реализацию от класса View, поэтому рассмотрим их отдельно.

ViewGroup.dispatchTouchEvent() определяет, следует ли отправить ивент в одну из дочерних view или обработать в текущей ViewGroup:

1. Сначала вызывается метод onInterceptTouchEvent(). Если этот метод возвращает false, то ивент пробрасывается вниз по иерархии view, иначе ViewGroup обрабатывает ивент сама;

2. Проверяются координаты каждой дочерней view и координаты объекта MotionEvent. Если MotionEvent входит в границы дочерней view, то на ней вызывается метод dispatchTouchEvent() и объект MotionEvent переходит вниз по иерархии;

3 Если ни одна из дочерних view не подходит для передачи ивента, то текущая ViewGroup считается таргет-view и обрабатывает ивент.


View.dispatchTouchEvent() вызывается только в том случае, когда текущая view уже определена как таргет-view ивента. Этот метод делает следующее:

1. Если view имеет OnTouchListener, то вызывается OnTouchListener.onTouch() с объектом MotionEvent в качестве параметра;

2. Если ивент не обработан листенером, то вызывается метод View.onTouchEvent().

#View
Для чего нужен метод onInterceptTouchEvent()?

Метод ViewGroup.onInterceptTouchEvent(e: MotionEvent) позволяет перехватить ивент во ViewGroup и не отправлять его вниз по иерархии в таргет-view.

Значение, возвращаемое методом onInterceptTouchEvent(), проверяется в dispatchTouchEvent().

Если onInterceptTouchEvent() возвращает false, то dispatchTouchEvent() ищет дочернюю view для обработки ивента и передает ивент вниз по иерархии.

Если onInterceptTouchEvent() возвращает true, то ивент передается на обработку текущей ViewGroup в метод onTouchEvent(). При этом ивент все также передается по иерархии до таргет-view, но MotionEvent.getAction() будет иметь значение ACTION_CANCEL.

#View
Как Touch Event доставляется до таргет-view?

Touch event в Андроиде проходит круг по иерархии view, пока одна из view не обработает его (см. картинку).

Первый класс, который получает touch event – это Activity. Путь от activity вниз по иерархии оповещает ViewGroup на каждом уровне. После достижения таргет-view ивент идет вверх по иерархии. На этом пути каждая из ViewGroup может обработать ивент. Когда ивент обработан таргет-view или одной из ViewGroup, он больше не доставляется на обработку следующим ViewGroup.

Реализовано это поведение следующим образом:

1. Сначала вызывается Activity.dispatchTouchEvent(). Этот метод делегирует вызов в decor view.

2. Ивент идет вниз по иерархии ViewGroup, начиная от decor view.

3. На каждом уровне вызывается метод ViewGroup.dispatchTouchEvent(), который проверяет результат вызова onInterceptTouchEvent() в текущей ViewGroup.

4. Если onInterceptTouchEvent() возвращает true, то происходит короткое замыкание. Ивент перестает спускаться по иерархии и текущая ViewGroup получает возможность первой обработать ивент. Если ViewGroup не обрабатывает ивент, то он поднимается по иерархии.

5. Если onInterceptTouchEvent() возвращает false, то ивент спускается на следующий уровень, пока не доходит до таргет-view.

6. На таргет-view вызывается метод dispatchTouchEvent(), который вызывает OnTouchListener.onTouch(), если листенер задан. Если ивент не обработан листенером, то вызывается View.onTouchEvent().

7. Если таргет-view не обработала ивент, то он поднимается по иерархии, пока не будет обработан или пока не достигнет activity.

ViewGroup – наследник класса View, поэтому обработка ивента на шаге 4 реализована также, как на шаге 6. Т.е. сначала вызывается OnTouchListener.onTouch(), затем ViewGroup.onTouchEvent().

#View
Назовите основные MotionEvent Actions

Наиболее частые actions:

ACTION_DOWN – это первый action, который доставляется в таргет-view, когда пользователь дотрагивается до экрана. Чтобы получить последующие actions, таргет-view должна вернуть true в методе onTouchEvent(). Это значит, что таргет-view заинтересована в обработке ивента. Если вернуть false из onTouchEvent() для ACTION_DOWN, то ивент пойдет на обработку вверх по иерархии, а последующие ивенты не будут доставляться в таргет-view.

ACTION_MOVE – ивенты с этим action происходят, когда пользователь перемещает палец на экране. ACTION_MOVE ивенты будут доставляться только во view, которая вернула true из onTouchEvent() для ACTION_DOWN.

ACTION_UP – последний action в серии тач ивентов. Он также доставляется только во view, которая начала обработку.

ACTION_CANCEL – показывает, что текущий ивент был отменен. Ивенты с ACTION_CANCEL доставляются в таргет-view, если один из родительских ViewGroup перехватил ивент в методе onInterceptTouchEvent().

#View